1+ # SPDX-License-Identifier: BSD-2-Clause
2+ import os
3+ import sys
4+ import unittest
5+ import tempfile
6+ from unittest import mock
7+ from pathlib import Path
8+
9+ import jsonschema
10+ import pytest
11+
12+ from chipflow_lib import (
13+ ChipFlowError ,
14+ _get_cls_by_reference ,
15+ _ensure_chipflow_root ,
16+ _parse_config_file ,
17+ _parse_config
18+ )
19+
20+
21+ class TestCoreUtilities (unittest .TestCase ):
22+ def setUp (self ):
23+ # Save original environment to restore later
24+ self .original_chipflow_root = os .environ .get ("CHIPFLOW_ROOT" )
25+ self .original_sys_path = sys .path .copy ()
26+
27+ # Create a temporary directory for tests
28+ self .temp_dir = tempfile .TemporaryDirectory ()
29+ self .temp_path = self .temp_dir .name
30+
31+ def tearDown (self ):
32+ # Restore original environment
33+ if self .original_chipflow_root :
34+ os .environ ["CHIPFLOW_ROOT" ] = self .original_chipflow_root
35+ else :
36+ os .environ .pop ("CHIPFLOW_ROOT" , None )
37+
38+ sys .path = self .original_sys_path
39+ self .temp_dir .cleanup ()
40+
41+ def test_chipflow_error (self ):
42+ """Test that ChipFlowError can be raised and caught properly"""
43+ with self .assertRaises (ChipFlowError ):
44+ raise ChipFlowError ("Test error" )
45+
46+ def test_get_cls_by_reference_valid (self ):
47+ """Test retrieving a class by reference when the module and class exist"""
48+ # unittest.TestCase is a valid class that should be importable
49+ cls = _get_cls_by_reference ("unittest:TestCase" , "test context" )
50+ self .assertEqual (cls , unittest .TestCase )
51+
52+ def test_get_cls_by_reference_module_not_found (self ):
53+ """Test _get_cls_by_reference when the module doesn't exist"""
54+ with self .assertRaises (ChipFlowError ) as cm :
55+ _get_cls_by_reference ("nonexistent_module:SomeClass" , "test context" )
56+
57+ self .assertIn ("Module `nonexistent_module` referenced by test context is not found" , str (cm .exception ))
58+
59+ def test_get_cls_by_reference_class_not_found (self ):
60+ """Test _get_cls_by_reference when the class doesn't exist in the module"""
61+ with self .assertRaises (ChipFlowError ) as cm :
62+ _get_cls_by_reference ("unittest:NonExistentClass" , "test context" )
63+
64+ self .assertIn ("Module `unittest` referenced by test context does not define `NonExistentClass`" , str (cm .exception ))
65+
66+ def test_ensure_chipflow_root_already_set (self ):
67+ """Test _ensure_chipflow_root when CHIPFLOW_ROOT is already set"""
68+ os .environ ["CHIPFLOW_ROOT" ] = "/test/path"
69+ sys .path = ["/some/other/path" ]
70+
71+ result = _ensure_chipflow_root ()
72+
73+ self .assertEqual (result , "/test/path" )
74+ self .assertIn ("/test/path" , sys .path )
75+
76+ def test_ensure_chipflow_root_not_set (self ):
77+ """Test _ensure_chipflow_root when CHIPFLOW_ROOT is not set"""
78+ if "CHIPFLOW_ROOT" in os .environ :
79+ del os .environ ["CHIPFLOW_ROOT" ]
80+
81+ with mock .patch ("os.getcwd" , return_value = "/mock/cwd" ):
82+ result = _ensure_chipflow_root ()
83+
84+ self .assertEqual (result , "/mock/cwd" )
85+ self .assertEqual (os .environ ["CHIPFLOW_ROOT" ], "/mock/cwd" )
86+ self .assertIn ("/mock/cwd" , sys .path )
87+
88+ def test_parse_config_file_valid (self ):
89+ """Test _parse_config_file with a valid config file"""
90+ # Create a temporary config file
91+ config_content = """
92+ [chipflow]
93+ project_name = "test_project"
94+ steps = { silicon = "chipflow_lib.steps.silicon:SiliconStep" }
95+ clocks = { default = "sys_clk" }
96+ resets = { default = "sys_rst_n" }
97+
98+ [chipflow.silicon]
99+ process = "sky130"
100+ package = "caravel"
101+ """
102+ config_path = os .path .join (self .temp_path , "chipflow.toml" )
103+ with open (config_path , "w" ) as f :
104+ f .write (config_content )
105+
106+ config = _parse_config_file (config_path )
107+
108+ self .assertIn ("chipflow" , config )
109+ self .assertEqual (config ["chipflow" ]["project_name" ], "test_project" )
110+ self .assertEqual (config ["chipflow" ]["silicon" ]["process" ], "sky130" )
111+
112+ def test_parse_config_file_invalid_schema (self ):
113+ """Test _parse_config_file with an invalid config file (schema validation fails)"""
114+ # Create a temporary config file with missing required fields
115+ config_content = """
116+ [chipflow]
117+ project_name = "test_project"
118+ # Missing required fields: steps, silicon
119+ """
120+ config_path = os .path .join (self .temp_path , "chipflow.toml" )
121+ with open (config_path , "w" ) as f :
122+ f .write (config_content )
123+
124+ with self .assertRaises (ChipFlowError ) as cm :
125+ _parse_config_file (config_path )
126+
127+ self .assertIn ("Validation error in chipflow.toml" , str (cm .exception ))
128+
129+ @mock .patch ("chipflow_lib._ensure_chipflow_root" )
130+ @mock .patch ("chipflow_lib._parse_config_file" )
131+ def test_parse_config (self , mock_parse_config_file , mock_ensure_chipflow_root ):
132+ """Test _parse_config which uses _ensure_chipflow_root and _parse_config_file"""
133+ mock_ensure_chipflow_root .return_value = "/mock/chipflow/root"
134+ mock_parse_config_file .return_value = {"chipflow" : {"test" : "value" }}
135+
136+ config = _parse_config ()
137+
138+ mock_ensure_chipflow_root .assert_called_once ()
139+ mock_parse_config_file .assert_called_once_with (Path ("/mock/chipflow/root/chipflow.toml" ))
140+ self .assertEqual (config , {"chipflow" : {"test" : "value" }})
0 commit comments