KiCad PCB EDA Suite
Loading...
Searching...
No Matches
downgrade_golden_utils.h
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, see AUTHORS.txt for contributors.
5 * SPDX-License-Identifier: GPL-3.0-or-later
6 */
7
8#pragma once
9
10#include <cmath>
11#include <fstream>
12#include <iterator>
13#include <stdexcept>
14#include <string>
15#include <vector>
16
17#include <sexpr/sexpr_parser.h>
18
19namespace KI_TEST
20{
21
22inline std::string ReadGoldenText( const std::string& aPath )
23{
24 std::ifstream file( aPath );
25
26 if( !file.is_open() )
27 throw std::runtime_error( "Cannot read downgrade fixture: " + aPath );
28
29 return { std::istreambuf_iterator<char>( file ), std::istreambuf_iterator<char>() };
30}
31
32
33inline std::string GoldenNodeHead( const SEXPR::SEXPR& aNode )
34{
35 if( aNode.IsList() && aNode.GetNumberOfChildren() && aNode.GetChild( 0 )->IsSymbol() )
36 return aNode.GetChild( 0 )->GetSymbol();
37
38 return {};
39}
40
41
42inline const SEXPR::SEXPR* FindGoldenChild( const SEXPR::SEXPR& aNode, const std::string& aHead )
43{
44 if( aNode.IsList() )
45 {
46 for( const SEXPR::SEXPR* child : *aNode.GetChildren() )
47 {
48 if( GoldenNodeHead( *child ) == aHead )
49 return child;
50 }
51 }
52
53 return nullptr;
54}
55
56
57inline double SerializedImageScale( const std::string& aContent, const std::string& aUuid )
58{
59 SEXPR::PARSER parser;
60 auto root = parser.Parse( aContent );
61
62 if( root && root->IsList() )
63 {
64 for( const SEXPR::SEXPR* child : *root->GetChildren() )
65 {
66 if( GoldenNodeHead( *child ) != "image" )
67 continue;
68
69 const SEXPR::SEXPR* uuid = FindGoldenChild( *child, "uuid" );
70
71 if( !uuid || uuid->GetNumberOfChildren() != 2 || !uuid->GetChild( 1 )->IsString()
72 || uuid->GetChild( 1 )->GetString() != aUuid )
73 continue;
74
75 const SEXPR::SEXPR* scale = FindGoldenChild( *child, "scale" );
76
77 if( scale && scale->GetNumberOfChildren() == 2 )
78 {
79 const SEXPR::SEXPR& value = *scale->GetChild( 1 );
80
81 if( value.IsInteger() )
82 return value.GetLongInteger();
83
84 if( value.IsDouble() )
85 return value.GetDouble();
86 }
87
88 throw std::runtime_error( "Image has no numeric scale: " + aUuid );
89 }
90 }
91
92 throw std::runtime_error( "Serialized image is missing: " + aUuid );
93}
94
95
96// Whitelist non-design differences ONLY in their actual contexts. Keep version stamps,
97// UUIDs, nets, coordinates, drawing order, attributes, and every other setting observable.
98inline bool IgnoreGoldenNode( const SEXPR::SEXPR& aNode, const std::string& aParent )
99{
100 if( !aNode.IsList() )
101 return false;
102
103 const std::string head = GoldenNodeHead( aNode );
104
105 // Every design and library root carries the writing application's version. A downgraded file
106 // is still written by the current build, so this never matches the target's own stamp.
107 if( ( aParent == "kicad_pcb" || aParent == "kicad_sch" || aParent == "footprint" || aParent == "kicad_symbol_lib" )
108 && head == "generator_version" )
109 {
110 return true;
111 }
112
113 // The shared modern plot-settings formatter adds these even to frozen PCB writers.
114 // The target releases have no PNG plot output and harmlessly skip these settings.
115 if( aParent != "pcbplotparams" )
116 return false;
117
118 if( head == "pngdpi" || head == "pngantialias" )
119 return true;
120
121 // Removed modern options that KiCad 9 itself writes at their default values. Missing
122 // entries reload to those SAME defaults. Never ignore a non-default reference value.
123 if( aNode.GetNumberOfChildren() != 2 )
124 return false;
125
126 const SEXPR::SEXPR& value = *aNode.GetChild( 1 );
127
128 if( head == "plotinvisibletext" )
129 return value.IsSymbol() && value.GetSymbol() == "no";
130
131 if( !value.IsInteger() && !value.IsDouble() )
132 return false;
133
134 const double number = value.IsInteger() ? value.GetLongInteger() : value.GetDouble();
135 return ( head == "hpglpennumber" && number == 1 ) || ( head == "hpglpenspeed" && number == 20 )
136 || ( head == "hpglpendiameter" && number == 15 );
137}
138
139
140inline std::string GoldenSexprDifference( const SEXPR::SEXPR& aExpected, const SEXPR::SEXPR& aActual,
141 const std::string& aPath = "", const std::string& aParent = "",
142 size_t aIndex = 0 )
143{
144 if( aExpected.IsList() && aActual.IsList() )
145 {
146 const std::string head = GoldenNodeHead( aExpected );
147 const std::string path = aPath + "/" + head;
148 std::vector<const SEXPR::SEXPR*> expected;
149 std::vector<const SEXPR::SEXPR*> actual;
150
151 for( const SEXPR::SEXPR* child : *aExpected.GetChildren() )
152 {
153 if( !IgnoreGoldenNode( *child, head ) )
154 expected.push_back( child );
155 }
156
157 for( const SEXPR::SEXPR* child : *aActual.GetChildren() )
158 {
159 if( !IgnoreGoldenNode( *child, GoldenNodeHead( aActual ) ) )
160 actual.push_back( child );
161 }
162
163 if( expected.size() != actual.size() )
164 return path + ": child count " + std::to_string( expected.size() )
165 + " != " + std::to_string( actual.size() );
166
167 for( size_t i = 0; i < expected.size(); ++i )
168 {
169 std::string difference =
170 GoldenSexprDifference( *expected[i], *actual[i], path + "[" + std::to_string( i ) + "]", head, i );
171
172 if( !difference.empty() )
173 return difference;
174 }
175
176 return {};
177 }
178
179 if( ( aExpected.IsInteger() || aExpected.IsDouble() ) && ( aActual.IsInteger() || aActual.IsDouble() ) )
180 {
181 const double expected = aExpected.IsInteger() ? aExpected.GetLongInteger() : aExpected.GetDouble();
182 const double actual = aActual.IsInteger() ? aActual.GetLongInteger() : aActual.GetDouble();
183
184 // The affine-to-legacy coordinate round trip can round by one internal PCB unit.
185 // Permit at most two nanometres in XY coordinates, NOT sizes, angles or settings.
186 const bool xy =
187 ( aParent == "at" || aParent == "xy" || aParent == "start" || aParent == "end" || aParent == "mid" )
188 && ( aIndex == 1 || aIndex == 2 );
189
190 if( expected == actual || ( xy && std::abs( expected - actual ) <= 0.000002001 ) )
191 return {};
192 }
193 else if( aExpected.AsString() == aActual.AsString() )
194 {
195 return {};
196 }
197
198 return aPath + ": expected " + aExpected.AsString() + ", got " + aActual.AsString();
199}
200
201
202inline std::string GoldenFileDifference( const std::string& aExpected, const std::string& aActual )
203{
204 SEXPR::PARSER parser;
205 auto expected = parser.Parse( ReadGoldenText( aExpected ) );
206 auto actual = parser.Parse( ReadGoldenText( aActual ) );
207
208 if( !expected || !actual )
209 throw std::runtime_error( "Empty downgrade golden or output" );
210
212}
213
214} // namespace KI_TEST
std::unique_ptr< SEXPR > Parse(const std::string &aString)
SEXPR_VECTOR const * GetChildren() const
Definition sexpr.cpp:40
bool IsInteger() const
Definition sexpr.h:53
std::string const & GetSymbol() const
Definition sexpr.cpp:130
std::string AsString(size_t aLevel=0) const
Definition sexpr.cpp:153
bool IsString() const
Definition sexpr.h:51
size_t GetNumberOfChildren() const
Definition sexpr.cpp:72
bool IsList() const
Definition sexpr.h:49
int64_t GetLongInteger() const
Definition sexpr.cpp:97
bool IsDouble() const
Definition sexpr.h:52
bool IsSymbol() const
Definition sexpr.h:50
std::string const & GetString() const
Definition sexpr.cpp:82
SEXPR * GetChild(size_t aIndex) const
Definition sexpr.cpp:50
double GetDouble() const
Definition sexpr.cpp:107
Test utilities for timestamps.
std::string GoldenNodeHead(const SEXPR::SEXPR &aNode)
std::string GoldenFileDifference(const std::string &aExpected, const std::string &aActual)
std::string ReadGoldenText(const std::string &aPath)
bool IgnoreGoldenNode(const SEXPR::SEXPR &aNode, const std::string &aParent)
std::string GoldenSexprDifference(const SEXPR::SEXPR &aExpected, const SEXPR::SEXPR &aActual, const std::string &aPath="", const std::string &aParent="", size_t aIndex=0)
const SEXPR::SEXPR * FindGoldenChild(const SEXPR::SEXPR &aNode, const std::string &aHead)
double SerializedImageScale(const std::string &aContent, const std::string &aUuid)
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:437
const int scale
std::string path
VECTOR3I expected(15, 30, 45)
int actual