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 m_settingsManager( true /* headless */ )
78 {
79 }
80
81 std::string generate_uuid();
82 bool SaveBoardToFile( BOARD* board, const wxString& filename );
83 void loadBoardAndVerifyInitialExclusions( const wxString& aBoardNameStem, int aExpectedInitialExclusions );
85 int createAndVerifyAdditionalUnconnectedExclusions( int aAdditionalExclusions, int aInitialExclusions );
86 void runDrcOnBoard();
87 void saveBoardAndProjectToTempFiles( const wxString& aBoardNameStem, FileCleaner& aCleaner,
88 wxString& aTempBoardFullPath, wxString& aTempProjectFullPath,
89 wxString& aTempBoardStemName );
90 void reloadBoardAndVerifyExclusions( const wxString& aTempBoardStemName, int aExpectedExclusions );
91
92
94 std::unique_ptr<BOARD> m_board;
95};
96
98{
101 {
102 }
103};
104
105
107{
110 {
111 m_board = std::make_unique<BOARD>();
112 }
113};
114
116{
117 boost::uuids::uuid uuid = boost::uuids::random_generator()();
118 return boost::uuids::to_string( uuid );
119}
120
121void DRC_BASE_FIXTURE::loadBoardAndVerifyInitialExclusions( const wxString& aBoardNameStem,
122 int aExpectedInitialExclusions )
123{
124 KI_TEST::LoadBoard( m_settingsManager, aBoardNameStem, m_board );
125 BOOST_REQUIRE_MESSAGE( m_board,
126 "Could not load board " + aBoardNameStem ); // Ensure board loaded from test data directory
127 PROJECT* pcb_project = m_board->GetProject();
128 BOOST_REQUIRE_MESSAGE( pcb_project, "Get project pointer after initial loading." );
129
130 // Board test file comes with initial exclusions, check if they are preserved after loading
131 const BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings();
132 size_t initialExclusionsCount = bds.m_DrcExclusions.size();
133 size_t initialExclusionsCommentsCount = bds.m_DrcExclusionComments.size();
134 BOOST_TEST_MESSAGE( "Initial DRC exclusions count: " << initialExclusionsCount );
135 BOOST_CHECK_EQUAL( initialExclusionsCount, (size_t) aExpectedInitialExclusions );
136 BOOST_TEST_MESSAGE( "Initial DRC exclusion comments count: " << initialExclusionsCommentsCount );
137 BOOST_CHECK_EQUAL( initialExclusionsCommentsCount, (size_t) aExpectedInitialExclusions );
138}
139
141{
142 const BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings();
143 std::vector<PCB_MARKER*> markers;
144 for( const wxString exclusion : bds.m_DrcExclusions )
145 {
146 PCB_MARKER* marker = PCB_MARKER::DeserializeFromString( exclusion );
147 if( marker )
148 {
149 wxString comment = bds.m_DrcExclusionComments.at( exclusion );
150 marker->SetExcluded( true, comment );
151 markers.push_back( marker );
152 m_board->Add( marker );
153 }
154 }
155 size_t actualExclusionsCount = bds.m_DrcExclusions.size();
156 size_t initialExclusionsCount = markers.size();
157 BOOST_CHECK_EQUAL( actualExclusionsCount, initialExclusionsCount );
158 BOOST_TEST_MESSAGE( std::string( "Actual DRC exclusions count: " ) + std::to_string( actualExclusionsCount )
159 + " after adding initial markers." );
160}
161
163 int aInitialExclusions )
164{
165 for( int i = 0; i < aAdditionalExclusions; ++i )
166 {
167 std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_UNCONNECTED_ITEMS );
168 wxString id1 = wxString( generate_uuid() );
169 wxString id2 = wxString( generate_uuid() );
170 drcItem->SetItems( KIID( id1 ), KIID( id2 ) );
171
172 PCB_MARKER* marker = new PCB_MARKER( drcItem, VECTOR2I( 1000 * i, 1000 * i ) );
173 m_board->Add( marker );
174
175 // Exclude odd-numbered markers
176 if( i % 2 == 1 )
177 {
178 marker->SetExcluded( true, wxString::Format( "Exclusion %d", i ) );
179 }
180 }
181
182 // Store the new exclusion markers in the board
183 m_board->RecordDRCExclusions();
184
185 // Verify the number of exclusions after adding unconnected items
186 const BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings();
187 const int expectedExclusions =
188 aInitialExclusions + aAdditionalExclusions / 2; // Only odd-numbered markers are excluded
189 size_t newActualExclusionsCount = bds.m_DrcExclusions.size();
190 BOOST_TEST_MESSAGE( std::string( "New actual DRC exclusions count: " ) + std::to_string( newActualExclusionsCount )
191 + " after adding unconnected items." );
192 BOOST_CHECK_EQUAL( newActualExclusionsCount, (size_t) expectedExclusions );
193 return expectedExclusions;
194}
195
197{
198 BOOST_TEST_MESSAGE( "Running DRC on board." );
199 BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings();
200 bds.m_DRCEngine->InitEngine( wxFileName() );
201 m_board->RecordDRCExclusions();
202 bool runDRC = true;
203 bool runDRCOnAllLayers = true;
204 bds.m_DRCEngine->RunTests( EDA_UNITS::MM, runDRC, runDRCOnAllLayers );
205 m_board->ResolveDRCExclusions( false );
206 BOOST_TEST_MESSAGE( "DRC done." );
207}
208
209void DRC_BASE_FIXTURE::saveBoardAndProjectToTempFiles( const wxString& aBoardNameStem, FileCleaner& aCleaner,
210 wxString& aTempBoardFullPath, wxString& aTempProjectFullPath,
211 wxString& aTempBoardStemName )
212{
213 wxString tempPrefix = "tmp_test_drc_";
214 aTempBoardStemName = tempPrefix + aBoardNameStem.ToStdString();
215 aTempBoardFullPath = KI_TEST::GetPcbnewTestDataDir() + aTempBoardStemName + ".kicad_pcb";
216 aCleaner.AddFile( aTempBoardFullPath );
217 wxString tempProjectStemName = tempPrefix + aBoardNameStem.ToStdString();
218 aTempProjectFullPath = KI_TEST::GetPcbnewTestDataDir() + aTempBoardStemName + ".kicad_pro";
219 aCleaner.AddFile( aTempProjectFullPath );
220
221 bool boardSaved = SaveBoardToFile( m_board->GetBoard(), aTempBoardFullPath );
222 BOOST_REQUIRE_MESSAGE( boardSaved, "Save board to temporary file: " << aTempBoardFullPath );
223
224 m_settingsManager.SaveProjectAs( aTempProjectFullPath, m_board->GetProject() );
225 BOOST_REQUIRE_MESSAGE( wxFileName::Exists( aTempProjectFullPath ),
226 "Save project to temporary file: " << aTempProjectFullPath );
227}
228
229void DRC_BASE_FIXTURE::reloadBoardAndVerifyExclusions( const wxString& aTempBoardStemName, int aExpectedExclusions )
230{
231 // clear the current board to ensure a fresh load
232 m_board.reset();
233
234 KI_TEST::LoadBoard( m_settingsManager, aTempBoardStemName, m_board );
235 BOOST_REQUIRE_MESSAGE( m_board, "Could not load board from tempfile:"
236 + aTempBoardStemName ); // Ensure board loaded from test data directory
237 PROJECT* pcb_project = m_board->GetProject();
238 BOOST_REQUIRE_MESSAGE( pcb_project, "Get project pointer after initial loading." );
239
240 BOARD_DESIGN_SETTINGS& reloaded_bds = m_board->GetDesignSettings();
241 size_t reloadedExclusionsCount = reloaded_bds.m_DrcExclusions.size();
242 BOOST_TEST_MESSAGE( "Reloaded DRC exclusions count: " << reloadedExclusionsCount );
243 BOOST_CHECK_EQUAL( reloadedExclusionsCount, aExpectedExclusions );
244}
245
246bool DRC_BASE_FIXTURE::SaveBoardToFile( BOARD* board, const wxString& filename )
247{
248 try
249 {
251 pi->SaveBoard( filename, board, nullptr );
252 return true;
253 }
254 catch( const IO_ERROR& error )
255 {
256 BOOST_TEST_MESSAGE( wxString::Format( "Save board to %s: %s", filename, error.What() ) );
257 return false;
258 }
259}
261{
262 // Test that unconnected item exclusions are not lost after multiple DRC runs.
263 // This test is expected to fail if the bug (issue17429) is present.
264
265 std::vector<std::pair<wxString, int>> tests = {
266 { "issue17429", 10 }, // board name stem, expected initial exclusions
267 };
268
269 const int NUM_DRC_RUNS = 2;
270
271 for( const std::pair<wxString, int>& test_params : tests )
272 {
273 wxString boardNameStem = test_params.first;
274 int expectedInitialExclusions = test_params.second;
275
276 loadBoardAndVerifyInitialExclusions( boardNameStem, expectedInitialExclusions );
277 createAndVerifyInitialExclusionMarkers();
278 const int additionalExclusions = 5;
279 int expectedExclusions =
280 createAndVerifyAdditionalUnconnectedExclusions( additionalExclusions, expectedInitialExclusions );
281
282 runDrcOnBoard();
283
284 const BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings();
285 BOOST_TEST_MESSAGE( std::string( "DRC exclusions count after DRC run: " ) + std::to_string( expectedExclusions )
286 + " after adding unconnected items." );
287 BOOST_CHECK_EQUAL( bds.m_DrcExclusions.size(), expectedExclusions );
288 }
289}
290
291BOOST_FIXTURE_TEST_CASE( DRCUnconnectedItemsExclusionsSaveLoad, DRC_REGRESSION_TEST_FIXTURE )
292{
293 namespace fs = std::filesystem;
294
295 // Test that unconnected item exclusions are not lost during save/load.
296 // This test is expected to fail if the bug (issue17429) is present.
297
298 std::vector<std::pair<wxString, int>> tests = {
299 { "issue17429", 10 }, // board name stem, expected initial exclusions
300 };
301
302
303 for( const std::pair<wxString, int>& test_params : tests )
304 {
305 FileCleaner tempFileCleaner;
306 wxString boardNameStem = test_params.first;
307 int expectedInitialExclusions = test_params.second;
308
309 loadBoardAndVerifyInitialExclusions( boardNameStem, expectedInitialExclusions );
310
311 wxString tempBoardFullPath, tempProjectFullPath, tempBoardStemName;
312 saveBoardAndProjectToTempFiles( boardNameStem, tempFileCleaner, tempBoardFullPath, tempProjectFullPath,
313 tempBoardStemName );
314
315 createAndVerifyInitialExclusionMarkers();
316
317 const int additionalExclusions = 5;
318 int expectedExclusions =
319 createAndVerifyAdditionalUnconnectedExclusions( additionalExclusions, expectedInitialExclusions );
320
321 bool boardSaved = SaveBoardToFile( m_board->GetBoard(), tempBoardFullPath );
322 BOOST_REQUIRE_MESSAGE( boardSaved, "Save board to temporary file: " << tempBoardFullPath );
323
324 m_settingsManager.SaveProjectAs( tempProjectFullPath, m_board->GetProject() );
325 BOOST_REQUIRE_MESSAGE( wxFileName::Exists( tempProjectFullPath ),
326 "Save project to temporary file: " << tempProjectFullPath );
327
328 reloadBoardAndVerifyExclusions( tempBoardStemName, expectedExclusions );
329 }
330}
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:317
static std::shared_ptr< DRC_ITEM > Create(int aErrorCode)
Constructs a DRC_ITEM for the given error code.
Definition: drc_item.cpp:393
Hold an error message and may be used when throwing exceptions containing meaningful error messages.
Definition: ki_exception.h:77
virtual const wxString What() const
A composite of Problem() and Where()
Definition: exceptions.cpp:30
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.
Definition: pcb_io_mgr.cpp:68
@ KICAD_SEXP
S-expression Pcbnew file format.
Definition: pcb_io_mgr.h:58
static PCB_MARKER * DeserializeFromString(const wxString &data)
Definition: pcb_marker.cpp:160
Container for project specific data.
Definition: project.h:65
void SaveProjectAs(const wxString &aFullPath, PROJECT *aProject=nullptr)
Set the currently loaded project path and saves it (pointers remain valid).
@ DRCE_UNCONNECTED_ITEMS
Definition: drc_item.h:39
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_CHECK_EQUAL(ret, c.m_exp_result)
BOOST_FIXTURE_TEST_CASE(DRCUnconnectedExclusionsLoss, DRC_UNCONNECTED_SAVE_FIXTURE)
BOOST_TEST_MESSAGE("Polyline has "<< chain.PointCount()<< " points")
VECTOR2< int32_t > VECTOR2I
Definition: vector2d.h:695