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 <drc/drc_engine.h>
30#include <board.h>
31#include <boost/test/unit_test.hpp>
32#include <boost/uuid/uuid_generators.hpp>
33#include <boost/uuid/uuid_io.hpp>
34#include <boost/uuid/uuid.hpp>
35#include <drc/drc_engine.h>
36#include <drc/drc_item.h>
37#include <footprint.h>
38#include <pad.h>
39#include <pcb_edit_frame.h>
41#include <pcb_io/pcb_io_mgr.h>
42#include <pcb_io/pcb_io.h>
43#include <pcb_marker.h>
44#include <pcb_track.h>
47#include <project.h>
51#include <tool/tool_manager.h>
52#include <wx/string.h>
53
55{
56 std::vector<wxString> m_files_to_delete;
57
58 FileCleaner() = default;
60 {
61 for( const auto& f_path : m_files_to_delete )
62 {
63 if( wxFileName::Exists( f_path ) )
64 {
65 if( !wxRemoveFile( f_path ) )
66 {
67 BOOST_TEST_MESSAGE( "Warning: Failed to delete temporary file " << f_path );
68 }
69 }
70 }
71 }
72
73 void AddFile( const wxString& f_path ) { m_files_to_delete.push_back( f_path ); }
74};
75
77{
79 {
80 }
81
82 std::string generate_uuid();
83 bool SaveBoardToFile( BOARD* board, const wxString& filename );
84 void loadBoardAndVerifyInitialExclusions( const wxString& aBoardNameStem, int aExpectedInitialExclusions );
86 int createAndVerifyAdditionalUnconnectedExclusions( int aAdditionalExclusions, int aInitialExclusions );
87 void runDrcOnBoard();
88 void saveBoardAndProjectToTempFiles( const wxString& aBoardNameStem, FileCleaner& aCleaner,
89 wxString& aTempBoardFullPath, wxString& aTempProjectFullPath,
90 wxString& aTempBoardStemName );
91 void reloadBoardAndVerifyExclusions( const wxString& aTempBoardStemName, int aExpectedExclusions );
92
93
95 std::unique_ptr<BOARD> m_board;
96};
97
99{
104};
105
106
108{
111 {
112 m_board = std::make_unique<BOARD>();
113 }
114};
115
117{
118 boost::uuids::uuid uuid = boost::uuids::random_generator()();
119 return boost::uuids::to_string( uuid );
120}
121
122void DRC_BASE_FIXTURE::loadBoardAndVerifyInitialExclusions( const wxString& aBoardNameStem,
123 int aExpectedInitialExclusions )
124{
125 KI_TEST::LoadBoard( m_settingsManager, aBoardNameStem, m_board );
126 BOOST_REQUIRE_MESSAGE( m_board,
127 "Could not load board " + aBoardNameStem ); // Ensure board loaded from test data directory
128 PROJECT* pcb_project = m_board->GetProject();
129 BOOST_REQUIRE_MESSAGE( pcb_project, "Get project pointer after initial loading." );
130
131 // Board test file comes with initial exclusions, check if they are preserved after loading
132 const BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings();
133 size_t initialExclusionsCount = bds.m_DrcExclusions.size();
134 size_t initialExclusionsCommentsCount = bds.m_DrcExclusionComments.size();
135 BOOST_TEST_MESSAGE( "Initial DRC exclusions count: " << initialExclusionsCount );
136 BOOST_CHECK_EQUAL( initialExclusionsCount, (size_t) aExpectedInitialExclusions );
137 BOOST_TEST_MESSAGE( "Initial DRC exclusion comments count: " << initialExclusionsCommentsCount );
138 BOOST_CHECK_EQUAL( initialExclusionsCommentsCount, (size_t) aExpectedInitialExclusions );
139}
140
142{
143 const BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings();
144 std::vector<PCB_MARKER*> markers;
145 for( const wxString exclusion : bds.m_DrcExclusions )
146 {
147 PCB_MARKER* marker = PCB_MARKER::DeserializeFromString( exclusion );
148 if( marker )
149 {
150 wxString comment = bds.m_DrcExclusionComments.at( exclusion );
151 marker->SetExcluded( true, comment );
152 markers.push_back( marker );
153 m_board->Add( marker );
154 }
155 }
156 size_t actualExclusionsCount = bds.m_DrcExclusions.size();
157 size_t initialExclusionsCount = markers.size();
158 BOOST_CHECK_EQUAL( actualExclusionsCount, initialExclusionsCount );
159 BOOST_TEST_MESSAGE( std::string( "Actual DRC exclusions count: " ) + std::to_string( actualExclusionsCount )
160 + " after adding initial markers." );
161}
162
164 int aInitialExclusions )
165{
166 for( int i = 0; i < aAdditionalExclusions; ++i )
167 {
168 std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_UNCONNECTED_ITEMS );
169 wxString id1 = wxString( generate_uuid() );
170 wxString id2 = wxString( generate_uuid() );
171 drcItem->SetItems( KIID( id1 ), KIID( id2 ) );
172
173 PCB_MARKER* marker = new PCB_MARKER( drcItem, VECTOR2I( 1000 * i, 1000 * i ) );
174 m_board->Add( marker );
175
176 // Exclude odd-numbered markers
177 if( i % 2 == 1 )
178 {
179 marker->SetExcluded( true, wxString::Format( "Exclusion %d", i ) );
180 }
181 }
182
183 // Store the new exclusion markers in the board
184 m_board->RecordDRCExclusions();
185
186 // Verify the number of exclusions after adding unconnected items
187 const BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings();
188 const int expectedExclusions =
189 aInitialExclusions + aAdditionalExclusions / 2; // Only odd-numbered markers are excluded
190 size_t newActualExclusionsCount = bds.m_DrcExclusions.size();
191 BOOST_TEST_MESSAGE( std::string( "New actual DRC exclusions count: " ) + std::to_string( newActualExclusionsCount )
192 + " after adding unconnected items." );
193 BOOST_CHECK_EQUAL( newActualExclusionsCount, (size_t) expectedExclusions );
194 return expectedExclusions;
195}
196
198{
199 BOOST_TEST_MESSAGE( "Running DRC on board." );
200 BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings();
201 bds.m_DRCEngine->InitEngine( wxFileName() );
202 m_board->RecordDRCExclusions();
203 bool runDRC = true;
204 bool runDRCOnAllLayers = true;
205 bds.m_DRCEngine->RunTests( EDA_UNITS::MM, runDRC, runDRCOnAllLayers );
206 m_board->ResolveDRCExclusions( false );
207 BOOST_TEST_MESSAGE( "DRC done." );
208}
209
210void DRC_BASE_FIXTURE::saveBoardAndProjectToTempFiles( const wxString& aBoardNameStem, FileCleaner& aCleaner,
211 wxString& aTempBoardFullPath, wxString& aTempProjectFullPath,
212 wxString& aTempBoardStemName )
213{
214 wxString tempPrefix = "tmp_test_drc_";
215 aTempBoardStemName = tempPrefix + aBoardNameStem.ToStdString();
216 aTempBoardFullPath = KI_TEST::GetPcbnewTestDataDir() + aTempBoardStemName + ".kicad_pcb";
217 aCleaner.AddFile( aTempBoardFullPath );
218 wxString tempProjectStemName = tempPrefix + aBoardNameStem.ToStdString();
219 aTempProjectFullPath = KI_TEST::GetPcbnewTestDataDir() + aTempBoardStemName + ".kicad_pro";
220 aCleaner.AddFile( aTempProjectFullPath );
221
222 bool boardSaved = SaveBoardToFile( m_board->GetBoard(), aTempBoardFullPath );
223 BOOST_REQUIRE_MESSAGE( boardSaved, "Save board to temporary file: " << aTempBoardFullPath );
224
225 m_settingsManager.SaveProjectAs( aTempProjectFullPath, m_board->GetProject() );
226 BOOST_REQUIRE_MESSAGE( wxFileName::Exists( aTempProjectFullPath ),
227 "Save project to temporary file: " << aTempProjectFullPath );
228}
229
230void DRC_BASE_FIXTURE::reloadBoardAndVerifyExclusions( const wxString& aTempBoardStemName, int aExpectedExclusions )
231{
232 // clear the current board to ensure a fresh load
233 m_board.reset();
234
235 KI_TEST::LoadBoard( m_settingsManager, aTempBoardStemName, m_board );
236 BOOST_REQUIRE_MESSAGE( m_board, "Could not load board from tempfile:"
237 + aTempBoardStemName ); // Ensure board loaded from test data directory
238 PROJECT* pcb_project = m_board->GetProject();
239 BOOST_REQUIRE_MESSAGE( pcb_project, "Get project pointer after initial loading." );
240
241 BOARD_DESIGN_SETTINGS& reloaded_bds = m_board->GetDesignSettings();
242 size_t reloadedExclusionsCount = reloaded_bds.m_DrcExclusions.size();
243 BOOST_TEST_MESSAGE( "Reloaded DRC exclusions count: " << reloadedExclusionsCount );
244 BOOST_CHECK_EQUAL( reloadedExclusionsCount, aExpectedExclusions );
245}
246
247bool DRC_BASE_FIXTURE::SaveBoardToFile( BOARD* board, const wxString& filename )
248{
249 try
250 {
252 pi->SaveBoard( filename, board, nullptr );
253 return true;
254 }
255 catch( const IO_ERROR& error )
256 {
257 BOOST_TEST_MESSAGE( wxString::Format( "Save board to %s: %s", filename, error.What() ) );
258 return false;
259 }
260}
262{
263 // Test that unconnected item exclusions are not lost after multiple DRC runs.
264 // This test is expected to fail if the bug (issue17429) is present.
265
266 std::vector<std::pair<wxString, int>> tests = {
267 { "issue17429", 10 }, // board name stem, expected initial exclusions
268 };
269
270 const int NUM_DRC_RUNS = 2;
271
272 for( const std::pair<wxString, int>& test_params : tests )
273 {
274 wxString boardNameStem = test_params.first;
275 int expectedInitialExclusions = test_params.second;
276
277 loadBoardAndVerifyInitialExclusions( boardNameStem, expectedInitialExclusions );
278 createAndVerifyInitialExclusionMarkers();
279 const int additionalExclusions = 5;
280 int expectedExclusions =
281 createAndVerifyAdditionalUnconnectedExclusions( additionalExclusions, expectedInitialExclusions );
282
283 runDrcOnBoard();
284
285 const BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings();
286 BOOST_TEST_MESSAGE( std::string( "DRC exclusions count after DRC run: " ) + std::to_string( expectedExclusions )
287 + " after adding unconnected items." );
288 BOOST_CHECK_EQUAL( bds.m_DrcExclusions.size(), expectedExclusions );
289 }
290}
291
292BOOST_FIXTURE_TEST_CASE( DRCUnconnectedItemsExclusionsSaveLoad, DRC_REGRESSION_TEST_FIXTURE )
293{
294 namespace fs = std::filesystem;
295
296 // Test that unconnected item exclusions are not lost during save/load.
297 // This test is expected to fail if the bug (issue17429) is present.
298
299 std::vector<std::pair<wxString, int>> tests = {
300 { "issue17429", 10 }, // board name stem, expected initial exclusions
301 };
302
303
304 for( const std::pair<wxString, int>& test_params : tests )
305 {
306 FileCleaner tempFileCleaner;
307 wxString boardNameStem = test_params.first;
308 int expectedInitialExclusions = test_params.second;
309
310 loadBoardAndVerifyInitialExclusions( boardNameStem, expectedInitialExclusions );
311
312 wxString tempBoardFullPath, tempProjectFullPath, tempBoardStemName;
313 saveBoardAndProjectToTempFiles( boardNameStem, tempFileCleaner, tempBoardFullPath, tempProjectFullPath,
314 tempBoardStemName );
315
316 createAndVerifyInitialExclusionMarkers();
317
318 const int additionalExclusions = 5;
319 int expectedExclusions =
320 createAndVerifyAdditionalUnconnectedExclusions( additionalExclusions, expectedInitialExclusions );
321
322 bool boardSaved = SaveBoardToFile( m_board->GetBoard(), tempBoardFullPath );
323 BOOST_REQUIRE_MESSAGE( boardSaved, "Save board to temporary file: " << tempBoardFullPath );
324
325 m_settingsManager.SaveProjectAs( tempProjectFullPath, m_board->GetProject() );
326 BOOST_REQUIRE_MESSAGE( wxFileName::Exists( tempProjectFullPath ),
327 "Save project to temporary file: " << tempProjectFullPath );
328
329 reloadBoardAndVerifyExclusions( tempBoardStemName, expectedExclusions );
330 }
331}
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:400
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
@ KICAD_SEXP
S-expression Pcbnew file format.
Definition pcb_io_mgr.h:58
static PCB_IO * FindPlugin(PCB_FILE_T aFileType)
Return a #PLUGIN which the caller can use to import, export, save, or load design documents.
static PCB_MARKER * DeserializeFromString(const wxString &data)
Container for project specific data.
Definition project.h:65
@ 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