KiCad PCB EDA Suite
Loading...
Searching...
No Matches
qa_pns_regressions_main.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright The KiCad Developers.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
20#define BOOST_TEST_NO_MAIN
21
22#include <wx/app.h>
23#include <wx/cmdline.h>
24#include <wx/stdstream.h>
25#include <wx/wfstream.h>
26#include <wx/textfile.h>
27
28#include <io/io_utils.h>
29
33#include <wx/dir.h>
34#include <wx/utils.h>
35
36#include "pns_log_file.h"
38
39#include <boost/test/unit_test.hpp>
40
41using namespace boost::unit_test;
42
43std::map<wxString, wxString> g_testBoards;
44static bool g_updateGolden = false;
45
47{
48 PNS_TEST_CASE( const wxString & aName, const wxString& aPath ) : m_name( aName ), m_dataPath( aPath ){};
49
50 wxString GetDataPath() const { return m_dataPath; }
51 wxString GetName() const { return m_name; }
52 wxString m_dataPath;
53 wxString m_name;
54};
55
56
58{
59public:
61 {
63 m_Log->SetConsoleOutputEnabled( wxGetEnv( wxS( "KICAD_PNS_VERBOSE" ), nullptr ) );
65 }
66
68 {
69 delete m_Reporter;
70 delete m_Log;
71 }
72
75};
76
77
79{
80public:
81 static bool RunTest( PNS_TEST_CASE* aTestData )
82 {
83 BOOST_TEST_CONTEXT( aTestData->GetName() )
84 {
85 PNS_LOG_FILE logFile;
86 PNS_LOG_PLAYER player;
87
88 auto hash = logFile.GetLogBoardHash( aTestData->GetDataPath() );
89
90 wxString boardFilename;
91
92 if( hash.has_value() )
93 {
94 if( auto it = g_testBoards.find( *hash ); it != g_testBoards.end() )
95 {
96 boardFilename = it->second;
97 BOOST_TEST_MESSAGE( "found matching board: " << boardFilename );
98 }
99 }
100
101 if( !logFile.Load( wxString( aTestData->GetDataPath() ), m_logger.m_Reporter, boardFilename ) )
102 {
103 // Missing board or stale hash; don't fail QA for this
104 BOOST_TEST_MESSAGE( "Failed to load test " << aTestData->GetName() << " from "
105 << aTestData->GetDataPath() << ", will skip" );
106 BOOST_CHECK( true );
107 return true;
108 }
109
110 player.SetReporter( m_logger.m_Reporter );
111
112 if( g_updateGolden )
113 {
114 player.ReplayLog( &logFile, 0, 0, -1, true );
115
116 if( logFile.SaveLog( aTestData->GetDataPath(), m_logger.m_Reporter ) )
117 BOOST_TEST_MESSAGE( "Updated golden file: " << aTestData->GetDataPath() );
118 else
119 BOOST_TEST_ERROR( "Failed to save golden file: " << aTestData->GetDataPath() );
120
121 return true;
122 }
123
124 player.ReplayLog( &logFile, 0 );
125
126 auto cstate = player.GetRouterUpdatedItems();
127 auto expected = logFile.GetExpectedResult();
128
129 bool hasHeads = expected.m_heads.size() > 0;
130
131 if( !hasHeads )
132 {
133 BOOST_TEST_MESSAGE( "Test " << aTestData->m_name << " has no reference head geometry. Skipping head comparson" );
134 }
135
136 BOOST_TEST_MESSAGE( "Reference has " << expected.m_addedItems.size() << " added items, "
137 << expected.m_removedIds.size() << " removed items and "
138 << expected.m_heads.size() << " head items." );
139
140 BOOST_TEST_MESSAGE( "Test result has " << cstate.m_addedItems.size() << " added items, "
141 << cstate.m_removedIds.size() << " removed items and "
142 << cstate.m_heads.size() << " head items." );
143
144
145 bool pass = cstate.Compare( expected, !hasHeads );
146 BOOST_REQUIRE( pass );
147 return pass;
148 }
149
150 return false;
151 }
152
154};
155
156
158
159std::vector<wxString> scanSubdirs( const wxString& absPath, bool aFiles, wxString filespec = wxEmptyString )
160{
161 std::vector<wxString> rv;
162
163 wxDir dir( absPath );
164 if( !dir.IsOpened() )
165 {
166 BOOST_TEST_ERROR( "Failed to open directory: " << absPath );
167 return rv;
168 }
169
170 wxString dirName;
171
172 bool hasFiles = dir.GetFirst( &dirName, filespec, aFiles ? wxDIR_FILES : wxDIR_DIRS );
173 while( hasFiles )
174 {
175 rv.push_back( dirName );
176 hasFiles = dir.GetNext( &dirName );
177 }
178
179 return rv;
180}
181
182void scanAndHashBoards( const wxString& aPath )
183{
184 for( const wxString& brdFile : scanSubdirs( aPath, true, wxT("*.kicad_pcb") ) )
185 {
186 wxFileName path ( aPath );
187 path.SetName( brdFile );
188 std::optional<wxString> hash = IO_UTILS::fileHashMMH3( path.GetFullPath() );
189 if( hash )
190 {
191 BOOST_TEST_MESSAGE("- file: " << std::left << std::setw(50) << brdFile << " hash: " << ( *hash ) );
192 g_testBoards[ *hash ] = path.GetFullPath();
193 }
194 }
195}
196
197std::vector<PNS_TEST_CASE*> createTestCases()
198{
199 std::vector<PNS_TEST_CASE*> testCases;
200
201 wxFileName absPath( KI_TEST::GetPcbnewTestDataDir() );
202 absPath.AppendDir (wxT("pns_regressions"));
203
204 wxFileName boardsPath( absPath );
205 boardsPath.AppendDir(wxT("boards"));
206
207 scanAndHashBoards( boardsPath.GetFullPath() );
208
209 for( const wxString& subdir : scanSubdirs( absPath.GetFullPath(), false ) )
210 {
211 if( !subdir.CmpNoCase( wxT("boards") ) )
212 continue;
213
214 wxFileName path( absPath );
215 path.AppendDir( subdir );
216
217 for( const wxString& logName: scanSubdirs( path.GetFullPath(), true, wxT("*.log") ) )
218 {
219 wxFileName logPath( path );
220 logPath.SetName( logName );
221
222 BOOST_TEST_MESSAGE( "Add case " << logPath.GetFullPath() );
223
224 testCases.push_back( new PNS_TEST_CASE( subdir, logPath.GetFullPath() ) );
225 }
226 }
227
228 return testCases;
229}
230
231
233{
234 test_suite* pnsTestSuite = BOOST_TEST_SUITE( "pns_regressions" );
235
236 auto testCases = createTestCases();
237
238 for( auto& c : testCases )
239 {
240 pnsTestSuite->add(
241 BOOST_TEST_CASE_NAME( std::bind( &PNS_TEST_FIXTURE::RunTest, c ), c->GetName().ToStdString() ) );
242 }
243
244 framework::master_test_suite().p_name.value = "P&S router regressions";
245 framework::master_test_suite().add( pnsTestSuite );
246 return true;
247}
248
249
250int main( int argc, char* argv[] )
251{
252 wxApp::SetInstance( new wxAppConsole );
253 wxInitialize( argc, argv );
254
255 for( int i = 1; i < argc; i++ )
256 {
257 if( wxString( argv[i] ).Cmp( wxT( "--update-golden" ) ) == 0 )
258 g_updateGolden = true;
259 }
260
261 int ret = unit_test_main( &init_pns_test_suite, argc, argv );
262
263 wxUninitialize();
264 return ret;
265}
General utilities for PCB file IO for QA programs.
KI_TEST::CONSOLE_LOG * m_Log
KI_TEST::CONSOLE_MSG_REPORTER * m_Reporter
const std::optional< wxString > GetLogBoardHash(const wxString &logFileName)
const COMMIT_STATE & GetExpectedResult() const
bool SaveLog(const wxFileName &logFileName, REPORTER *aRpt)
bool Load(const wxFileName &logFileName, REPORTER *aRpt, const wxString boardFileName=wxT(""))
const PNS_LOG_FILE::COMMIT_STATE GetRouterUpdatedItems()
void ReplayLog(PNS_LOG_FILE *aLog, int aStartEventIndex=0, int aFrom=0, int aTo=-1, bool aUpdateExpectedResult=false)
void SetReporter(REPORTER *aReporter)
std::optional< wxString > fileHashMMH3(const wxString &aFilePath)
Calculates an MMH3 hash of a given file.
Definition io_utils.cpp:91
std::string GetPcbnewTestDataDir()
Utility which returns a path to the data directory where the test board files are stored.
std::vector< PNS_TEST_CASE * > createTestCases()
void scanAndHashBoards(const wxString &aPath)
std::map< wxString, wxString > g_testBoards
static bool g_updateGolden
bool init_pns_test_suite()
std::vector< wxString > scanSubdirs(const wxString &absPath, bool aFiles, wxString filespec=wxEmptyString)
wxString GetName() const
PNS_TEST_CASE(const wxString &aName, const wxString &aPath)
wxString GetDataPath() const
static bool RunTest(PNS_TEST_CASE *aTestData)
static FIXTURE_LOGGER m_logger
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
std::string path
VECTOR3I expected(15, 30, 45)
BOOST_TEST_CONTEXT("Test Clearance")
BOOST_TEST_MESSAGE("Polyline has "<< chain.PointCount()<< " points")