KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_drc_unconnected_items_exclusion_loss.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, see AUTHORS.txt for contributors.
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, you may find one here:
18 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
19 * or you may search the http://www.gnu.org website for the version 2 license,
20 * or you may write to the Free Software Foundation, Inc.,
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
22 */
23
24#include <filesystem>
25#include <iostream>
26#include <string>
27
29#include <board.h>
30#include <boost/test/unit_test.hpp>
31#include <boost/uuid/uuid_generators.hpp>
32#include <boost/uuid/uuid_io.hpp>
33#include <boost/uuid/uuid.hpp>
34#include <drc/drc_item.h>
35#include <footprint.h>
36#include <pad.h>
37#include <pcb_edit_frame.h>
39#include <pcb_io/pcb_io_mgr.h>
40#include <pcb_io/pcb_io.h>
41#include <pcb_marker.h>
42#include <pcb_track.h>
45#include <project.h>
49#include <tool/tool_manager.h>
50#include <wx/string.h>
51
53{
54 std::vector<wxString> m_files_to_delete;
55
56 FileCleaner() = default;
58 {
59 for( const auto& f_path : m_files_to_delete )
60 {
61 if( wxFileName::Exists( f_path ) )
62 {
63 if( !wxRemoveFile( f_path ) )
64 {
65 BOOST_TEST_MESSAGE( "Warning: Failed to delete temporary file " << f_path );
66 }
67 }
68 }
69 }
70
71 void AddFile( const wxString& f_path ) { m_files_to_delete.push_back( f_path ); }
72};
73
75{
77 {
78 }
79
80 std::string generate_uuid();
81 bool SaveBoardToFile( BOARD* board, const wxString& filename );
82 void loadBoardAndVerifyInitialExclusions( const wxString& aBoardNameStem, int aExpectedInitialExclusions );
84 int createAndVerifyAdditionalUnconnectedExclusions( int aAdditionalExclusions, int aInitialExclusions );
85 void runDrcOnBoard();
86 void saveBoardAndProjectToTempFiles( const wxString& aBoardNameStem, FileCleaner& aCleaner,
87 wxString& aTempBoardFullPath, wxString& aTempProjectFullPath,
88 wxString& aTempBoardStemName );
89 void reloadBoardAndVerifyExclusions( const wxString& aTempBoardStemName, int aExpectedExclusions );
90
91
93 std::unique_ptr<BOARD> m_board;
94};
95
97{
102};
103
104
106{
109 {
110 m_board = std::make_unique<BOARD>();
111 }
112};
113
115{
116 boost::uuids::uuid uuid = boost::uuids::random_generator()();
117 return boost::uuids::to_string( uuid );
118}
119
120void DRC_BASE_FIXTURE::loadBoardAndVerifyInitialExclusions( const wxString& aBoardNameStem,
121 int aExpectedInitialExclusions )
122{
123 KI_TEST::LoadBoard( m_settingsManager, aBoardNameStem, m_board );
124 BOOST_REQUIRE_MESSAGE( m_board,
125 "Could not load board " + aBoardNameStem ); // Ensure board loaded from test data directory
126 PROJECT* pcb_project = m_board->GetProject();
127 BOOST_REQUIRE_MESSAGE( pcb_project, "Get project pointer after initial loading." );
128
129 // Board test file comes with initial exclusions, check if they are preserved after loading
130 const BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings();
131 size_t initialExclusionsCount = bds.m_DrcExclusions.size();
132 size_t initialExclusionsCommentsCount = bds.m_DrcExclusionComments.size();
133 BOOST_TEST_MESSAGE( "Initial DRC exclusions count: " << initialExclusionsCount );
134 BOOST_CHECK_EQUAL( initialExclusionsCount, (size_t) aExpectedInitialExclusions );
135 BOOST_TEST_MESSAGE( "Initial DRC exclusion comments count: " << initialExclusionsCommentsCount );
136 BOOST_CHECK_EQUAL( initialExclusionsCommentsCount, (size_t) aExpectedInitialExclusions );
137}
138
140{
141 const BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings();
142 std::vector<PCB_MARKER*> markers;
143 for( const wxString exclusion : bds.m_DrcExclusions )
144 {
145 PCB_MARKER* marker = PCB_MARKER::DeserializeFromString( exclusion );
146 if( marker )
147 {
148 wxString comment = bds.m_DrcExclusionComments.at( exclusion );
149 marker->SetExcluded( true, comment );
150 markers.push_back( marker );
151 m_board->Add( marker );
152 }
153 }
154 size_t actualExclusionsCount = bds.m_DrcExclusions.size();
155 size_t initialExclusionsCount = markers.size();
156 BOOST_CHECK_EQUAL( actualExclusionsCount, initialExclusionsCount );
157 BOOST_TEST_MESSAGE( std::string( "Actual DRC exclusions count: " ) + std::to_string( actualExclusionsCount )
158 + " after adding initial markers." );
159}
160
162 int aInitialExclusions )
163{
164 for( int i = 0; i < aAdditionalExclusions; ++i )
165 {
166 std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_UNCONNECTED_ITEMS );
167 wxString id1 = wxString( generate_uuid() );
168 wxString id2 = wxString( generate_uuid() );
169 drcItem->SetItems( KIID( id1 ), KIID( id2 ) );
170
171 PCB_MARKER* marker = new PCB_MARKER( drcItem, VECTOR2I( 1000 * i, 1000 * i ) );
172 m_board->Add( marker );
173
174 // Exclude odd-numbered markers
175 if( i % 2 == 1 )
176 {
177 marker->SetExcluded( true, wxString::Format( "Exclusion %d", i ) );
178 }
179 }
180
181 // Store the new exclusion markers in the board
182 m_board->RecordDRCExclusions();
183
184 // Verify the number of exclusions after adding unconnected items
185 const BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings();
186 const int expectedExclusions =
187 aInitialExclusions + aAdditionalExclusions / 2; // Only odd-numbered markers are excluded
188 size_t newActualExclusionsCount = bds.m_DrcExclusions.size();
189 BOOST_TEST_MESSAGE( std::string( "New actual DRC exclusions count: " ) + std::to_string( newActualExclusionsCount )
190 + " after adding unconnected items." );
191 BOOST_CHECK_EQUAL( newActualExclusionsCount, (size_t) expectedExclusions );
192 return expectedExclusions;
193}
194
196{
197 BOOST_TEST_MESSAGE( "Running DRC on board." );
198 BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings();
199 bds.m_DRCEngine->InitEngine( wxFileName() );
200 m_board->RecordDRCExclusions();
201 bool runDRC = true;
202 bool runDRCOnAllLayers = true;
203 bds.m_DRCEngine->RunTests( EDA_UNITS::MM, runDRC, runDRCOnAllLayers );
204 m_board->ResolveDRCExclusions( false );
205 BOOST_TEST_MESSAGE( "DRC done." );
206}
207
208void DRC_BASE_FIXTURE::saveBoardAndProjectToTempFiles( const wxString& aBoardNameStem, FileCleaner& aCleaner,
209 wxString& aTempBoardFullPath, wxString& aTempProjectFullPath,
210 wxString& aTempBoardStemName )
211{
212 wxString tempPrefix = "tmp_test_drc_";
213 aTempBoardStemName = tempPrefix + aBoardNameStem.ToStdString();
214 aTempBoardFullPath = KI_TEST::GetPcbnewTestDataDir() + aTempBoardStemName + ".kicad_pcb";
215 aCleaner.AddFile( aTempBoardFullPath );
216 wxString tempProjectStemName = tempPrefix + aBoardNameStem.ToStdString();
217 aTempProjectFullPath = KI_TEST::GetPcbnewTestDataDir() + aTempBoardStemName + ".kicad_pro";
218 aCleaner.AddFile( aTempProjectFullPath );
219
220 bool boardSaved = SaveBoardToFile( m_board->GetBoard(), aTempBoardFullPath );
221 BOOST_REQUIRE_MESSAGE( boardSaved, "Save board to temporary file: " << aTempBoardFullPath );
222
223 m_settingsManager.SaveProjectAs( aTempProjectFullPath, m_board->GetProject() );
224 BOOST_REQUIRE_MESSAGE( wxFileName::Exists( aTempProjectFullPath ),
225 "Save project to temporary file: " << aTempProjectFullPath );
226}
227
228void DRC_BASE_FIXTURE::reloadBoardAndVerifyExclusions( const wxString& aTempBoardStemName, int aExpectedExclusions )
229{
230 // clear the current board to ensure a fresh load
231 m_board.reset();
232
233 KI_TEST::LoadBoard( m_settingsManager, aTempBoardStemName, m_board );
234 BOOST_REQUIRE_MESSAGE( m_board, "Could not load board from tempfile:"
235 + aTempBoardStemName ); // Ensure board loaded from test data directory
236 PROJECT* pcb_project = m_board->GetProject();
237 BOOST_REQUIRE_MESSAGE( pcb_project, "Get project pointer after initial loading." );
238
239 BOARD_DESIGN_SETTINGS& reloaded_bds = m_board->GetDesignSettings();
240 size_t reloadedExclusionsCount = reloaded_bds.m_DrcExclusions.size();
241 BOOST_TEST_MESSAGE( "Reloaded DRC exclusions count: " << reloadedExclusionsCount );
242 BOOST_CHECK_EQUAL( reloadedExclusionsCount, aExpectedExclusions );
243}
244
245bool DRC_BASE_FIXTURE::SaveBoardToFile( BOARD* board, const wxString& filename )
246{
247 try
248 {
250 pi->SaveBoard( filename, board, nullptr );
251 return true;
252 }
253 catch( const IO_ERROR& error )
254 {
255 BOOST_TEST_MESSAGE( wxString::Format( "Save board to %s: %s", filename, error.What() ) );
256 return false;
257 }
258}
260{
261 // Test that unconnected item exclusions are not lost after multiple DRC runs.
262 // This test is expected to fail if the bug (issue17429) is present.
263
264 std::vector<std::pair<wxString, int>> tests = {
265 { "issue17429", 10 }, // board name stem, expected initial exclusions
266 };
267
268 const int NUM_DRC_RUNS = 2;
269
270 for( const std::pair<wxString, int>& test_params : tests )
271 {
272 wxString boardNameStem = test_params.first;
273 int expectedInitialExclusions = test_params.second;
274
275 loadBoardAndVerifyInitialExclusions( boardNameStem, expectedInitialExclusions );
276 createAndVerifyInitialExclusionMarkers();
277 const int additionalExclusions = 5;
278 int expectedExclusions =
279 createAndVerifyAdditionalUnconnectedExclusions( additionalExclusions, expectedInitialExclusions );
280
281 runDrcOnBoard();
282
283 const BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings();
284 BOOST_TEST_MESSAGE( std::string( "DRC exclusions count after DRC run: " ) + std::to_string( expectedExclusions )
285 + " after adding unconnected items." );
286 BOOST_CHECK_EQUAL( bds.m_DrcExclusions.size(), expectedExclusions );
287 }
288}
289
290BOOST_FIXTURE_TEST_CASE( DRCUnconnectedItemsExclusionsSaveLoad, DRC_REGRESSION_TEST_FIXTURE )
291{
292 namespace fs = std::filesystem;
293
294 // Test that unconnected item exclusions are not lost during save/load.
295 // This test is expected to fail if the bug (issue17429) is present.
296
297 std::vector<std::pair<wxString, int>> tests = {
298 { "issue17429", 10 }, // board name stem, expected initial exclusions
299 };
300
301
302 for( const std::pair<wxString, int>& test_params : tests )
303 {
304 FileCleaner tempFileCleaner;
305 wxString boardNameStem = test_params.first;
306 int expectedInitialExclusions = test_params.second;
307
308 loadBoardAndVerifyInitialExclusions( boardNameStem, expectedInitialExclusions );
309
310 wxString tempBoardFullPath, tempProjectFullPath, tempBoardStemName;
311 saveBoardAndProjectToTempFiles( boardNameStem, tempFileCleaner, tempBoardFullPath, tempProjectFullPath,
312 tempBoardStemName );
313
314 createAndVerifyInitialExclusionMarkers();
315
316 const int additionalExclusions = 5;
317 int expectedExclusions =
318 createAndVerifyAdditionalUnconnectedExclusions( additionalExclusions, expectedInitialExclusions );
319
320 bool boardSaved = SaveBoardToFile( m_board->GetBoard(), tempBoardFullPath );
321 BOOST_REQUIRE_MESSAGE( boardSaved, "Save board to temporary file: " << tempBoardFullPath );
322
323 m_settingsManager.SaveProjectAs( tempProjectFullPath, m_board->GetProject() );
324 BOOST_REQUIRE_MESSAGE( wxFileName::Exists( tempProjectFullPath ),
325 "Save project to temporary file: " << tempProjectFullPath );
326
327 reloadBoardAndVerifyExclusions( tempBoardStemName, expectedExclusions );
328 }
329}
General utilities for PCB file IO for QA programs.
Container for design settings for a BOARD object.
std::map< wxString, wxString > m_DrcExclusionComments
std::shared_ptr< DRC_ENGINE > m_DRCEngine
std::set< wxString > m_DrcExclusions
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:322
void RunTests(EDA_UNITS aUnits, bool aReportAllTrackErrors, bool aTestFootprints, BOARD_COMMIT *aCommit=nullptr)
Run the DRC tests.
void InitEngine(const wxFileName &aRulePath)
Initialize the DRC engine.
static std::shared_ptr< DRC_ITEM > Create(int aErrorCode)
Constructs a DRC_ITEM for the given error code.
Definition drc_item.cpp:384
Hold an error message and may be used when throwing exceptions containing meaningful error messages.
virtual const wxString What() const
A composite of Problem() and Where()
Definition kiid.h:49
void SetExcluded(bool aExcluded, const wxString &aComment=wxEmptyString)
Definition marker_base.h:94
static PCB_IO * PluginFind(PCB_FILE_T aFileType)
Return a #PLUGIN which the caller can use to import, export, save, or load design documents.
@ KICAD_SEXP
S-expression Pcbnew file format.
Definition pcb_io_mgr.h:58
static PCB_MARKER * DeserializeFromString(const wxString &data)
Container for project specific data.
Definition project.h:66
@ DRCE_UNCONNECTED_ITEMS
Definition drc_item.h:40
std::unique_ptr< T > IO_RELEASER
Helper to hold and release an IO_BASE object when exceptions are thrown.
Definition io_mgr.h:33
std::string GetPcbnewTestDataDir()
Utility which returns a path to the data directory where the test board files are stored.
void LoadBoard(SETTINGS_MANAGER &aSettingsManager, const wxString &aRelPath, std::unique_ptr< BOARD > &aBoard)
void saveBoardAndProjectToTempFiles(const wxString &aBoardNameStem, FileCleaner &aCleaner, wxString &aTempBoardFullPath, wxString &aTempProjectFullPath, wxString &aTempBoardStemName)
int createAndVerifyAdditionalUnconnectedExclusions(int aAdditionalExclusions, int aInitialExclusions)
void reloadBoardAndVerifyExclusions(const wxString &aTempBoardStemName, int aExpectedExclusions)
void loadBoardAndVerifyInitialExclusions(const wxString &aBoardNameStem, int aExpectedInitialExclusions)
bool SaveBoardToFile(BOARD *board, const wxString &filename)
void AddFile(const wxString &f_path)
std::vector< wxString > m_files_to_delete
FileCleaner()=default
BOOST_FIXTURE_TEST_CASE(DRCUnconnectedExclusionsLoss, DRC_UNCONNECTED_SAVE_FIXTURE)
BOOST_TEST_MESSAGE("Polyline has "<< chain.PointCount()<< " points")
BOOST_CHECK_EQUAL(result, "25.4")
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:695