66import json
77from pathlib import Path
88
9- from chipflow_lib .platforms .utils import LockFile , Package , Port
10- from chipflow_lib import ChipFlowError
119from chipflow_lib .pin_lock import (
1210 lock_pins ,
13- count_member_pins ,
14- allocate_pins ,
1511 PinCommand
1612)
13+ from chipflow_lib .config_models import Config
1714
1815
1916class TestPinLockAdvanced (unittest .TestCase ):
@@ -27,10 +24,15 @@ def setUp(self):
2724 self .env_patcher = mock .patch .dict (os .environ , {"CHIPFLOW_ROOT" : self .temp_dir .name })
2825 self .env_patcher .start ()
2926
30- # Create test data
27+ # Create test data - valid for Pydantic Config model
3128 self .mock_config = {
3229 "chipflow" : {
30+ "project_name" : "test_project" ,
31+ "steps" : {
32+ "silicon" : "chipflow_lib.steps.silicon:SiliconStep"
33+ },
3334 "silicon" : {
35+ "process" : "ihp_sg13g2" ,
3436 "package" : "pga144" ,
3537 "pads" : {
3638 "pad1" : {"type" : "io" , "loc" : "1" },
@@ -58,6 +60,127 @@ def tearDown(self):
5860 os .chdir (self .original_cwd )
5961 self .temp_dir .cleanup ()
6062
63+ @mock .patch ('chipflow_lib.pin_lock._parse_config' )
64+ @mock .patch ('chipflow_lib.pin_lock.top_interfaces' )
65+ @mock .patch ('chipflow_lib.pin_lock.PACKAGE_DEFINITIONS' )
66+ @mock .patch ('chipflow_lib.pin_lock.Config.model_validate' )
67+ def test_pydantic_lockfile_creation (self , mock_model_validate , mock_package_defs , mock_top_interfaces , mock_parse_config ):
68+ """Test lock_pins creates a proper LockFile using Pydantic models"""
69+ # Import the Pydantic models we need
70+ from chipflow_lib .platforms .utils import _QuadPackageDef
71+ from chipflow_lib .config_models import SiliconConfig , ChipFlowConfig , PadConfig
72+
73+ # Create a proper PackageDef instance (real Pydantic object, not a mock)
74+ package_def = _QuadPackageDef (name = "test_package" , width = 10 , height = 10 )
75+
76+ # Since we can't modify allocate directly on a Pydantic model instance,
77+ # create a patch for the allocate method at module level
78+ with mock .patch .object (_QuadPackageDef , 'allocate' , autospec = True ) as mock_allocate :
79+ # Configure the mock to return predictable values
80+ mock_allocate .return_value = ["10" , "11" ]
81+
82+ # Set up package definitions with our real Pydantic object
83+ mock_package_defs .__getitem__ .return_value = package_def
84+ mock_package_defs .__contains__ .return_value = True
85+
86+ # Create real Pydantic objects for configuration instead of mocks
87+ # Start with pads and power
88+ pads = {}
89+ for name , config in self .mock_config ["chipflow" ]["silicon" ]["pads" ].items ():
90+ pads [name ] = PadConfig (
91+ type = config ["type" ],
92+ loc = config ["loc" ]
93+ )
94+
95+ power = {}
96+ for name , config in self .mock_config ["chipflow" ]["silicon" ]["power" ].items ():
97+ power [name ] = PadConfig (
98+ type = config ["type" ],
99+ loc = config ["loc" ]
100+ )
101+
102+ # Create a Silicon config object
103+ silicon_config = SiliconConfig (
104+ process = "ihp_sg13g2" ,
105+ package = "pga144" ,
106+ pads = pads ,
107+ power = power
108+ )
109+
110+ # Create the Chipflow config object with proper StepsConfig
111+ from chipflow_lib .config_models import StepsConfig
112+
113+ steps_config = StepsConfig (
114+ silicon = "chipflow_lib.steps.silicon:SiliconStep"
115+ )
116+
117+ chipflow_config = ChipFlowConfig (
118+ project_name = "test_project" ,
119+ silicon = silicon_config ,
120+ steps = steps_config
121+ )
122+
123+ # Create the full Config object
124+ config_model = Config (chipflow = chipflow_config )
125+
126+ # Set up the mock model_validate to return our real Pydantic config object
127+ mock_model_validate .return_value = config_model
128+
129+ # Set up parse_config to return the dict version
130+ mock_parse_config .return_value = self .mock_config
131+
132+ # Mock top interfaces to return something simple
133+ mock_interface = {
134+ "component1" : {
135+ "interface" : {
136+ "members" : {
137+ "uart" : {
138+ "type" : "interface" ,
139+ "members" : {
140+ "tx" : {"type" : "port" , "width" : 1 , "dir" : "o" },
141+ "rx" : {"type" : "port" , "width" : 1 , "dir" : "i" }
142+ }
143+ }
144+ }
145+ }
146+ }
147+ }
148+ mock_top_interfaces .return_value = ({}, mock_interface )
149+
150+ # Call lock_pins with mocked file operations
151+ with mock .patch ('builtins.print' ), \
152+ mock .patch ('pathlib.Path.exists' , return_value = False ), \
153+ mock .patch ('chipflow_lib.pin_lock.LockFile.model_validate_json' ):
154+ lock_pins ()
155+
156+ # Check that a lockfile was created
157+ lockfile_path = Path ('pins.lock' )
158+ self .assertTrue (lockfile_path .exists ())
159+
160+ # Read the lockfile
161+ with open (lockfile_path , 'r' ) as f :
162+ lock_data = json .load (f )
163+
164+ # Verify it has the expected structure (Pydantic model)
165+ self .assertIn ("process" , lock_data )
166+ self .assertIn ("package" , lock_data )
167+ self .assertIn ("port_map" , lock_data )
168+ self .assertIn ("metadata" , lock_data )
169+
170+ # Verify process is correct
171+ self .assertEqual (lock_data ["process" ], "ihp_sg13g2" )
172+
173+ # Verify package
174+ self .assertIn ("package_type" , lock_data ["package" ])
175+
176+ # Verify port_map has the right structure for our uart interface
177+ self .assertIn ("component1" , lock_data ["port_map" ])
178+ self .assertIn ("uart" , lock_data ["port_map" ]["component1" ])
179+ self .assertIn ("power" , lock_data ["package" ])
180+
181+ # Verify port_map has our component
182+ self .assertIn ("component1" , lock_data ["port_map" ])
183+
61184
62185
63186class TestPinCommandCLI (unittest .TestCase ):
0 commit comments