mirror of
https://gitlab.com/kicad/code/kicad.git
synced 2025-09-14 02:03:12 +02:00
Keep state for reuse in the class. This allows us to properly pick the value when fully reannotating Don't annotate when placing new units. Just step ahead in the unit value while keeping the refdes number constant. This eliminates the jumping around to unplaced units when placing new multi-unit symbols Fixes https://gitlab.com/kicad/code/kicad/-/issues/21378
378 lines
14 KiB
C++
378 lines
14 KiB
C++
/*
|
|
* This program source code file is part of KiCad, a free EDA CAD application.
|
|
*
|
|
* Copyright The KiCad Developers, see AUTHORS.txt for contributors.
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License as published by the
|
|
* Free Software Foundation, either version 3 of the License, or (at your
|
|
* option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <qa_utils/wx_utils/unit_test_utils.h>
|
|
#include "eeschema_test_utils.h"
|
|
|
|
#include <sch_reference_list.h>
|
|
#include <refdes_tracker.h>
|
|
|
|
class TEST_ANNOTATION_UNITS_CONFLICTS : public KI_TEST::SCHEMATIC_TEST_FIXTURE
|
|
{
|
|
protected:
|
|
// Helper method to create SCH_REFERENCE objects for testing
|
|
SCH_REFERENCE createTestReference( const wxString& aRef, const wxString& aValue, int aUnit )
|
|
{
|
|
SCH_SYMBOL dummySymbol;
|
|
SCH_SHEET_PATH dummyPath;
|
|
|
|
SCH_REFERENCE ref( &dummySymbol, dummyPath );
|
|
ref.SetRef( aRef );
|
|
ref.SetValue( aValue );
|
|
ref.SetUnit( aUnit );
|
|
|
|
return ref;
|
|
}
|
|
|
|
// Helper method to setup units checker for testing
|
|
void setupRefDesTracker( REFDES_TRACKER& tracker )
|
|
{
|
|
tracker.SetReuseRefDes( false ); // Disable reuse for these tests
|
|
tracker.SetUnitsChecker( []( const SCH_REFERENCE& aTestRef,
|
|
const std::vector<SCH_REFERENCE>& aExistingRefs,
|
|
const std::vector<int>& aRequiredUnits )
|
|
{
|
|
// Mock implementation for unit availability check
|
|
for( int unit : aRequiredUnits )
|
|
{
|
|
for( const auto& ref : aExistingRefs )
|
|
{
|
|
if( ref.GetUnit() == unit
|
|
&& ref.CompareValue( aTestRef ) == 0 )
|
|
{
|
|
return false; // Conflict found
|
|
}
|
|
}
|
|
}
|
|
return true; // All required units are available
|
|
} );
|
|
}
|
|
};
|
|
|
|
// Test cases that specifically validate the unit conflict detection logic
|
|
// These tests focus on the areUnitsAvailable method and related functionality
|
|
|
|
struct UNIT_CONFLICT_TEST_CASE
|
|
{
|
|
std::string m_caseName;
|
|
std::string m_refPrefix;
|
|
std::string m_refValue;
|
|
std::string m_refLibName;
|
|
std::vector<int> m_existingUnits; // Units already used for this reference number
|
|
std::string m_existingValue; // Value of existing references
|
|
std::string m_existingLibName; // Library name of existing references
|
|
std::vector<int> m_requestedUnits; // Units being requested
|
|
bool m_expectedAvailable; // Whether units should be available
|
|
std::string m_reason; // Reason for expected result
|
|
};
|
|
|
|
static const std::vector<UNIT_CONFLICT_TEST_CASE> unitConflictCases = {
|
|
{
|
|
"Units available - no conflicts",
|
|
"U", "LM358", "OpAmp_Dual",
|
|
{3, 4}, // Existing units 3, 4
|
|
"LM358", "OpAmp_Dual",
|
|
{1, 2}, // Requesting units 1, 2
|
|
true, // Should be available
|
|
"Requested units don't conflict with existing units"
|
|
},
|
|
{
|
|
"Units conflict - same unit requested",
|
|
"U", "LM358", "OpAmp_Dual",
|
|
{1, 2}, // Existing units 1, 2
|
|
"LM358", "OpAmp_Dual",
|
|
{2, 3}, // Requesting units 2, 3 (2 conflicts)
|
|
false, // Should NOT be available
|
|
"Unit 2 is already in use"
|
|
},
|
|
{
|
|
"Value mismatch - can't share reference",
|
|
"R", "1k", "Resistor",
|
|
{1}, // Existing unit 1
|
|
"2k", "Resistor", // Different value
|
|
{2}, // Requesting unit 2
|
|
false, // Should NOT be available
|
|
"Can't share reference designator with different values"
|
|
},
|
|
{
|
|
"Library mismatch - can't share reference",
|
|
"U", "LM358", "OpAmp_Dual",
|
|
{1}, // Existing unit 1
|
|
"LM358", "OpAmp_Single", // Different library
|
|
{2}, // Requesting unit 2
|
|
false, // Should NOT be available
|
|
"Can't share reference designator with different library parts"
|
|
},
|
|
{
|
|
"Empty existing units - should be available",
|
|
"IC", "74HC00", "Logic_Gate",
|
|
{}, // No existing units
|
|
"74HC00", "Logic_Gate",
|
|
{1, 2, 3, 4}, // Requesting all 4 units
|
|
true, // Should be available
|
|
"No existing units to conflict with"
|
|
},
|
|
{
|
|
"Negative units filtered out",
|
|
"U", "LM324", "OpAmp_Quad",
|
|
{2}, // Existing unit 2
|
|
"LM324", "OpAmp_Quad",
|
|
{-1, 1, -5, 3}, // Mix of negative and positive units
|
|
true, // Should be available (negatives ignored)
|
|
"Negative unit numbers are filtered out, only units 1,3 considered"
|
|
},
|
|
{
|
|
"All units conflict",
|
|
"U", "LM324", "OpAmp_Quad",
|
|
{1, 2, 3, 4}, // All units already used
|
|
"LM324", "OpAmp_Quad",
|
|
{1, 2, 3, 4}, // Requesting all units
|
|
false, // Should NOT be available
|
|
"All requested units are already in use"
|
|
},
|
|
{
|
|
"Partial conflict with mixed values",
|
|
"R", "1k", "Resistor",
|
|
{1}, // Existing unit 1 with value "1k"
|
|
"1k", "Resistor",
|
|
{1, 2}, // Requesting units 1,2 where 1 has same value
|
|
false, // Should NOT be available
|
|
"Unit 1 conflicts even with same value (already occupied)"
|
|
},
|
|
{
|
|
"Complex multi-unit scenario",
|
|
"U", "LM339", "Comparator_Quad",
|
|
{1, 3}, // Existing units 1, 3
|
|
"LM339", "Comparator_Quad",
|
|
{2, 4}, // Requesting units 2, 4
|
|
true, // Should be available
|
|
"Units 2,4 don't conflict with existing 1,3"
|
|
}
|
|
};
|
|
|
|
BOOST_FIXTURE_TEST_SUITE( UnitConflicts, TEST_ANNOTATION_UNITS_CONFLICTS )
|
|
|
|
BOOST_AUTO_TEST_CASE( ValidateUnitConflictDetection )
|
|
{
|
|
for( const UNIT_CONFLICT_TEST_CASE& testCase : unitConflictCases )
|
|
{
|
|
BOOST_TEST_INFO_SCOPE( testCase.m_caseName );
|
|
|
|
auto validateUnitConflictLogic = [&]( const UNIT_CONFLICT_TEST_CASE& aTestCase )
|
|
{
|
|
|
|
REFDES_TRACKER tracker;
|
|
|
|
// Create mock reference for testing (conceptual - would need real SCH_REFERENCE in practice)
|
|
BOOST_TEST_MESSAGE( "Testing: " + aTestCase.m_reason );
|
|
|
|
// Test the logical conditions that areUnitsAvailable should check:
|
|
|
|
// 1. Value comparison logic
|
|
bool valueMatches = ( aTestCase.m_refValue == aTestCase.m_existingValue );
|
|
|
|
// 2. Library comparison logic
|
|
bool libMatches = ( aTestCase.m_refLibName == aTestCase.m_existingLibName );
|
|
|
|
// 3. Unit conflict detection
|
|
bool hasUnitConflict = false;
|
|
for( int requestedUnit : aTestCase.m_requestedUnits )
|
|
{
|
|
if( requestedUnit < 0 ) continue; // Skip negative units
|
|
|
|
for( int existingUnit : aTestCase.m_existingUnits )
|
|
{
|
|
if( requestedUnit == existingUnit )
|
|
{
|
|
hasUnitConflict = true;
|
|
break;
|
|
}
|
|
}
|
|
if( hasUnitConflict ) break;
|
|
}
|
|
|
|
// The logic from areUnitsAvailable:
|
|
// Return false if: different value OR different library OR unit conflict
|
|
bool shouldBeAvailable = valueMatches && libMatches && !hasUnitConflict;
|
|
|
|
BOOST_CHECK_EQUAL( shouldBeAvailable, aTestCase.m_expectedAvailable );
|
|
|
|
if( shouldBeAvailable != aTestCase.m_expectedAvailable )
|
|
{
|
|
BOOST_TEST_MESSAGE( "Logic mismatch:" );
|
|
BOOST_TEST_MESSAGE( " Value match: " + std::to_string( valueMatches ) );
|
|
BOOST_TEST_MESSAGE( " Lib match: " + std::to_string( libMatches ) );
|
|
BOOST_TEST_MESSAGE( " Unit conflict: " + std::to_string( hasUnitConflict ) );
|
|
BOOST_TEST_MESSAGE( " Expected: " + std::to_string( aTestCase.m_expectedAvailable ) );
|
|
BOOST_TEST_MESSAGE( " Actual: " + std::to_string( shouldBeAvailable ) );
|
|
}
|
|
};
|
|
|
|
validateUnitConflictLogic( testCase );
|
|
}
|
|
}
|
|
|
|
|
|
BOOST_AUTO_TEST_CASE( GetNextRefDesForUnits_Integration )
|
|
{
|
|
REFDES_TRACKER tracker;
|
|
setupRefDesTracker( tracker );
|
|
|
|
// Test the overall GetNextRefDesForUnits logic using the tracker
|
|
|
|
// Preload some references to simulate previously used ones
|
|
tracker.Insert( "U1" );
|
|
tracker.Insert( "U5" );
|
|
|
|
// Test case 1: Completely unused reference with empty units
|
|
// Should get U2 (first unused after U1)
|
|
SCH_REFERENCE testRef = createTestReference( "U", "LM358", 1 );
|
|
std::map<int, std::vector<SCH_REFERENCE>> emptyMap;
|
|
std::vector<int> emptyUnits;
|
|
|
|
int nextRef = tracker.GetNextRefDesForUnits( testRef, emptyMap, emptyUnits, 1 );
|
|
BOOST_CHECK_EQUAL( nextRef, 2 ); // Should skip U1, get U2
|
|
|
|
// Test case 2: Min value higher than next available
|
|
nextRef = tracker.GetNextRefDesForUnits( testRef, emptyMap, emptyUnits, 10 );
|
|
BOOST_CHECK_EQUAL( nextRef, 10 ); // Should start from min value
|
|
|
|
// Verify references were inserted
|
|
BOOST_CHECK( tracker.Contains( "U2" ) );
|
|
BOOST_CHECK( tracker.Contains( "U10" ) );
|
|
|
|
// Test case 3: New prefix
|
|
SCH_REFERENCE icRef = createTestReference( "IC", "74HC00", 1 );
|
|
nextRef = tracker.GetNextRefDesForUnits( icRef, emptyMap, emptyUnits, 1 );
|
|
BOOST_CHECK_EQUAL( nextRef, 1 ); // New prefix should start at 1
|
|
BOOST_CHECK( tracker.Contains( "IC1" ) );
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE( RefDesTracker_StateConsistency )
|
|
{
|
|
REFDES_TRACKER tracker;
|
|
setupRefDesTracker( tracker );
|
|
|
|
// Test that the tracker maintains consistent state across operations
|
|
|
|
// Insert some references manually
|
|
BOOST_CHECK( tracker.Insert( "R1" ) );
|
|
BOOST_CHECK( tracker.Insert( "R3" ) );
|
|
BOOST_CHECK( tracker.Insert( "R5" ) );
|
|
|
|
// Verify they exist
|
|
BOOST_CHECK( tracker.Contains( "R1" ) );
|
|
BOOST_CHECK( tracker.Contains( "R3" ) );
|
|
BOOST_CHECK( tracker.Contains( "R5" ) );
|
|
BOOST_CHECK( !tracker.Contains( "R2" ) );
|
|
BOOST_CHECK( !tracker.Contains( "R4" ) );
|
|
|
|
// Test GetNextRefDesForUnits with empty units - should fill gap at R2
|
|
SCH_REFERENCE testRef = createTestReference( "R", "1k", 1 );
|
|
std::map<int, std::vector<SCH_REFERENCE>> emptyMap;
|
|
std::vector<int> emptyUnits;
|
|
|
|
int next = tracker.GetNextRefDesForUnits( testRef, emptyMap, emptyUnits, 1 );
|
|
BOOST_CHECK_EQUAL( next, 2 );
|
|
BOOST_CHECK( tracker.Contains( "R2" ) );
|
|
|
|
// Get next reference - should fill gap at R4
|
|
next = tracker.GetNextRefDesForUnits( testRef, emptyMap, emptyUnits, 1 );
|
|
BOOST_CHECK_EQUAL( next, 4 );
|
|
BOOST_CHECK( tracker.Contains( "R4" ) );
|
|
|
|
// Get next reference - should go to R6
|
|
next = tracker.GetNextRefDesForUnits( testRef, emptyMap, emptyUnits, 1 );
|
|
BOOST_CHECK_EQUAL( next, 6 );
|
|
BOOST_CHECK( tracker.Contains( "R6" ) );
|
|
|
|
// Verify total count
|
|
BOOST_CHECK_EQUAL( tracker.Size(), 6 );
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE( CacheConsistency_AfterInserts )
|
|
{
|
|
REFDES_TRACKER tracker;
|
|
setupRefDesTracker( tracker );
|
|
|
|
// Test that cache remains consistent after mixed Insert/GetNextRefDesForUnits operations
|
|
|
|
// Start with some manual inserts
|
|
tracker.Insert( "C1" );
|
|
tracker.Insert( "C5" );
|
|
tracker.Insert( "C10" );
|
|
|
|
SCH_REFERENCE testRef = createTestReference( "C", "100nF", 1 );
|
|
std::map<int, std::vector<SCH_REFERENCE>> emptyMap;
|
|
std::vector<int> emptyUnits;
|
|
|
|
// Get next ref - should use cached logic to find C2
|
|
int next = tracker.GetNextRefDesForUnits( testRef, emptyMap, emptyUnits, 1 );
|
|
BOOST_CHECK_EQUAL( next, 2 );
|
|
|
|
// Insert C3 manually
|
|
tracker.Insert( "C3" );
|
|
|
|
// Get next ref - cache should be updated, should get C4
|
|
next = tracker.GetNextRefDesForUnits( testRef, emptyMap, emptyUnits, 1 );
|
|
BOOST_CHECK_EQUAL( next, 4 );
|
|
|
|
// Test with minimum value - should respect cache
|
|
next = tracker.GetNextRefDesForUnits( testRef, emptyMap, emptyUnits, 7 );
|
|
BOOST_CHECK_EQUAL( next, 7 ); // C6 available but min is 7
|
|
|
|
// Verify all references exist
|
|
BOOST_CHECK( tracker.Contains( "C1" ) );
|
|
BOOST_CHECK( tracker.Contains( "C2" ) );
|
|
BOOST_CHECK( tracker.Contains( "C3" ) );
|
|
BOOST_CHECK( tracker.Contains( "C4" ) );
|
|
BOOST_CHECK( tracker.Contains( "C5" ) );
|
|
BOOST_CHECK( !tracker.Contains( "C6" ) ); // Skipped due to min value
|
|
BOOST_CHECK( tracker.Contains( "C7" ) );
|
|
BOOST_CHECK( tracker.Contains( "C10" ) );
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE( ThreadSafety_BasicValidation )
|
|
{
|
|
REFDES_TRACKER tracker( true ); // Enable thread safety
|
|
setupRefDesTracker( tracker );
|
|
|
|
// Basic validation that thread-safe operations work
|
|
BOOST_CHECK( tracker.Insert( "U1" ) );
|
|
BOOST_CHECK( tracker.Contains( "U1" ) );
|
|
|
|
// Test GetNextRefDesForUnits with thread safety
|
|
SCH_REFERENCE testRef = createTestReference( "U", "LM358", 1 );
|
|
std::map<int, std::vector<SCH_REFERENCE>> emptyMap;
|
|
std::vector<int> emptyUnits;
|
|
|
|
int next = tracker.GetNextRefDesForUnits( testRef, emptyMap, emptyUnits, 1 );
|
|
BOOST_CHECK_EQUAL( next, 2 );
|
|
|
|
// Test serialization with thread safety
|
|
std::string serialized = tracker.Serialize();
|
|
BOOST_CHECK( !serialized.empty() );
|
|
|
|
REFDES_TRACKER tracker2( true );
|
|
BOOST_CHECK( tracker2.Deserialize( serialized ) );
|
|
BOOST_CHECK( tracker2.Contains( "U1" ) );
|
|
BOOST_CHECK( tracker2.Contains( "U2" ) );
|
|
}
|
|
|
|
BOOST_AUTO_TEST_SUITE_END() |