From 3a7aa7b6601b7ba3325abc3e978576953e380ff4 Mon Sep 17 00:00:00 2001
From: JamesJCode <13408010-JamesJCode@users.noreply.gitlab.com>
Date: Fri, 20 Dec 2024 00:23:25 +0000
Subject: [PATCH] Ensure deterministic ordering in cadstar netlist exporter

Also re-enable checking of exporter in qa_cli
---
 .../netlist_exporter_cadstar.cpp              | 37 +++++++++++++++++--
 .../cli/basic_test/basic_test.netlist.cadstar | 30 +++++++--------
 qa/tests/cli/test_sch.py                      |  3 +-
 3 files changed, 49 insertions(+), 21 deletions(-)

diff --git a/eeschema/netlist_exporters/netlist_exporter_cadstar.cpp b/eeschema/netlist_exporters/netlist_exporter_cadstar.cpp
index 12520e98a1..0aeeda79d6 100644
--- a/eeschema/netlist_exporters/netlist_exporter_cadstar.cpp
+++ b/eeschema/netlist_exporters/netlist_exporter_cadstar.cpp
@@ -67,7 +67,23 @@ bool NETLIST_EXPORTER_CADSTAR::WriteNetlist( const wxString& aOutFileName,
 
     for( const SCH_SHEET_PATH& sheet : m_schematic->Hierarchy() )
     {
-        for( SCH_ITEM* item : sheet.LastScreen()->Items().OfType( SCH_SYMBOL_T ) )
+        // The rtree returns items in a non-deterministic order (platform-dependent)
+        // Therefore we need to sort them before outputting to ensure file stability for version
+        // control and QA comparisons
+        std::vector<EDA_ITEM*> sheetItems;
+
+        for( EDA_ITEM* item : sheet.LastScreen()->Items().OfType( SCH_SYMBOL_T ) )
+            sheetItems.push_back( item );
+
+        auto pred = []( const EDA_ITEM* item1, const EDA_ITEM* item2 )
+        {
+            return item1->m_Uuid < item2->m_Uuid;
+        };
+
+        std::sort( sheetItems.begin(), sheetItems.end(), pred );
+
+        // Process symbol attributes
+        for( EDA_ITEM* item : sheetItems )
         {
             symbol = findNextSymbol( item, sheet );
 
@@ -115,13 +131,16 @@ bool NETLIST_EXPORTER_CADSTAR::writeListOfNets( FILE* f )
     wxString InitNetDesc  = StartLine + wxT( "ADD_TER" );
     wxString StartNetDesc = StartLine + wxT( "TER" );
     wxString InitNetDescLine;
-    wxString netName;
+
+    std::vector<std::pair<wxString, std::vector<std::pair<SCH_PIN*, SCH_SHEET_PATH>>>> all_nets;
 
     for( const auto& [ key, subgraphs ] : m_schematic->ConnectionGraph()->GetNetMap() )
     {
+        wxString netName;
         netName.Printf( wxT( "\"%s\"" ), key.Name );
 
-        std::vector<std::pair<SCH_PIN*, SCH_SHEET_PATH>> sorted_items;
+        all_nets.emplace_back( netName, std::vector<std::pair<SCH_PIN*, SCH_SHEET_PATH>>{} );
+        std::vector<std::pair<SCH_PIN*, SCH_SHEET_PATH>>& sorted_items = all_nets.back().second;
 
         for( CONNECTION_SUBGRAPH* subgraph : subgraphs )
         {
@@ -134,7 +153,7 @@ bool NETLIST_EXPORTER_CADSTAR::writeListOfNets( FILE* f )
             }
         }
 
-        // Netlist ordering: Net name, then ref des, then pin name
+        // Intra-net ordering: Ref des, then pin name
         std::sort( sorted_items.begin(), sorted_items.end(),
                 []( std::pair<SCH_PIN*, SCH_SHEET_PATH> a, std::pair<SCH_PIN*, SCH_SHEET_PATH> b )
                 {
@@ -159,7 +178,17 @@ bool NETLIST_EXPORTER_CADSTAR::writeListOfNets( FILE* f )
                     return ref_a == ref_b && a.first->GetShownNumber() == b.first->GetShownNumber();
                 } ),
                 sorted_items.end() );
+    }
 
+    // Inter-net ordering by net name
+    std::sort( all_nets.begin(), all_nets.end(),
+               []( const auto& a, const auto& b )
+               {
+                   return a.first < b.first;
+               } );
+
+    for( const auto& [netName, sorted_items] : all_nets )
+    {
         print_ter = 0;
 
         for( const std::pair<SCH_PIN*, SCH_SHEET_PATH>& pair : sorted_items )
diff --git a/qa/data/cli/basic_test/basic_test.netlist.cadstar b/qa/data/cli/basic_test/basic_test.netlist.cadstar
index 8493a84522..7224edba51 100644
--- a/qa/data/cli/basic_test/basic_test.netlist.cadstar
+++ b/qa/data/cli/basic_test/basic_test.netlist.cadstar
@@ -1,19 +1,22 @@
 .HEA
-.TIM 5/2/2023 8:59:04 PM
-.APP "Eeschema 7.99.0-893-g4a5939297b-dirty"
+.TIM 2024-12-20T00:07:25+0000
+.APP "Eeschema 8.99.0-3439-gc9d74d24f0-dirty"
 .TYP FULL
 
-.ADD_COM     R1     "10k"     "Resistor_SMD:R_1206_3216Metric"
+.ADD_COM     J1     "Conn_01x01_Pin"     "TestPoint:TestPoint_Pad_3.0x3.0mm"
+.ADD_COM     J2     "Conn_01x01_Pin"     "TestPoint:TestPoint_Pad_3.0x3.0mm"
+.ADD_COM     J4     "Conn_01x01_Pin"     "TestPoint:TestPoint_Pad_3.0x3.0mm"
 .ADD_COM     C1     "10u"     "Capacitor_SMD:C_1206_3216Metric"
 .ADD_COM     R2     "10k"     "Resistor_SMD:R_1206_3216Metric"
-.ADD_COM     J3     "Conn_01x01_Pin"     "TestPoint:TestPoint_Pad_3.0x3.0mm"
-.ADD_COM     J2     "Conn_01x01_Pin"     "TestPoint:TestPoint_Pad_3.0x3.0mm"
-.ADD_COM     J1     "Conn_01x01_Pin"     "TestPoint:TestPoint_Pad_3.0x3.0mm"
-.ADD_COM     J4     "Conn_01x01_Pin"     "TestPoint:TestPoint_Pad_3.0x3.0mm"
 .ADD_COM     R3     "10k"     "Resistor_SMD:R_1206_3216Metric"
+.ADD_COM     J3     "Conn_01x01_Pin"     "TestPoint:TestPoint_Pad_3.0x3.0mm"
 .ADD_COM     U1     "TLV2371DBV"     "Package_TO_SOT_SMD:SOT-23-5"
+.ADD_COM     R1     "10k"     "Resistor_SMD:R_1206_3216Metric"
 
 
+.ADD_TER   J2   1     "/IN"
+.TER       R3   1
+
 .ADD_TER   J3   1     "/OUT"
 .TER       R2   2
             U1   1
@@ -21,6 +24,11 @@
 .ADD_TER   J1   1     "/VCC"
 .TER       U1   5
 
+.ADD_TER   C1   2     "GND"
+.TER       J4   1
+            R1   1
+            U1   2
+
 .ADD_TER   C1   1     "Net-(U1-+)"
 .TER       R3   2
             U1   3
@@ -29,12 +37,4 @@
 .TER       R2   1
             U1   4
 
-.ADD_TER   C1   2     "GND"
-.TER       J4   1
-            R1   1
-            U1   2
-
-.ADD_TER   J2   1     "/IN"
-.TER       R3   1
-
 .END
diff --git a/qa/tests/cli/test_sch.py b/qa/tests/cli/test_sch.py
index 1553b48824..1a4e19770d 100644
--- a/qa/tests/cli/test_sch.py
+++ b/qa/tests/cli/test_sch.py
@@ -75,8 +75,7 @@ def test_sch_export_svg( kitest,
                             [("cli/basic_test/basic_test.kicad_sch", "basic_test.netlist.kicadsexpr", 5, True, []),
                              ("cli/basic_test/basic_test.kicad_sch", "basic_test.netlist.kicadsexpr", 5, True,["--format=kicadsexpr"]),
                              ("cli/basic_test/basic_test.kicad_sch", "basic_test.netlist.kicadxml", 6, True,["--format=kicadxml"]),
-                             # currently inconsistenly sorts nets between platforms (MSW/Linux)
-                             ("cli/basic_test/basic_test.kicad_sch", "basic_test.netlist.cadstar", 3, True, ["--format=cadstar"]),
+                             ("cli/basic_test/basic_test.kicad_sch", "basic_test.netlist.cadstar", 3, False, ["--format=cadstar"]),
                              ("cli/basic_test/basic_test.kicad_sch", "basic_test.netlist.orcadpcb2", 1, False, ["--format=orcadpcb2"])
                              ])
 def test_sch_export_netlist( kitest,