@@ -165,6 +165,18 @@ def test_allocate_pins_port(self):
165165
166166 # Check remaining pins
167167 self .assertEqual (remaining_pins , pins [3 :])
168+
169+ def test_allocate_pins_invalid_type (self ):
170+ """Test allocate_pins with an invalid member type"""
171+ # Create member data with an invalid type - not 'interface' or 'port'
172+ member_data = {
173+ "type" : "invalid_type"
174+ }
175+ pins = ["pin1" , "pin2" , "pin3" ]
176+
177+ # This should cause the function to raise an AssertionError at the "assert False" line
178+ with self .assertRaises (AssertionError ):
179+ allocate_pins ("test_invalid" , member_data , pins )
168180
169181 @mock .patch ("chipflow_lib.pin_lock.lock_pins" )
170182 def test_pin_command_mocked (self , mock_lock_pins ):
@@ -199,6 +211,161 @@ def test_pin_command_mocked(self, mock_lock_pins):
199211 mock_parser .add_subparsers .assert_called_once ()
200212 mock_subparsers .add_parser .assert_called_once ()
201213
214+ @mock .patch ("builtins.open" , new_callable = mock .mock_open )
215+ @mock .patch ("chipflow_lib.pin_lock._parse_config" )
216+ @mock .patch ("chipflow_lib.pin_lock.top_interfaces" )
217+ @mock .patch ("pathlib.Path.exists" )
218+ @mock .patch ("pathlib.Path.read_text" )
219+ @mock .patch ("chipflow_lib.pin_lock.PACKAGE_DEFINITIONS" , new_callable = dict )
220+ @mock .patch ("chipflow_lib.pin_lock.LockFile" )
221+ def test_lock_pins_no_pins_allocated (self , mock_lock_file , mock_package_defs ,
222+ mock_read_text , mock_exists , mock_top_interfaces ,
223+ mock_parse_config , mock_open ):
224+ """Test that lock_pins raises appropriate error when no pins can be allocated"""
225+ # Setup mock package definitions with a special allocate method
226+ # that returns an empty list (no pins allocated)
227+ mock_package_type = MockPackageType (name = "cf20" )
228+ mock_package_type .allocate = mock .MagicMock (return_value = []) # Return empty list
229+ mock_package_defs ["cf20" ] = mock_package_type
230+
231+ # Setup mocks
232+ mock_exists .return_value = False # No existing pins.lock
233+
234+ # Mock config
235+ mock_config = {
236+ "chipflow" : {
237+ "steps" : {
238+ "silicon" : "chipflow_lib.steps.silicon:SiliconStep"
239+ },
240+ "silicon" : {
241+ "process" : "ihp_sg13g2" ,
242+ "package" : "cf20" ,
243+ "pads" : {},
244+ "power" : {}
245+ }
246+ }
247+ }
248+ mock_parse_config .return_value = mock_config
249+
250+ # Mock top_interfaces with an interface that needs pins
251+ mock_interface = {
252+ "comp1" : {
253+ "interface" : {
254+ "members" : {
255+ "uart" : {
256+ "type" : "interface" ,
257+ "members" : {
258+ "tx" : {"type" : "port" , "width" : 1 , "dir" : "o" }
259+ }
260+ }
261+ }
262+ }
263+ }
264+ }
265+ mock_top_interfaces .return_value = (None , mock_interface )
266+
267+ # Import and run lock_pins
268+ from chipflow_lib .pin_lock import lock_pins
269+
270+ # Mock the Package.__init__ to avoid validation errors
271+ with mock .patch ("chipflow_lib.pin_lock.Package" ) as mock_package_class :
272+ mock_package_instance = mock .MagicMock ()
273+ mock_package_class .return_value = mock_package_instance
274+
275+ # Test for the expected error when no pins are allocated
276+ with self .assertRaises (ChipFlowError ) as cm :
277+ lock_pins ()
278+
279+ self .assertIn ("No pins were allocated" , str (cm .exception ))
280+
281+ @mock .patch ("builtins.open" , new_callable = mock .mock_open )
282+ @mock .patch ("chipflow_lib.pin_lock._parse_config" )
283+ @mock .patch ("chipflow_lib.pin_lock.top_interfaces" )
284+ @mock .patch ("pathlib.Path.exists" )
285+ @mock .patch ("pathlib.Path.read_text" )
286+ @mock .patch ("chipflow_lib.pin_lock.LockFile.model_validate_json" )
287+ @mock .patch ("chipflow_lib.pin_lock.PACKAGE_DEFINITIONS" , new_callable = dict )
288+ @mock .patch ("chipflow_lib.pin_lock.LockFile" )
289+ def test_lock_pins_interface_size_change (self , mock_lock_file , mock_package_defs ,
290+ mock_validate_json , mock_read_text ,
291+ mock_exists , mock_top_interfaces ,
292+ mock_parse_config , mock_open ):
293+ """Test that lock_pins raises appropriate error when interface size changes"""
294+ # Setup mock package definitions
295+ mock_package_type = MockPackageType (name = "cf20" )
296+ mock_package_defs ["cf20" ] = mock_package_type
297+
298+ # Setup mocks
299+ mock_exists .return_value = True # Existing pins.lock
300+ mock_read_text .return_value = '{"mock": "json"}'
301+
302+ # Mock config
303+ mock_config = {
304+ "chipflow" : {
305+ "steps" : {
306+ "silicon" : "chipflow_lib.steps.silicon:SiliconStep"
307+ },
308+ "silicon" : {
309+ "process" : "ihp_sg13g2" ,
310+ "package" : "cf20" ,
311+ "pads" : {},
312+ "power" : {}
313+ }
314+ }
315+ }
316+ mock_parse_config .return_value = mock_config
317+
318+ # Create a mock for the existing lock file
319+ mock_old_lock = mock .MagicMock ()
320+ mock_old_lock .package = mock .MagicMock ()
321+ mock_old_lock .package .check_pad .return_value = None # No conflicts
322+
323+ # Create a port map that will have a different size than the new interface
324+ existing_ports = {
325+ "tx" : mock .MagicMock (pins = ["10" ]), # Only 1 pin
326+ }
327+
328+ # Setup the port_map to return these ports
329+ mock_port_map = mock .MagicMock ()
330+ mock_port_map .get_ports .return_value = existing_ports
331+ mock_old_lock .configure_mock (port_map = mock_port_map )
332+ mock_validate_json .return_value = mock_old_lock
333+
334+ # Mock top_interfaces with an interface that has DIFFERENT size (2 pins instead of 1)
335+ mock_interface = {
336+ "comp1" : {
337+ "interface" : {
338+ "members" : {
339+ "uart" : {
340+ "type" : "interface" ,
341+ "members" : {
342+ "tx" : {"type" : "port" , "width" : 2 , "dir" : "o" } # Width 2 instead of 1
343+ }
344+ }
345+ }
346+ }
347+ }
348+ }
349+ mock_top_interfaces .return_value = (None , mock_interface )
350+
351+ # Import and run lock_pins
352+ from chipflow_lib .pin_lock import lock_pins
353+
354+ # Mock the Package.__init__ to avoid validation errors
355+ with mock .patch ("chipflow_lib.pin_lock.Package" ) as mock_package_class :
356+ mock_package_instance = mock .MagicMock ()
357+ mock_package_class .return_value = mock_package_instance
358+
359+ # Test for the expected error when interface size changes
360+ with self .assertRaises (ChipFlowError ) as cm :
361+ lock_pins ()
362+
363+ # Check that the error message includes the size change information
364+ error_msg = str (cm .exception )
365+ self .assertIn ("top level interface has changed size" , error_msg )
366+ self .assertIn ("Old size = 1" , error_msg )
367+ self .assertIn ("new size = 2" , error_msg )
368+
202369 @mock .patch ("builtins.open" , new_callable = mock .mock_open )
203370 @mock .patch ("chipflow_lib.pin_lock._parse_config" )
204371 @mock .patch ("chipflow_lib.pin_lock.top_interfaces" )
@@ -447,7 +614,12 @@ class MockConflictPort:
447614 def __init__ (self ):
448615 self .pins = ["5" ] # Different from config
449616
450- mock_old_lock .package .check_pad .return_value = MockConflictPort ()
617+ # Setup package
618+ mock_package = mock .MagicMock ()
619+ mock_package .check_pad .return_value = MockConflictPort ()
620+
621+ # Configure mock for both dict and Pydantic model compatibility
622+ mock_old_lock .configure_mock (package = mock_package )
451623 mock_validate_json .return_value = mock_old_lock
452624
453625 # Set up new LockFile mock for constructor (will not be reached in this test)
@@ -513,14 +685,23 @@ def test_lock_pins_reuse_existing_ports(self, mock_lock_file, mock_package_defs,
513685
514686 # Mock LockFile instance for existing lock
515687 mock_old_lock = mock .MagicMock ()
516- mock_old_lock .package .check_pad .return_value = None # No conflicting pads
688+
689+ # Setup package
690+ mock_package = mock .MagicMock ()
691+ mock_package .check_pad .return_value = None # No conflicting pads
692+
693+ # Configure mock for both dict and Pydantic model compatibility
694+ mock_old_lock .configure_mock (package = mock_package )
517695
518696 # Create existing ports to be reused
519697 existing_ports = {
520698 "tx" : mock .MagicMock (pins = ["10" ]),
521699 "rx" : mock .MagicMock (pins = ["11" ])
522700 }
523- mock_old_lock .port_map .get_ports .return_value = existing_ports
701+ # Configure port_map in a way that's compatible with both dict and Pydantic models
702+ mock_port_map = mock .MagicMock ()
703+ mock_port_map .get_ports .return_value = existing_ports
704+ mock_old_lock .configure_mock (port_map = mock_port_map )
524705 mock_validate_json .return_value = mock_old_lock
525706
526707 # Set up new LockFile mock for constructor
0 commit comments