Refactor REFDES_TRACKER

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
This commit is contained in:
Seth Hillbrand 2025-07-25 16:13:35 -07:00
parent 9fff1d955e
commit 15166a9f14
13 changed files with 558 additions and 243 deletions

View File

@ -360,11 +360,7 @@ void SCH_EDIT_FRAME::AnnotateSymbols( SCH_COMMIT* aCommit, ANNOTATE_SCOPE_T aAn
}
}
std::shared_ptr<REFDES_TRACKER> refdes = Schematic().Settings().m_refDesTracker;
bool reuseRefDes = Schematic().Settings().m_reuseRefDes;
references.SetReuseRefDes( reuseRefDes );
references.SetRefDesTracker( refdes );
references.SetRefDesTracker( Schematic().Settings().m_refDesTracker );
// Break full symbol reference into name (prefix) and number:
// example: IC1 become IC, and 1

View File

@ -17,6 +17,7 @@
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <refdes_tracker.h>
#include <sch_edit_frame.h>
#include <schematic.h>
#include <schematic_settings.h>
@ -52,7 +53,7 @@ bool PANEL_SETUP_ANNOTATION::TransferDataToWindow()
m_choiceSeparatorRefId->SetSelection( getRefStyleMenuIndex( settings.m_SubpartIdSeparator,
settings.m_SubpartFirstId ) );
m_checkReuseRefdes->SetValue( settings.m_reuseRefDes );
m_checkReuseRefdes->SetValue( settings.m_refDesTracker->GetReuseRefDes() );
return true;
}
@ -75,7 +76,7 @@ bool PANEL_SETUP_ANNOTATION::TransferDataFromWindow()
case 6: settings.m_SubpartFirstId = '1'; settings.m_SubpartIdSeparator = '_'; break;
}
settings.m_reuseRefDes = m_checkReuseRefdes->GetValue();
settings.m_refDesTracker->SetReuseRefDes( m_checkReuseRefdes->GetValue() );
return true;
}
@ -85,7 +86,7 @@ void PANEL_SETUP_ANNOTATION::ImportSettingsFrom( SCHEMATIC_SETTINGS& aSettings )
m_choiceSeparatorRefId->SetSelection( getRefStyleMenuIndex( aSettings.m_SubpartIdSeparator,
aSettings.m_SubpartFirstId ) );
m_checkReuseRefdes->SetValue( aSettings.m_reuseRefDes );
m_checkReuseRefdes->SetValue( aSettings.m_refDesTracker->GetReuseRefDes() );
}

View File

@ -26,7 +26,7 @@
#include "refdes_tracker.h"
REFDES_TRACKER::REFDES_TRACKER( bool aThreadSafe ) :
m_threadSafe( aThreadSafe )
m_threadSafe( aThreadSafe ), m_reuseRefDes( true )
{
}
@ -68,6 +68,12 @@ bool REFDES_TRACKER::insertNumber( const std::string& aPrefix, int aNumber )
return true;
}
bool REFDES_TRACKER::containsImpl( const std::string& aRefDes ) const
{
return m_allRefDes.contains( aRefDes );
}
bool REFDES_TRACKER::Contains( const std::string& aRefDes ) const
{
std::unique_lock<std::mutex> lock;
@ -75,41 +81,7 @@ bool REFDES_TRACKER::Contains( const std::string& aRefDes ) const
if( m_threadSafe )
lock = std::unique_lock<std::mutex>( m_mutex );
return m_allRefDes.find( aRefDes ) != m_allRefDes.end();
}
int REFDES_TRACKER::GetNextRefDes( const std::string& aPrefix, int aMinValue )
{
std::unique_lock<std::mutex> lock;
if( m_threadSafe )
lock = std::unique_lock<std::mutex>( m_mutex );
auto it = m_prefixData.find( aPrefix );
if( it == m_prefixData.end() )
{
// No data for this prefix yet
int nextNum = std::max( 1, aMinValue );
std::string result = aPrefix + std::to_string( nextNum );
// Insert this designator since we're reserving it
insertNumber( aPrefix, nextNum );
m_allRefDes.insert( result );
return aMinValue;
}
PREFIX_DATA& data = it->second;
int nextAvailable = findNextAvailable( data, aMinValue );
std::string result = aPrefix + std::to_string( nextAvailable );
// Insert this designator since we're reserving it
insertNumber( aPrefix, nextAvailable );
m_allRefDes.insert( result );
return nextAvailable;
return containsImpl( aRefDes );
}
@ -141,7 +113,7 @@ int REFDES_TRACKER::GetNextRefDesForUnits( const SCH_REFERENCE& aRef,
// Not currently in use - check if it was previously used
std::string candidateRefDes = aRef.GetRef().ToStdString() + std::to_string( candidate );
if( !Contains( candidateRefDes ) )
if( m_reuseRefDes || !containsImpl( candidateRefDes ) )
{
// Completely unused - this is our answer
insertNumber( aRef.GetRefStr(), candidate );
@ -165,7 +137,20 @@ int REFDES_TRACKER::GetNextRefDesForUnits( const SCH_REFERENCE& aRef,
continue;
}
if( areUnitsAvailable( aRef, mapIt->second, validUnits ) )
// Check if required units are available
bool unitsAvailable;
if( m_externalUnitsChecker )
{
// Use external units checker if available
unitsAvailable = m_externalUnitsChecker( aRef, mapIt->second, validUnits );
}
else
{
// Use default implementation
unitsAvailable = areUnitsAvailable( aRef, mapIt->second, validUnits );
}
if( unitsAvailable )
{
// All required units are available - this is our answer
// Note: Don't insert into tracker since reference is already in use
@ -299,7 +284,6 @@ int REFDES_TRACKER::findNextAvailable( const PREFIX_DATA& aData, int aMinValue )
if( auto cacheIt = aData.m_nextCache.find( aMinValue ); cacheIt != aData.m_nextCache.end() )
return cacheIt->second;
updateBaseNext( const_cast<PREFIX_DATA&>( aData ) );
int candidate;
@ -565,4 +549,26 @@ std::vector<std::string> REFDES_TRACKER::splitString( const std::string& aStr, c
result.push_back( current );
return result;
}
void REFDES_TRACKER::SetUnitsChecker( const UNITS_CHECKER_FUNC<SCH_REFERENCE>& aChecker )
{
std::unique_lock<std::mutex> lock;
if( m_threadSafe )
lock = std::unique_lock<std::mutex>( m_mutex );
m_externalUnitsChecker = aChecker;
}
void REFDES_TRACKER::ClearUnitsChecker()
{
std::unique_lock<std::mutex> lock;
if( m_threadSafe )
lock = std::unique_lock<std::mutex>( m_mutex );
m_externalUnitsChecker = nullptr;
}

View File

@ -27,10 +27,28 @@
#include <sstream>
#include <map>
#include <vector>
#include <functional>
#include <algorithm>
// Forward declaration
class SCH_REFERENCE;
/**
* Function type for external units availability checking.
*
* This allows the REFDES_TRACKER to work with mock objects or custom logic
* without requiring actual SCH_REFERENCE dependencies.
*
* @param aTestRef Reference object being tested for compatibility
* @param aExistingRefs Vector of existing references for the same reference number
* @param aRequiredUnits Vector of unit numbers needed
* @return true if all required units are available (no conflicts)
*/
template<typename T>
using UNITS_CHECKER_FUNC = std::function<bool(const T& aTestRef,
const std::vector<T>& aExistingRefs,
const std::vector<int>& aRequiredUnits)>;
/**
* Class to efficiently track reference designators and provide next available designators.
*
@ -92,6 +110,21 @@ public:
const std::vector<int>& aRequiredUnits,
int aMinValue );
/**
* Set an external units checker function for SCH_REFERENCE objects.
*
* This allows overriding the default units availability logic without
* requiring LIB_SYMBOL dependencies.
*
* @param aChecker function to use for checking unit availability
*/
void SetUnitsChecker( const UNITS_CHECKER_FUNC<SCH_REFERENCE>& aChecker );
/**
* Clear the external units checker, reverting to default behavior.
*/
void ClearUnitsChecker();
/**
* Serialize the tracker data to a compact string representation.
*
@ -121,16 +154,19 @@ public:
*/
size_t Size() const;
bool GetReuseRefDes() const { return m_reuseRefDes; }
void SetReuseRefDes( bool aReuse ) { m_reuseRefDes = aReuse; }
private:
/**
* Data structure for tracking used numbers and caching next available values.
*/
struct PREFIX_DATA
{
std::set<int> m_usedNumbers; ///< Sorted set of used numbers for this prefix
mutable std::map<int, int> m_nextCache; ///< Cache of next available number for given min values
mutable int m_baseNext; ///< Next available from 1 (cached)
mutable bool m_cacheValid; ///< True if m_baseNext cache is valid
std::set<int> m_usedNumbers; ///< Sorted set of used numbers for this prefix
mutable std::map<int, int> m_nextCache; ///< Cache of next available number for given min values
mutable int m_baseNext; ///< Next available from 1 (cached)
mutable bool m_cacheValid; ///< True if m_baseNext cache is valid
PREFIX_DATA() : m_baseNext( 1 ), m_cacheValid( false ) {}
};
@ -140,10 +176,13 @@ private:
/// Map from prefix to its tracking data
std::unordered_map<std::string, PREFIX_DATA> m_prefixData;
/// Set of all reference designators for O(1) lookup
std::unordered_set<std::string> m_allRefDes;
bool m_reuseRefDes; ///< If true, allows reusing existing reference designators
/// External units checker function (optional)
UNITS_CHECKER_FUNC<SCH_REFERENCE> m_externalUnitsChecker;
/**
* Internal implementation of Insert without locking.
*
@ -159,6 +198,14 @@ private:
*/
void clearImpl();
/**
* Check if a reference designator exists in the tracker without locking.
*
* @param aRefDes reference designator to check
* @return true if the reference designator exists
*/
bool containsImpl( const std::string& aRefDes ) const;
/**
* Parse a reference designator into prefix and numerical suffix.
*

View File

@ -275,9 +275,7 @@ class SCH_REFERENCE_LIST
{
public:
SCH_REFERENCE_LIST()
{
m_reuseRefDes = true; // Default to reusing reference designators after deletion
}
{}
SCH_REFERENCE& operator[]( int aIndex )
{
@ -594,16 +592,6 @@ public:
m_refDesTracker = aTracker;
}
bool ReuseRefDes() const
{
return m_reuseRefDes;
}
void SetReuseRefDes( bool aReuse )
{
m_reuseRefDes = aReuse;
}
friend class BACK_ANNOTATION;
typedef std::vector<SCH_REFERENCE>::iterator iterator;
@ -637,7 +625,6 @@ private:
std::vector<SCH_REFERENCE> m_flatList;
bool m_reuseRefDes; // True if the reference designators should be reused after being deleted
std::shared_ptr<REFDES_TRACKER> m_refDesTracker; ///< A list of previously used reference designators.
};

View File

@ -255,8 +255,18 @@ SCHEMATIC_SETTINGS::SCHEMATIC_SETTINGS( JSON_SETTINGS* aParent, const std::strin
m_NgspiceSettings = std::make_shared<NGSPICE_SETTINGS>( this, "ngspice" );
m_params.emplace_back( new PARAM<bool>( "reuse_designators",
&m_reuseRefDes, true ) );
m_params.emplace_back( new PARAM_LAMBDA<bool>( "reuse_designators",
[&]() -> bool
{
return m_refDesTracker ? m_refDesTracker->GetReuseRefDes() : false;
},
[&]( bool aReuse )
{
if( !m_refDesTracker )
m_refDesTracker = std::make_shared<REFDES_TRACKER>();
m_refDesTracker->SetReuseRefDes( aReuse );
}, true ) );
m_params.emplace_back( new PARAM_LAMBDA<std::string>( "used_designators",
[&]() -> std::string
@ -269,7 +279,7 @@ SCHEMATIC_SETTINGS::SCHEMATIC_SETTINGS( JSON_SETTINGS* aParent, const std::strin
[&]( const std::string& aData )
{
if( !m_refDesTracker )
m_refDesTracker = std::make_unique<REFDES_TRACKER>();
m_refDesTracker = std::make_shared<REFDES_TRACKER>();
m_refDesTracker->Deserialize( aData );
}, {} ) );

View File

@ -126,12 +126,6 @@ public:
*/
std::shared_ptr<NGSPICE_SETTINGS> m_NgspiceSettings;
/**
* True if we want allow deleted schematic reference designators to be reused
* in the same project.
*/
bool m_reuseRefDes;
/**
* A list of previously used schematic reference designators.
* This is used to avoid reusing designators in the same project.

View File

@ -233,7 +233,6 @@ int SCH_DRAWING_TOOLS::PlaceSymbol( const TOOL_EVENT& aEvent )
SCH_REFERENCE_LIST refs;
refs.AddItem( newReference );
refs.SetRefDesTracker( schSettings.m_refDesTracker );
refs.SetReuseRefDes( schSettings.m_reuseRefDes );
if( cfg->m_AnnotatePanel.automatic || newReference.AlwaysAnnotate() )
{
@ -473,32 +472,36 @@ int SCH_DRAWING_TOOLS::PlaceSymbol( const TOOL_EVENT& aEvent )
if( m_frame->eeconfig()->m_SymChooserPanel.place_all_units
|| m_frame->eeconfig()->m_SymChooserPanel.keep_symbol )
{
int new_unit = symbol->GetUnit();
SCH_REFERENCE currentReference( symbol, m_frame->GetCurrentSheet() );
SCHEMATIC& schematic = m_frame->Schematic();
if( m_frame->eeconfig()->m_SymChooserPanel.place_all_units
&& symbol->GetUnit() < symbol->GetUnitCount() )
if( m_frame->eeconfig()->m_SymChooserPanel.place_all_units )
{
new_unit++;
}
else
{
new_unit = 1;
while( currentReference.GetUnit() <= symbol->GetUnitCount()
&& schematic.Contains( currentReference ) )
{
currentReference.SetUnit( currentReference.GetUnit() + 1 );
}
if( currentReference.GetUnit() > symbol->GetUnitCount() )
{
currentReference.SetUnit( 1 );
}
}
// We are either stepping to the next unit or next symbol
if( m_frame->eeconfig()->m_SymChooserPanel.keep_symbol || new_unit > 1 )
if( m_frame->eeconfig()->m_SymChooserPanel.keep_symbol ||
currentReference.GetUnit() > 1 )
{
nextSymbol = static_cast<SCH_SYMBOL*>( symbol->Duplicate( IGNORE_PARENT_GROUP ) );
nextSymbol->SetUnit( new_unit );
nextSymbol->SetUnitSelection( new_unit );
// Start new annotation sequence at first unit
if( new_unit == 1 )
nextSymbol->ClearAnnotation( nullptr, false );
nextSymbol->SetUnit( currentReference.GetUnit() );
nextSymbol->SetUnitSelection( currentReference.GetUnit() );
addSymbol( nextSymbol );
symbol = nextSymbol;
annotate();
if( currentReference.GetUnit() == 1 )
annotate();
// Update the list of references for the next symbol placement.
SCH_REFERENCE placedSymbolReference( symbol, m_frame->GetCurrentSheet() );

View File

@ -2108,7 +2108,6 @@ int SCH_EDITOR_CONTROL::Paste( const TOOL_EVENT& aEvent )
{
annotatedSymbols[path].SortByReferenceOnly();
annotatedSymbols[path].SetRefDesTracker( schematicSettings.m_refDesTracker );
annotatedSymbols[path].SetReuseRefDes( schematicSettings.m_reuseRefDes );
if( pasteMode == PASTE_MODE::UNIQUE_ANNOTATIONS )
{
@ -2132,7 +2131,6 @@ int SCH_EDITOR_CONTROL::Paste( const TOOL_EVENT& aEvent )
{
annotatedSymbols[pastedSheetPath].SortByReferenceOnly();
annotatedSymbols[pastedSheetPath].SetRefDesTracker( schematicSettings.m_refDesTracker );
annotatedSymbols[pastedSheetPath].SetReuseRefDes( schematicSettings.m_reuseRefDes );
if( pasteMode == PASTE_MODE::UNIQUE_ANNOTATIONS )
{

View File

@ -23,47 +23,12 @@
#include <refdes_tracker.h>
#include <sch_reference_list.h>
// Mock SCH_REFERENCE class for testing GetNextRefDesForUnits
class MOCK_SCH_REFERENCE
{
public:
MOCK_SCH_REFERENCE( const wxString& aRef, const wxString& aValue,
const wxString& aLibName, int aUnit ) :
m_ref( aRef ), m_value( aValue ), m_libName( aLibName ), m_unit( aUnit ),
m_refStr( aRef.ToStdString() )
{
}
wxString GetRef() const { return m_ref; }
const char* GetRefStr() const { return m_refStr.c_str(); }
wxString GetValue() const { return m_value; }
int GetUnit() const { return m_unit; }
int CompareValue( const MOCK_SCH_REFERENCE& other ) const
{
return m_value.Cmp( other.m_value );
}
int CompareLibName( const MOCK_SCH_REFERENCE& other ) const
{
return m_libName.Cmp( other.m_libName );
}
private:
wxString m_ref;
wxString m_value;
wxString m_libName;
int m_unit;
std::string m_refStr; // Store as std::string to avoid temp object issues
};
struct REFDES_UNITS_TEST_CASE
{
std::string m_caseName;
std::string m_testRefPrefix;
std::string m_testRefValue;
std::string m_testRefLibName;
std::map<int, std::vector<MOCK_SCH_REFERENCE>> m_refNumberMap;
std::map<int, std::vector<std::tuple<std::string, int>>> m_refNumberMap; // Map of ref number to vector of (value, unit) tuples
std::vector<int> m_requiredUnits;
int m_minValue;
int m_expectedResult;
@ -74,6 +39,44 @@ class TEST_REFDES_TRACKER_UNITS : public KI_TEST::SCHEMATIC_TEST_FIXTURE
{
protected:
void runTestCase( const REFDES_UNITS_TEST_CASE& testCase );
// Helper method to create test references
SCH_REFERENCE createTestReference( const std::string& aRefPrefix, const std::string& aValue, int aUnit )
{
SCH_SYMBOL dummySymbol;
SCH_SHEET_PATH dummyPath;
SCH_REFERENCE ref( &dummySymbol, dummyPath );
ref.SetRef( aRefPrefix );
ref.SetValue( aValue );
ref.SetUnit( aUnit );
return ref;
}
// Helper method to setup units checker
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 )
{
// Check if all required units are available
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
} );
}
};
void TEST_REFDES_TRACKER_UNITS::runTestCase( const REFDES_UNITS_TEST_CASE& testCase )
@ -87,26 +90,50 @@ void TEST_REFDES_TRACKER_UNITS::runTestCase( const REFDES_UNITS_TEST_CASE& testC
}
// Create test reference
MOCK_SCH_REFERENCE testRef( testCase.m_testRefPrefix, testCase.m_testRefValue,
testCase.m_testRefLibName, 1 );
SCH_REFERENCE testRef = createTestReference( testCase.m_testRefPrefix, testCase.m_testRefValue, 1 );
// Convert mock references to SCH_REFERENCE map (this would need adaptation for real SCH_REFERENCE)
// For now, we'll test the core logic conceptually
// Convert test case data to actual SCH_REFERENCE map
std::map<int, std::vector<SCH_REFERENCE>> refNumberMap;
for( const auto& [refNum, tupleVec] : testCase.m_refNumberMap )
{
std::vector<SCH_REFERENCE> refs;
for( const auto& [value, unit] : tupleVec )
{
refs.push_back( createTestReference( testCase.m_testRefPrefix, value, unit ) );
}
refNumberMap[refNum] = refs;
}
BOOST_TEST_INFO( "Testing case: " + testCase.m_caseName );
// Test the basic tracker functionality first
BOOST_CHECK_EQUAL( tracker.Contains( testCase.m_testRefPrefix + "1" ),
testCase.m_trackerPreloads.end() !=
std::find( testCase.m_trackerPreloads.begin(),
testCase.m_trackerPreloads.end(),
testCase.m_testRefPrefix + "1" ) );
setupRefDesTracker( tracker );
// Test GetNextRefDesForUnits logic using the 4-parameter method
int result = tracker.GetNextRefDesForUnits( testRef,
refNumberMap,
testCase.m_requiredUnits,
testCase.m_minValue );
BOOST_CHECK_EQUAL( result, testCase.m_expectedResult );
// Additional verification: check that the result reference is in the tracker
// (unless it was a case where units were available in existing reference)
std::string resultRefDes = testCase.m_testRefPrefix + std::to_string( result );
// Check if this reference number was already in use
bool wasInUse = testCase.m_refNumberMap.find( result ) != testCase.m_refNumberMap.end();
if( !wasInUse && !testCase.m_requiredUnits.empty() )
{
// For completely new references, it should be added to tracker
BOOST_CHECK( tracker.Contains( resultRefDes ) );
}
}
static const std::vector<REFDES_UNITS_TEST_CASE> refdesUnitsTestCases = {
{
"Case 1: Completely unused reference - empty units",
"U", "LM358", "OpAmp",
"U", "LM358",
{}, // No currently used references
{}, // Empty required units (need completely unused)
1, // Min value
@ -115,7 +142,7 @@ static const std::vector<REFDES_UNITS_TEST_CASE> refdesUnitsTestCases = {
},
{
"Case 2: Completely unused reference - with units",
"U", "LM358", "OpAmp",
"U", "LM358",
{}, // No currently used references
{1, 2}, // Need units 1 and 2
1, // Min value
@ -124,9 +151,9 @@ static const std::vector<REFDES_UNITS_TEST_CASE> refdesUnitsTestCases = {
},
{
"Case 3: Skip currently in use reference",
"U", "LM358", "OpAmp",
"U", "LM358",
{
{ 1, { MOCK_SCH_REFERENCE("U", "LM358", "OpAmp", 1) } } // U1 unit 1 in use
{ 1, { std::make_tuple("LM358", 1) } } // U1 unit 1 in use
},
{1, 2}, // Need units 1 and 2
1, // Min value
@ -135,10 +162,10 @@ static const std::vector<REFDES_UNITS_TEST_CASE> refdesUnitsTestCases = {
},
{
"Case 4: Units available in currently used reference",
"U", "LM358", "OpAmp",
"U", "LM358",
{
{ 1, { MOCK_SCH_REFERENCE("U", "LM358", "OpAmp", 3),
MOCK_SCH_REFERENCE("U", "LM358", "OpAmp", 4) } } // U1 units 3,4 in use
{ 1, { std::make_tuple("LM358", 3),
std::make_tuple("LM358", 4) } } // U1 units 3,4 in use
},
{1, 2}, // Need units 1 and 2 (available)
1, // Min value
@ -147,9 +174,9 @@ static const std::vector<REFDES_UNITS_TEST_CASE> refdesUnitsTestCases = {
},
{
"Case 5: Different value conflict",
"U", "LM358", "OpAmp",
"U", "LM358",
{
{ 1, { MOCK_SCH_REFERENCE("U", "LM741", "OpAmp", 1) } } // U1 different value
{ 1, { std::make_tuple("LM741", 1) } } // U1 different value
},
{1}, // Need unit 1
1, // Min value
@ -157,19 +184,8 @@ static const std::vector<REFDES_UNITS_TEST_CASE> refdesUnitsTestCases = {
{}
},
{
"Case 6: Different library conflict",
"U", "LM358", "OpAmp",
{
{ 1, { MOCK_SCH_REFERENCE("U", "LM358", "DifferentLib", 2) } } // U1 different lib
},
{1}, // Need unit 1
1, // Min value
2, // Expected: U2 (can't share with different library)
{}
},
{
"Case 7: Previously used reference in tracker",
"U", "LM358", "OpAmp",
"Case 6: Previously used reference in tracker",
"U", "LM358",
{}, // No currently used references
{1}, // Need unit 1
1, // Min value
@ -177,10 +193,10 @@ static const std::vector<REFDES_UNITS_TEST_CASE> refdesUnitsTestCases = {
{"U1"} // U1 preloaded in tracker
},
{
"Case 8: Min value higher than available",
"U", "LM358", "OpAmp",
"Case 7: Min value higher than available",
"U", "LM358",
{
{ 5, { MOCK_SCH_REFERENCE("U", "LM358", "OpAmp", 1) } } // U5 unit 1 in use
{ 5, { std::make_tuple("LM358", 1) } } // U5 unit 1 in use
},
{2}, // Need unit 2
10, // Min value = 10
@ -188,8 +204,8 @@ static const std::vector<REFDES_UNITS_TEST_CASE> refdesUnitsTestCases = {
{}
},
{
"Case 9: Negative units filtered out",
"U", "LM358", "OpAmp",
"Case 8: Negative units filtered out",
"U", "LM358",
{},
{-1, 1, -5, 2}, // Mix of negative and positive units
1, // Min value
@ -197,11 +213,11 @@ static const std::vector<REFDES_UNITS_TEST_CASE> refdesUnitsTestCases = {
{}
},
{
"Case 10: Complex scenario with gaps",
"IC", "74HC00", "Logic",
"Case 9: Complex scenario with gaps",
"IC", "74HC00",
{
{ 2, { MOCK_SCH_REFERENCE("IC", "74HC00", "Logic", 1) } }, // IC2 unit 1 used
{ 4, { MOCK_SCH_REFERENCE("IC", "74HC00", "Logic", 2) } } // IC4 unit 2 used
{ 2, { std::make_tuple("74HC00", 1) } }, // IC2 unit 1 used
{ 4, { std::make_tuple("74HC00", 2) } } // IC4 unit 2 used
},
{1, 3}, // Need units 1 and 3
1, // Min value
@ -224,26 +240,93 @@ BOOST_AUTO_TEST_CASE( GetNextRefDesForUnits_EdgeCases )
{
REFDES_TRACKER tracker;
// Test empty required units vector
MOCK_SCH_REFERENCE testRef( "R", "1k", "Resistor", 1 );
// Test empty required units vector - should find completely unused reference
SCH_REFERENCE testRef = createTestReference( "R", "1k", 1 );
std::map<int, std::vector<SCH_REFERENCE>> emptyMap;
std::vector<int> emptyUnits;
// This should return the first unused reference number
// Note: This test framework is conceptual since we can't easily mock SCH_REFERENCE
setupRefDesTracker( tracker );
int result = tracker.GetNextRefDesForUnits( testRef, emptyMap, emptyUnits, 1 );
BOOST_CHECK_EQUAL( result, 1 );
BOOST_CHECK( tracker.Contains( "R1" ) );
BOOST_TEST_MESSAGE( "Edge case testing would require full SCH_REFERENCE mock implementation" );
// Test with some references already in tracker
tracker.Insert( "R3" );
result = tracker.GetNextRefDesForUnits( testRef, emptyMap, emptyUnits, 1 );
BOOST_CHECK_EQUAL( result, 2 ); // Should skip R1 (already inserted above) and get R2
// Test with negative units (should be filtered out)
std::vector<int> mixedUnits = {-1, 1, -5, 2};
SCH_REFERENCE testRef2 = createTestReference( "C", "100nF", 1 );
result = tracker.GetNextRefDesForUnits( testRef2, emptyMap, mixedUnits, 1 );
BOOST_CHECK_EQUAL( result, 1 );
}
BOOST_AUTO_TEST_CASE( GetNextRefDesForUnits_UsagePattern )
{
REFDES_TRACKER tracker;
// Demonstrate actual usage pattern for GetNextRefDesForUnits with our test helper
SCH_REFERENCE testRef = createTestReference( "U", "LM358", 1 );
// Create map of currently used references
std::map<int, std::vector<SCH_REFERENCE>> refNumberMap;
refNumberMap[1] = { createTestReference("U", "LM358", 1),
createTestReference("U", "LM358", 2) }; // U1 has units 1,2 used
refNumberMap[3] = { createTestReference("U", "LM358", 1) }; // U3 has unit 1 used
// Specify required units for new symbol
std::vector<int> requiredUnits = {1, 2};
setupRefDesTracker( tracker );
// Get next available reference number
int nextRefNum = tracker.GetNextRefDesForUnits( testRef, refNumberMap, requiredUnits, 1 );
// Should return 2 (U2) since U1 conflicts (units 1,2 already used) and U2 is available
BOOST_CHECK_EQUAL( nextRefNum, 2 );
BOOST_CHECK( tracker.Contains( "U2" ) );
// Test case where units are available in existing reference
std::vector<int> requiredUnits2 = {3, 4}; // These should be available in U1
refNumberMap[3] = { createTestReference("U", "LM358", 1) }; // U1 only has unit 1 and 2 used
nextRefNum = tracker.GetNextRefDesForUnits( testRef, refNumberMap, requiredUnits2, 1 );
BOOST_CHECK_EQUAL( nextRefNum, 1 ); // U1 should work since units 3,4 are available
// Test different value conflict
SCH_REFERENCE differentValueRef = createTestReference( "U", "LM741", 1 );
refNumberMap[4] = { createTestReference("U", "LM358", 1) }; // U4 has different value
nextRefNum = tracker.GetNextRefDesForUnits( differentValueRef, refNumberMap, {1}, 4 );
BOOST_CHECK_EQUAL( nextRefNum, 5 ); // Should skip U4 due to value conflict
}
BOOST_AUTO_TEST_CASE( GetNextRefDesForUnits_ThreadSafety )
{
REFDES_TRACKER tracker( true ); // Enable thread safety
// Basic test that thread-safe version can be created and used
int result = tracker.GetNextRefDes( "U", 1 );
BOOST_CHECK_EQUAL( result, 1 );
// Test that GetNextRefDesForUnits works with thread safety enabled
SCH_REFERENCE testRef = createTestReference( "U", "LM358", 1 );
std::map<int, std::vector<SCH_REFERENCE>> emptyMap;
std::vector<int> requiredUnits = {1, 2};
setupRefDesTracker( tracker );
int result = tracker.GetNextRefDesForUnits( testRef, emptyMap, requiredUnits, 1 );
BOOST_CHECK_EQUAL( result, 1 );
BOOST_CHECK( tracker.Contains( "U1" ) );
// Test multiple calls work correctly with thread safety
result = tracker.GetNextRefDesForUnits( testRef, emptyMap, requiredUnits, 1 );
BOOST_CHECK_EQUAL( result, 2 );
BOOST_CHECK( tracker.Contains( "U2" ) );
// Test with conflicts and thread safety
std::map<int, std::vector<SCH_REFERENCE>> conflictMap;
conflictMap[3] = { createTestReference("U", "LM358", 1) }; // U3 unit 1 in use
result = tracker.GetNextRefDesForUnits( testRef, conflictMap, requiredUnits, 1 );
BOOST_CHECK_EQUAL( result, 4 ); // Should skip U1,U2 (in tracker) and U3 (conflicted)
}
BOOST_AUTO_TEST_CASE( GetNextRefDesForUnits_Integration )
@ -258,24 +341,63 @@ BOOST_AUTO_TEST_CASE( GetNextRefDesForUnits_Integration )
BOOST_CHECK( tracker.Contains( "U3" ) );
BOOST_CHECK( !tracker.Contains( "U2" ) );
// GetNextRefDes should return U2
int next = tracker.GetNextRefDes( "U", 1 );
// Test GetNextRefDesForUnits with preloaded tracker
SCH_REFERENCE testRef = createTestReference( "U", "LM358", 1 );
std::map<int, std::vector<SCH_REFERENCE>> emptyMap;
std::vector<int> requiredUnits = {1, 2};
setupRefDesTracker( tracker );
// Should get U2 since U1 is already in tracker (preloaded) and U3 is also preloaded
int next = tracker.GetNextRefDesForUnits( testRef, emptyMap, requiredUnits, 1 );
BOOST_CHECK_EQUAL( next, 2 );
BOOST_CHECK( tracker.Contains( "U2" ) );
// Test with higher minimum value
next = tracker.GetNextRefDesForUnits( testRef, emptyMap, requiredUnits, 5 );
BOOST_CHECK_EQUAL( next, 5 );
BOOST_CHECK( tracker.Contains( "U5" ) );
// Test integration with serialization
std::string serialized = tracker.Serialize();
REFDES_TRACKER tracker2;
BOOST_CHECK( tracker2.Deserialize( serialized ) );
// Verify deserialized tracker has the same state
BOOST_CHECK( tracker2.Contains( "U1" ) );
BOOST_CHECK( tracker2.Contains( "U2" ) );
BOOST_CHECK( tracker2.Contains( "U3" ) );
BOOST_CHECK( tracker2.Contains( "U5" ) );
setupRefDesTracker( tracker2 );
// GetNextRefDesForUnits should work with deserialized tracker
next = tracker2.GetNextRefDesForUnits( testRef, emptyMap, requiredUnits, 1 );
BOOST_CHECK_EQUAL( next, 4 ); // Should get U4 (first available after U1,U2,U3,U5)
}
BOOST_AUTO_TEST_CASE( Serialization_WithTrackedReferences )
{
REFDES_TRACKER tracker;
// Add some references using both Insert and GetNextRefDes
// Add some references using both Insert and GetNextRefDesForUnits
tracker.Insert( "R1" );
tracker.Insert( "R3" );
int next = tracker.GetNextRefDes( "R", 1 ); // Should get R2
BOOST_CHECK_EQUAL( next, 2 );
next = tracker.GetNextRefDes( "C", 5 ); // Should get C5
BOOST_CHECK_EQUAL( next, 5 );
setupRefDesTracker( tracker );
// Use GetNextRefDesForUnits to get next reference
SCH_REFERENCE testRef = createTestReference( "R", "1k", 1 );
std::map<int, std::vector<SCH_REFERENCE>> emptyMap;
std::vector<int> requiredUnits = {1};
int next = tracker.GetNextRefDesForUnits( testRef, emptyMap, requiredUnits, 1 );
BOOST_CHECK_EQUAL( next, 2 ); // Should get R2
// Test with different prefix
SCH_REFERENCE capacitorRef = createTestReference( "C", "100nF", 1 );
next = tracker.GetNextRefDesForUnits( capacitorRef, emptyMap, requiredUnits, 5 );
BOOST_CHECK_EQUAL( next, 5 ); // Should get C5
// Test serialization
std::string serialized = tracker.Serialize();
@ -290,9 +412,18 @@ BOOST_AUTO_TEST_CASE( Serialization_WithTrackedReferences )
BOOST_CHECK( tracker2.Contains( "R3" ) );
BOOST_CHECK( tracker2.Contains( "C5" ) );
// Next reference should be R4
next = tracker2.GetNextRefDes( "R", 1 );
BOOST_CHECK_EQUAL( next, 4 );
setupRefDesTracker( tracker2 );
// Test GetNextRefDesForUnits with deserialized tracker
next = tracker2.GetNextRefDesForUnits( testRef, emptyMap, requiredUnits, 1 );
BOOST_CHECK_EQUAL( next, 4 ); // Next reference should be R4
// Test with unit conflicts after deserialization
std::map<int, std::vector<SCH_REFERENCE>> conflictMap;
conflictMap[4] = { createTestReference("R", "2k", 1) }; // R4 different value
next = tracker2.GetNextRefDesForUnits( testRef, conflictMap, requiredUnits, 1 );
BOOST_CHECK_EQUAL( next, 5 ); // Should skip R4 due to value conflict
}
BOOST_AUTO_TEST_SUITE_END()

View File

@ -23,6 +23,48 @@
#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
@ -40,17 +82,6 @@ struct UNIT_CONFLICT_TEST_CASE
std::string m_reason; // Reason for expected result
};
class TEST_UNIT_CONFLICTS_FIXTURE : public KI_TEST::SCHEMATIC_TEST_FIXTURE
{
protected:
// Helper to create mock SCH_REFERENCE objects for testing
SCH_REFERENCE createMockReference( const std::string& prefix, int number, int unit,
const std::string& value, const std::string& libName );
};
// Note: In a real implementation, this would need actual SCH_SYMBOL and SCH_SHEET_PATH objects
// For testing purposes, we'll focus on the logical validation
static const std::vector<UNIT_CONFLICT_TEST_CASE> unitConflictCases = {
{
"Units available - no conflicts",
@ -135,7 +166,7 @@ static const std::vector<UNIT_CONFLICT_TEST_CASE> unitConflictCases = {
}
};
BOOST_FIXTURE_TEST_SUITE( UnitConflicts, TEST_UNIT_CONFLICTS_FIXTURE )
BOOST_FIXTURE_TEST_SUITE( UnitConflicts, TEST_ANNOTATION_UNITS_CONFLICTS )
BOOST_AUTO_TEST_CASE( ValidateUnitConflictDetection )
{
@ -201,6 +232,7 @@ BOOST_AUTO_TEST_CASE( ValidateUnitConflictDetection )
BOOST_AUTO_TEST_CASE( GetNextRefDesForUnits_Integration )
{
REFDES_TRACKER tracker;
setupRefDesTracker( tracker );
// Test the overall GetNextRefDesForUnits logic using the tracker
@ -210,13 +242,15 @@ BOOST_AUTO_TEST_CASE( GetNextRefDesForUnits_Integration )
// Test case 1: Completely unused reference with empty units
// Should get U2 (first unused after U1)
// Note: This test is conceptual since we need real SCH_REFERENCE objects
SCH_REFERENCE testRef = createTestReference( "U", "LM358", 1 );
std::map<int, std::vector<SCH_REFERENCE>> emptyMap;
std::vector<int> emptyUnits;
int nextRef = tracker.GetNextRefDes( "U", 1 );
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.GetNextRefDes( "U", 10 );
nextRef = tracker.GetNextRefDesForUnits( testRef, emptyMap, emptyUnits, 10 );
BOOST_CHECK_EQUAL( nextRef, 10 ); // Should start from min value
// Verify references were inserted
@ -224,7 +258,8 @@ BOOST_AUTO_TEST_CASE( GetNextRefDesForUnits_Integration )
BOOST_CHECK( tracker.Contains( "U10" ) );
// Test case 3: New prefix
nextRef = tracker.GetNextRefDes( "IC", 1 );
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" ) );
}
@ -232,6 +267,7 @@ BOOST_AUTO_TEST_CASE( GetNextRefDesForUnits_Integration )
BOOST_AUTO_TEST_CASE( RefDesTracker_StateConsistency )
{
REFDES_TRACKER tracker;
setupRefDesTracker( tracker );
// Test that the tracker maintains consistent state across operations
@ -247,18 +283,22 @@ BOOST_AUTO_TEST_CASE( RefDesTracker_StateConsistency )
BOOST_CHECK( !tracker.Contains( "R2" ) );
BOOST_CHECK( !tracker.Contains( "R4" ) );
// Get next reference - should fill gap at R2
int next = tracker.GetNextRefDes( "R", 1 );
// 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.GetNextRefDes( "R", 1 );
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.GetNextRefDes( "R", 1 );
next = tracker.GetNextRefDesForUnits( testRef, emptyMap, emptyUnits, 1 );
BOOST_CHECK_EQUAL( next, 6 );
BOOST_CHECK( tracker.Contains( "R6" ) );
@ -269,27 +309,32 @@ BOOST_AUTO_TEST_CASE( RefDesTracker_StateConsistency )
BOOST_AUTO_TEST_CASE( CacheConsistency_AfterInserts )
{
REFDES_TRACKER tracker;
setupRefDesTracker( tracker );
// Test that cache remains consistent after mixed Insert/GetNextRefDes operations
// 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.GetNextRefDes( "C", 1 );
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.GetNextRefDes( "C", 1 );
next = tracker.GetNextRefDesForUnits( testRef, emptyMap, emptyUnits, 1 );
BOOST_CHECK_EQUAL( next, 4 );
// Test with minimum value - should respect cache
next = tracker.GetNextRefDes( "C", 7 );
next = tracker.GetNextRefDesForUnits( testRef, emptyMap, emptyUnits, 7 );
BOOST_CHECK_EQUAL( next, 7 ); // C6 available but min is 7
// Verify all references exist
@ -306,12 +351,18 @@ BOOST_AUTO_TEST_CASE( CacheConsistency_AfterInserts )
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" ) );
int next = tracker.GetNextRefDes( "U", 1 );
// 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

View File

@ -24,6 +24,48 @@
#include <sch_sheet_path.h>
#include <refdes_tracker.h>
class TEST_ANNOTATION_UNITS_INTEGRATION : 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
} );
}
};
struct REANNOTATED_REFERENCE
{
wxString m_KIID; ///< KIID of the symbol to reannotate
@ -39,6 +81,29 @@ protected:
void setupRefDesTracker();
void testFindFirstUnusedReferenceWithUnits();
void verifyUnitAvailabilityLogic();
// Helper method to setup units checker for testing
void setupRefDesTracker( REFDES_TRACKER& tracker )
{
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
} );
}
};
void TEST_SCH_REFERENCE_LIST_UNITS_FIXTURE::setupRefDesTracker()
@ -55,7 +120,7 @@ void TEST_SCH_REFERENCE_LIST_UNITS_FIXTURE::setupRefDesTracker()
m_schematic->Settings().m_refDesTracker = tracker;
}
BOOST_FIXTURE_TEST_SUITE( SchReferenceListUnits, TEST_SCH_REFERENCE_LIST_UNITS_FIXTURE )
BOOST_FIXTURE_TEST_SUITE( SchReferenceListUnits, TEST_ANNOTATION_UNITS_INTEGRATION )
struct UNIT_ANNOTATION_CASE
{
@ -159,18 +224,26 @@ BOOST_AUTO_TEST_CASE( RefDesTrackerIntegration )
tracker->Insert( "R2" );
tracker->Insert( "R5" );
// Test that GetNextRefDes respects previously inserted references
int nextU = tracker->GetNextRefDes( "U", 1 );
// Test GetNextRefDesForUnits respects previously inserted references
SCH_REFERENCE uRef = createTestReference( "U", "LM358", 1 );
std::map<int, std::vector<SCH_REFERENCE>> emptyMap;
std::vector<int> emptyUnits;
setupRefDesTracker( *tracker );
int nextU = tracker->GetNextRefDesForUnits( uRef, emptyMap, emptyUnits, 1 );
BOOST_CHECK_EQUAL( nextU, 2 ); // Should skip U1, get U2
int nextR = tracker->GetNextRefDes( "R", 1 );
SCH_REFERENCE rRef = createTestReference( "R", "1k", 1 );
int nextR = tracker->GetNextRefDesForUnits( rRef, emptyMap, emptyUnits, 1 );
BOOST_CHECK_EQUAL( nextR, 3 ); // Should skip R1,R2, get R3
int nextIC = tracker->GetNextRefDes( "IC", 1 );
SCH_REFERENCE icRef = createTestReference( "IC", "74HC00", 1 );
int nextIC = tracker->GetNextRefDesForUnits( icRef, emptyMap, emptyUnits, 1 );
BOOST_CHECK_EQUAL( nextIC, 1 ); // New prefix, should get IC1
// Test with minimum values
int nextU_min5 = tracker->GetNextRefDes( "U", 5 );
int nextU_min5 = tracker->GetNextRefDesForUnits( uRef, emptyMap, emptyUnits, 5 );
BOOST_CHECK_EQUAL( nextU_min5, 5 ); // Should get U5 (min value 5)
// Verify all references were inserted
@ -188,13 +261,7 @@ BOOST_AUTO_TEST_CASE( FindFirstUnusedReferenceWithUnits )
refList.SetRefDesTracker( tracker );
// Simulate some existing references in the list
// This would normally come from the schematic, but we'll create mock data
// Test the concept of unit availability checking
// (The actual implementation would require real SCH_REFERENCE objects)
BOOST_TEST_MESSAGE( "Testing unit availability concept - requires full schematic integration" );
// Test the concept of unit availability checking with GetNextRefDesForUnits
// Test that tracker properly handles unit conflicts
tracker->Insert( "U1" ); // Simulate U1 being previously used
@ -202,8 +269,16 @@ BOOST_AUTO_TEST_CASE( FindFirstUnusedReferenceWithUnits )
// The FindFirstUnusedReference method should now use GetNextRefDesForUnits
// and properly consider unit conflicts when finding available references
int next = tracker->GetNextRefDes( "U", 1 );
SCH_REFERENCE testRef = createTestReference( "U", "LM358", 1 );
std::map<int, std::vector<SCH_REFERENCE>> emptyMap;
std::vector<int> emptyUnits;
setupRefDesTracker( *tracker );
int next = tracker->GetNextRefDesForUnits( testRef, emptyMap, emptyUnits, 1 );
BOOST_CHECK_EQUAL( next, 2 ); // Should skip U1
BOOST_TEST_MESSAGE( "Testing unit availability concept - integrated with GetNextRefDesForUnits" );
}
BOOST_AUTO_TEST_CASE( SerializationWithComplexRefs )
@ -213,13 +288,23 @@ BOOST_AUTO_TEST_CASE( SerializationWithComplexRefs )
// Add references through various methods
tracker->Insert( "U1" );
tracker->Insert( "U3" );
int next = tracker->GetNextRefDes( "U", 1 ); // Gets U2
BOOST_CHECK_EQUAL( next, 2 );
// Use GetNextRefDesForUnits to get U2
SCH_REFERENCE uRef = createTestReference( "U", "LM358", 1 );
std::map<int, std::vector<SCH_REFERENCE>> emptyMap;
std::vector<int> emptyUnits;
setupRefDesTracker( *tracker );
int next = tracker->GetNextRefDesForUnits( uRef, emptyMap, emptyUnits, 1 );
BOOST_CHECK_EQUAL( next, 2 ); // Gets U2
tracker->Insert( "IC1" );
tracker->Insert( "IC5" );
next = tracker->GetNextRefDes( "IC", 3 ); // Gets IC3
BOOST_CHECK_EQUAL( next, 3 );
SCH_REFERENCE icRef = createTestReference( "IC", "74HC00", 1 );
next = tracker->GetNextRefDesForUnits( icRef, emptyMap, emptyUnits, 3 );
BOOST_CHECK_EQUAL( next, 3 ); // Gets IC3
// Test serialization captures all state
std::string serialized = tracker->Serialize();
@ -235,11 +320,12 @@ BOOST_AUTO_TEST_CASE( SerializationWithComplexRefs )
BOOST_CHECK( tracker2->Contains( "IC3" ) );
BOOST_CHECK( tracker2->Contains( "IC5" ) );
// Verify next references continue correctly
next = tracker2->GetNextRefDes( "U", 1 );
// Verify next references continue correctly using GetNextRefDesForUnits
setupRefDesTracker( *tracker2 );
next = tracker2->GetNextRefDesForUnits( uRef, emptyMap, emptyUnits, 1 );
BOOST_CHECK_EQUAL( next, 4 );
next = tracker2->GetNextRefDes( "IC", 1 );
next = tracker2->GetNextRefDesForUnits( icRef, emptyMap, emptyUnits, 1 );
BOOST_CHECK_EQUAL( next, 2 );
}
@ -253,22 +339,29 @@ BOOST_AUTO_TEST_CASE( CachingEfficiency )
tracker->Insert( "R" + std::to_string( i ) );
}
SCH_REFERENCE rRef = createTestReference( "R", "1k", 1 );
std::map<int, std::vector<SCH_REFERENCE>> emptyMap;
std::vector<int> emptyUnits;
setupRefDesTracker( *tracker );
// Test that repeated calls with same parameters are fast (cached)
auto start = std::chrono::high_resolution_clock::now();
for( int i = 0; i < 100; ++i )
{
int result1 = tracker->GetNextRefDes( "R", 1 ); // Should be cached after first call
int result50 = tracker->GetNextRefDes( "R", 50 ); // Should be cached after first call
// These calls should benefit from internal caching in REFDES_TRACKER
int result1 = tracker->GetNextRefDesForUnits( rRef, emptyMap, emptyUnits, 1 );
int result50 = tracker->GetNextRefDesForUnits( rRef, emptyMap, emptyUnits, 50 );
}
auto end = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::microseconds>( end - start );
BOOST_TEST_MESSAGE( "200 cached calls took: " + std::to_string( duration.count() ) + " microseconds" );
BOOST_TEST_MESSAGE( "200 GetNextRefDesForUnits calls took: " + std::to_string( duration.count() ) + " microseconds" );
// The actual time will vary, but cached calls should be very fast
BOOST_CHECK_LT( duration.count(), 10000 ); // Should be under 10ms for 200 cached calls
// The actual time will vary, but the calls should complete in reasonable time
BOOST_CHECK_LT( duration.count(), 50000 ); // Should be under 50ms for 200 calls
}
BOOST_AUTO_TEST_SUITE_END()

View File

@ -248,7 +248,6 @@ BOOST_AUTO_TEST_CASE( Reannotate )
loadTestCase( c.m_SchematicRelativePath, c.m_ExpectedReannotations );
m_refsToReannotate.SetRefDesTracker( m_schematic->Settings().m_refDesTracker );
m_refsToReannotate.SetReuseRefDes( m_schematic->Settings().m_reuseRefDes );
m_refsToReannotate.RemoveAnnotation();
m_refsToReannotate.SplitReferences();
m_refsToReannotate.Annotate( false, 0, c.m_StartNumber, m_lockedRefs, getAdditionalRefs() );
@ -290,7 +289,6 @@ BOOST_AUTO_TEST_CASE( ReannotateDuplicates )
loadTestCase( c.m_SchematicRelativePath, c.m_ExpectedReannotations );
m_refsToReannotate.SetRefDesTracker( m_schematic->Settings().m_refDesTracker );
m_refsToReannotate.SetReuseRefDes( m_schematic->Settings().m_reuseRefDes );
m_refsToReannotate.ReannotateDuplicates( getAdditionalRefs() );
m_refsToReannotate.UpdateAnnotation();