24#include <boost/test/unit_test.hpp>
31#include <wx/filename.h>
32#include <wx/process.h>
33#include <wx/stdpaths.h>
34#include <wx/txtstrm.h>
41#ifndef QA_KICAD_CLI_PATH
42#define QA_KICAD_CLI_PATH "kicad-cli"
53 static int counter = 0;
54 m_path = wxFileName::GetTempDir() + wxFILE_SEP_PATH
55 + wxString::Format( wxS(
"kicad_cli_visual_diff_%ld_%d" ),
56 static_cast<long>( wxGetProcessId() ), ++counter );
58 BOOST_REQUIRE( wxFileName::Mkdir( m_path, wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ) );
63 if( !m_path.IsEmpty() && wxFileName::DirExists( m_path ) )
64 wxFileName::Rmdir( m_path, wxPATH_RMDIR_RECURSIVE );
67 wxString Path(
const wxString& aName )
const
69 return m_path + wxFILE_SEP_PATH + aName;
84wxString readStream( wxInputStream* aStream )
91 wxTextInputStream textStream( *aStream );
93 while( !aStream->Eof() )
95 wxString line = textStream.ReadLine();
97 if( !line.IsEmpty() || !aStream->Eof() )
99 if( !output.IsEmpty() )
100 output += wxS(
"\n" );
110COMMAND_RESULT runCli(
const std::vector<wxString>& aArgs )
115 std::vector<const wchar_t*> argv;
116 argv.reserve( aArgs.size() + 2 );
119 for(
const wxString& arg : aArgs )
120 argv.push_back( arg.wc_str() );
122 argv.push_back(
nullptr );
125 result.exitCode =
static_cast<int>( wxExecute(
const_cast<wchar_t**
>( argv.data() ), wxEXEC_SYNC, &
process ) );
135void copyFixture(
const wxString& aName,
const wxString& aDest )
138 + wxS(
"diff_merge/visual_diff/" ) + aName;
140 BOOST_REQUIRE_MESSAGE( wxCopyFile( src, aDest ),
"Could not copy " << src );
144std::string readFileBytes(
const wxString& aPath )
146 wxFile file( aPath );
147 BOOST_REQUIRE_MESSAGE( file.IsOpened(),
"Could not open " << aPath );
149 const wxFileOffset len = file.Length();
150 BOOST_REQUIRE_GE( len, 0 );
152 std::string bytes(
static_cast<size_t>( len ),
'\0' );
156 ssize_t read = file.Read( bytes.data(),
static_cast<size_t>( len ) );
157 BOOST_REQUIRE_EQUAL( read, len );
164void expectCleanExit(
const wxString& aName,
const COMMAND_RESULT& aResult,
int aExpectedExitCode )
169 BOOST_CHECK_MESSAGE( aResult.output.IsEmpty(),
"Unexpected stdout: " << aResult.output );
170 BOOST_CHECK_MESSAGE( aResult.error.IsEmpty(),
"Unexpected stderr: " << aResult.error );
175void expectInvalidExit(
const wxString& aName,
const COMMAND_RESULT& aResult,
int aExpectedExitCode )
184void expectSvg(
const wxString& aName,
const wxString& aPath )
188 std::string bytes = readFileBytes( aPath );
190 BOOST_CHECK( bytes.find(
"<svg" ) != std::string::npos );
195void expectPng(
const wxString& aName,
const wxString& aPath )
197 static constexpr std::array<unsigned char, 8> PNG_HEADER = { 0x89,
'P',
'N',
'G',
'\r',
'\n', 0x1a,
'\n' };
201 std::string bytes = readFileBytes( aPath );
202 BOOST_REQUIRE_GE( bytes.size(), PNG_HEADER.size() );
204 for(
size_t i = 0; i < PNG_HEADER.size(); ++i )
210void expectFilesDiffer(
const wxString& aName,
const wxString& aPathA,
const wxString& aPathB )
214 BOOST_CHECK( readFileBytes( aPathA ) != readFileBytes( aPathB ) );
221 explicit CLI_FIXTURES( TEMP_DIR& aDir )
223 pcbA = aDir.Path( wxS(
"pcb_a.kicad_pcb" ) );
224 pcbB = aDir.Path( wxS(
"pcb_b.kicad_pcb" ) );
225 schA = aDir.Path( wxS(
"sch_a.kicad_sch" ) );
226 schB = aDir.Path( wxS(
"sch_b.kicad_sch" ) );
227 fpA = aDir.Path( wxS(
"fp_a.pretty" ) );
228 fpB = aDir.Path( wxS(
"fp_b.pretty" ) );
229 symA = aDir.Path( wxS(
"sym_a.kicad_sym" ) );
230 symB = aDir.Path( wxS(
"sym_b.kicad_sym" ) );
238 void writePcbFixtures()
240 copyFixture( wxS(
"pcb_a.kicad_pcb" ), pcbA );
241 copyFixture( wxS(
"pcb_b.kicad_pcb" ), pcbB );
244 void writeSchFixtures()
246 copyFixture( wxS(
"sch_a.kicad_sch" ), schA );
247 copyFixture( wxS(
"sch_b.kicad_sch" ), schB );
250 void writeFpFixtures()
252 BOOST_REQUIRE( wxFileName::Mkdir( fpA, wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ) );
253 BOOST_REQUIRE( wxFileName::Mkdir( fpB, wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ) );
256 copyFixture( wxS(
"fp_b.pretty/VisualDiff.kicad_mod" ),
257 fpB + wxFILE_SEP_PATH + wxS(
"VisualDiff.kicad_mod" ) );
260 void writeSymFixtures()
262 copyFixture( wxS(
"sym_a.kicad_sym" ), symA );
263 copyFixture( wxS(
"sym_b.kicad_sym" ), symB );
279 wxString commandGroup;
280 wxString commandName;
282 wxString changedPath;
286void expectVisualDifference( TEMP_DIR& aDir,
const CLI_CASE& aCase,
const wxString& aFormat )
288 const wxString caseName = aCase.commandGroup + wxS(
" " ) + aFormat;
289 const wxString sameOut = aDir.Path( aCase.commandGroup + wxS(
"_same." ) + aFormat );
290 const wxString diffOut = aDir.Path( aCase.commandGroup + wxS(
"_diff." ) + aFormat );
292 std::vector<wxString> sameArgs = { aCase.commandGroup, aCase.commandName, aCase.refPath, aCase.refPath,
293 wxS(
"--format" ), aFormat, wxS(
"--output" ), sameOut };
294 std::vector<wxString> diffArgs = { aCase.commandGroup, aCase.commandName, aCase.refPath, aCase.changedPath,
295 wxS(
"--format" ), aFormat, wxS(
"--output" ), diffOut };
297 expectCleanExit( caseName + wxS(
" identical" ), runCli( sameArgs ), 0 );
298 expectCleanExit( caseName + wxS(
" changed" ), runCli( diffArgs ), 5 );
300 if( aFormat == wxS(
"svg" ) )
302 expectSvg( caseName + wxS(
" identical" ), sameOut );
303 expectSvg( caseName + wxS(
" changed" ), diffOut );
307 expectPng( caseName + wxS(
" identical" ), sameOut );
308 expectPng( caseName + wxS(
" changed" ), diffOut );
311 expectFilesDiffer( caseName, sameOut, diffOut );
333 BOOST_CHECK( j.contains(
"output_filename" ) );
334 BOOST_CHECK_EQUAL( j[
"output_filename"].get<wxString>(), wxString( wxS(
"diff-out.json" ) ) );
335 BOOST_CHECK( !j.contains(
"output" ) );
348 CLI_FIXTURES fixtures( dir );
350 const std::vector<CLI_CASE> cases = {
351 { wxS(
"pcb" ), wxS(
"diff" ), fixtures.pcbA, fixtures.pcbB },
352 { wxS(
"sch" ), wxS(
"diff" ), fixtures.schA, fixtures.schB },
353 { wxS(
"fp" ), wxS(
"diff" ), fixtures.fpA, fixtures.fpB },
354 { wxS(
"sym" ), wxS(
"diff" ), fixtures.symA, fixtures.symB },
357 for(
const CLI_CASE& c : cases )
361 expectInvalidExit( wxS(
"invalid format" ),
362 runCli( { c.commandGroup, c.commandName, c.refPath, c.refPath, wxS(
"--format" ),
366 expectInvalidExit( wxS(
"svg requires output" ),
367 runCli( { c.commandGroup, c.commandName, c.refPath, c.refPath, wxS(
"--format" ),
371 expectInvalidExit( wxS(
"png requires output" ),
372 runCli( { c.commandGroup, c.commandName, c.refPath, c.refPath, wxS(
"--format" ),
383 CLI_FIXTURES fixtures( dir );
385 const std::vector<CLI_CASE> cases = {
386 { wxS(
"pcb" ), wxS(
"diff" ), fixtures.pcbA, fixtures.pcbB },
387 { wxS(
"sch" ), wxS(
"diff" ), fixtures.schA, fixtures.schB },
388 { wxS(
"fp" ), wxS(
"diff" ), fixtures.fpA, fixtures.fpB },
389 { wxS(
"sym" ), wxS(
"diff" ), fixtures.symA, fixtures.symB },
392 for(
const CLI_CASE& c : cases )
394 expectVisualDifference( dir, c, wxS(
"svg" ) );
395 expectVisualDifference( dir, c, wxS(
"png" ) );
#define QA_KICAD_CLI_PATH
Job: diff two PCB files end-to-end via PCB_DIFFER.
void SetConfiguredOutputPath(const wxString &aPath)
Sets the configured output path for the job, this path is always saved to file.
wxString GetConfiguredOutputPath() const
Returns the configured output path for the job.
virtual void ToJson(nlohmann::json &j) const
std::string GetTestDataRootDir()
static PGM_BASE * process
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_AUTO_TEST_SUITE(CadstarPartParser)
BOOST_AUTO_TEST_CASE(DiffJobSerializesSingleOutputKey)
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
BOOST_TEST_CONTEXT("Test Clearance")
wxString result
Test unit parsing edge cases and error handling.
BOOST_CHECK_EQUAL(result, "25.4")