diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index 33c98c74bc..5935f720fd 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -415,6 +415,7 @@ set( COMMON_SRCS add_library( common STATIC ${COMMON_SRCS} ) add_dependencies( common version_header ) target_link_libraries( common + bitmaps gal ${Boost_LIBRARIES} ${CURL_LIBRARIES} diff --git a/pcbnew/CMakeLists.txt b/pcbnew/CMakeLists.txt index f20df851ce..a64da3bb5a 100644 --- a/pcbnew/CMakeLists.txt +++ b/pcbnew/CMakeLists.txt @@ -212,7 +212,9 @@ set( PCBNEW_MICROWAVE_SRCS ) set( PCBNEW_DRC_SRCS + drc/courtyard_overlap.cpp drc/drc_marker_factory.cpp + drc/drc_provider.cpp ) set( PCBNEW_CLASS_SRCS diff --git a/pcbnew/drc.cpp b/pcbnew/drc.cpp index 5c4a400b4b..6808636fc3 100644 --- a/pcbnew/drc.cpp +++ b/pcbnew/drc.cpp @@ -58,6 +58,8 @@ #include #include +#include + void DRC::ShowDRCDialog( wxWindow* aParent ) { bool show_dlg_modal = true; @@ -1416,105 +1418,10 @@ bool DRC::doPadToPadsDrc( D_PAD* aRefPad, D_PAD** aStart, D_PAD** aEnd, int x_li } -bool DRC::doFootprintOverlappingDrc() +void DRC::doFootprintOverlappingDrc() { - // Detects missing (or malformed) footprint courtyard, - // and for footprint with courtyard, courtyards overlap. - wxString msg; - bool success = true; + DRC_COURTYARD_OVERLAP drc_overlap( + m_markerFactory, [&]( MARKER_PCB* aMarker ) { addMarkerToPcb( aMarker ); } ); - // Update courtyard polygons, and test for missing courtyard definition: - for( MODULE* footprint = m_pcb->m_Modules; footprint; footprint = footprint->Next() ) - { - wxPoint pos = footprint->GetPosition(); - bool is_ok = footprint->BuildPolyCourtyard(); - - if( !is_ok && m_pcb->GetDesignSettings().m_ProhibitOverlappingCourtyards ) - { - addMarkerToPcb( m_markerFactory.NewMarker( - pos, footprint, DRCE_MALFORMED_COURTYARD_IN_FOOTPRINT ) ); - success = false; - } - - if( !m_pcb->GetDesignSettings().m_RequireCourtyards ) - continue; - - if( footprint->GetPolyCourtyardFront().OutlineCount() == 0 && - footprint->GetPolyCourtyardBack().OutlineCount() == 0 && - is_ok ) - { - addMarkerToPcb( m_markerFactory.NewMarker( - pos, footprint, DRCE_MISSING_COURTYARD_IN_FOOTPRINT ) ); - success = false; - } - } - - if( !m_pcb->GetDesignSettings().m_ProhibitOverlappingCourtyards ) - return success; - - // Now test for overlapping on top layer: - SHAPE_POLY_SET courtyard; // temporary storage of the courtyard of current footprint - - for( MODULE* footprint = m_pcb->m_Modules; footprint; footprint = footprint->Next() ) - { - if( footprint->GetPolyCourtyardFront().OutlineCount() == 0 ) - continue; // No courtyard defined - - for( MODULE* candidate = footprint->Next(); candidate; candidate = candidate->Next() ) - { - if( candidate->GetPolyCourtyardFront().OutlineCount() == 0 ) - continue; // No courtyard defined - - courtyard.RemoveAllContours(); - courtyard.Append( footprint->GetPolyCourtyardFront() ); - - // Build the common area between footprint and the candidate: - courtyard.BooleanIntersection( candidate->GetPolyCourtyardFront(), - SHAPE_POLY_SET::PM_FAST ); - - // If no overlap, courtyard is empty (no common area). - // Therefore if a common polygon exists, this is a DRC error - if( courtyard.OutlineCount() ) - { - //Overlap between footprint and candidate - VECTOR2I& pos = courtyard.Vertex( 0, 0, -1 ); - addMarkerToPcb( m_markerFactory.NewMarker( wxPoint( pos.x, pos.y ), footprint, - candidate, DRCE_OVERLAPPING_FOOTPRINTS ) ); - success = false; - } - } - } - - // Test for overlapping on bottom layer: - for( MODULE* footprint = m_pcb->m_Modules; footprint; footprint = footprint->Next() ) - { - if( footprint->GetPolyCourtyardBack().OutlineCount() == 0 ) - continue; // No courtyard defined - - for( MODULE* candidate = footprint->Next(); candidate; candidate = candidate->Next() ) - { - if( candidate->GetPolyCourtyardBack().OutlineCount() == 0 ) - continue; // No courtyard defined - - courtyard.RemoveAllContours(); - courtyard.Append( footprint->GetPolyCourtyardBack() ); - - // Build the common area between footprint and the candidate: - courtyard.BooleanIntersection( candidate->GetPolyCourtyardBack(), - SHAPE_POLY_SET::PM_FAST ); - - // If no overlap, courtyard is empty (no common area). - // Therefore if a common polygon exists, this is a DRC error - if( courtyard.OutlineCount() ) - { - //Overlap between footprint and candidate - VECTOR2I& pos = courtyard.Vertex( 0, 0, -1 ); - addMarkerToPcb( m_markerFactory.NewMarker( wxPoint( pos.x, pos.y ), footprint, - candidate, DRCE_OVERLAPPING_FOOTPRINTS ) ); - success = false; - } - } - } - - return success; + drc_overlap.RunDRC( *m_pcb ); } diff --git a/pcbnew/drc.h b/pcbnew/drc.h index 012d135e5c..83c4a810e4 100644 --- a/pcbnew/drc.h +++ b/pcbnew/drc.h @@ -342,10 +342,8 @@ private: /** * Test for footprint courtyard overlaps. - * - * @return bool - false if DRC error or true if OK */ - bool doFootprintOverlappingDrc(); + void doFootprintOverlappingDrc(); //--------------------------------------------------- diff --git a/pcbnew/drc/courtyard_overlap.cpp b/pcbnew/drc/courtyard_overlap.cpp new file mode 100644 index 0000000000..4b383cb171 --- /dev/null +++ b/pcbnew/drc/courtyard_overlap.cpp @@ -0,0 +1,168 @@ +/** + * @file drc_marker_functions.cpp + */ + +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2010 Dick Hollenbeck, dick@softplc.com + * Copyright (C) 2004-2017 Jean-Pierre Charras, jp.charras at wanadoo.fr + * Copyright (C) 2018 KiCad Developers, see AUTHORS.txt for contributors. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + + +#include + +#include +#include + +#include + + +/** + * Flag to enable courtyard DRC debug tracing. + * + * Use "KICAD_DRC_COURTYARD" to enable. + * + * @ingroup trace_env_vars + */ +static const wxChar* DRC_COURTYARD_TRACE = wxT( "KICAD_DRC_COURTYARD" ); + + +DRC_COURTYARD_OVERLAP::DRC_COURTYARD_OVERLAP( + const DRC_MARKER_FACTORY& aMarkerFactory, MARKER_HANDLER aMarkerHandler ) + : DRC_PROVIDER( aMarkerFactory, aMarkerHandler ) +{ +} + + +bool DRC_COURTYARD_OVERLAP::RunDRC( BOARD& aBoard ) const +{ + wxLogTrace( DRC_COURTYARD_TRACE, "Running DRC: Courtyard" ); + + // Detects missing (or malformed) footprint courtyard, + // and for footprint with courtyard, courtyards overlap. + wxString msg; + bool success = true; + + const DRC_MARKER_FACTORY& marker_factory = GetMarkerFactory(); + + // Update courtyard polygons, and test for missing courtyard definition: + for( MODULE* footprint = aBoard.m_Modules; footprint; footprint = footprint->Next() ) + { + wxPoint pos = footprint->GetPosition(); + bool is_ok = footprint->BuildPolyCourtyard(); + + if( !is_ok && aBoard.GetDesignSettings().m_ProhibitOverlappingCourtyards ) + { + auto marker = std::unique_ptr( marker_factory.NewMarker( + pos, footprint, DRCE_MALFORMED_COURTYARD_IN_FOOTPRINT ) ); + HandleMarker( std::move( marker ) ); + success = false; + } + + if( !aBoard.GetDesignSettings().m_RequireCourtyards ) + continue; + + if( footprint->GetPolyCourtyardFront().OutlineCount() == 0 + && footprint->GetPolyCourtyardBack().OutlineCount() == 0 && is_ok ) + { + auto marker = std::unique_ptr( marker_factory.NewMarker( + pos, footprint, DRCE_MISSING_COURTYARD_IN_FOOTPRINT ) ); + HandleMarker( std::move( marker ) ); + success = false; + } + } + + if( !aBoard.GetDesignSettings().m_ProhibitOverlappingCourtyards ) + return success; + + wxLogTrace( DRC_COURTYARD_TRACE, "Checking for courtyard overlap" ); + + // Now test for overlapping on top layer: + SHAPE_POLY_SET courtyard; // temporary storage of the courtyard of current footprint + + for( MODULE* footprint = aBoard.m_Modules; footprint; footprint = footprint->Next() ) + { + if( footprint->GetPolyCourtyardFront().OutlineCount() == 0 ) + continue; // No courtyard defined + + for( MODULE* candidate = footprint->Next(); candidate; candidate = candidate->Next() ) + { + if( candidate->GetPolyCourtyardFront().OutlineCount() == 0 ) + continue; // No courtyard defined + + courtyard.RemoveAllContours(); + courtyard.Append( footprint->GetPolyCourtyardFront() ); + + // Build the common area between footprint and the candidate: + courtyard.BooleanIntersection( + candidate->GetPolyCourtyardFront(), SHAPE_POLY_SET::PM_FAST ); + + // If no overlap, courtyard is empty (no common area). + // Therefore if a common polygon exists, this is a DRC error + if( courtyard.OutlineCount() ) + { + //Overlap between footprint and candidate + VECTOR2I& pos = courtyard.Vertex( 0, 0, -1 ); + auto marker = std::unique_ptr( + marker_factory.NewMarker( wxPoint( pos.x, pos.y ), footprint, candidate, + DRCE_OVERLAPPING_FOOTPRINTS ) ); + HandleMarker( std::move( marker ) ); + success = false; + } + } + } + + // Test for overlapping on bottom layer: + for( MODULE* footprint = aBoard.m_Modules; footprint; footprint = footprint->Next() ) + { + if( footprint->GetPolyCourtyardBack().OutlineCount() == 0 ) + continue; // No courtyard defined + + for( MODULE* candidate = footprint->Next(); candidate; candidate = candidate->Next() ) + { + if( candidate->GetPolyCourtyardBack().OutlineCount() == 0 ) + continue; // No courtyard defined + + courtyard.RemoveAllContours(); + courtyard.Append( footprint->GetPolyCourtyardBack() ); + + // Build the common area between footprint and the candidate: + courtyard.BooleanIntersection( + candidate->GetPolyCourtyardBack(), SHAPE_POLY_SET::PM_FAST ); + + // If no overlap, courtyard is empty (no common area). + // Therefore if a common polygon exists, this is a DRC error + if( courtyard.OutlineCount() ) + { + //Overlap between footprint and candidate + VECTOR2I& pos = courtyard.Vertex( 0, 0, -1 ); + auto marker = std::unique_ptr( + marker_factory.NewMarker( wxPoint( pos.x, pos.y ), footprint, candidate, + DRCE_OVERLAPPING_FOOTPRINTS ) ); + HandleMarker( std::move( marker ) ); + success = false; + } + } + } + + return success; +} diff --git a/pcbnew/drc/courtyard_overlap.h b/pcbnew/drc/courtyard_overlap.h new file mode 100644 index 0000000000..3e54618567 --- /dev/null +++ b/pcbnew/drc/courtyard_overlap.h @@ -0,0 +1,44 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2018 KiCad Developers, see change_log.txt for contributors. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + + +#ifndef DRC_COURTYARD_OVERLAP__H +#define DRC_COURTYARD_OVERLAP__H + +#include + +#include + +/** + * A class that provides the courtyard-based DRC checks. + */ +class DRC_COURTYARD_OVERLAP : public DRC_PROVIDER +{ +public: + DRC_COURTYARD_OVERLAP( + const DRC_MARKER_FACTORY& aMarkerFactory, MARKER_HANDLER aMarkerHandler ); + + bool RunDRC( BOARD& aBoard ) const override; +}; + +#endif // DRC_COURTYARD_OVERLAP__H \ No newline at end of file diff --git a/pcbnew/drc/drc_provider.cpp b/pcbnew/drc/drc_provider.cpp new file mode 100644 index 0000000000..6b7d0c9d62 --- /dev/null +++ b/pcbnew/drc/drc_provider.cpp @@ -0,0 +1,44 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2018 KiCad Developers, see AUTHORS.txt for contributors. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include + + +DRC_PROVIDER::DRC_PROVIDER( const DRC_MARKER_FACTORY& aMarkerMaker, MARKER_HANDLER aMarkerHandler ) + : m_marker_factory( aMarkerMaker ), m_marker_handler( aMarkerHandler ) +{ +} + + +const DRC_MARKER_FACTORY& DRC_PROVIDER::GetMarkerFactory() const +{ + return m_marker_factory; +} + + +void DRC_PROVIDER::HandleMarker( std::unique_ptr aMarker ) const +{ + // The marker hander currently takes a raw pointer, + // but it also assumes ownership + m_marker_handler( aMarker.release() ); +} diff --git a/pcbnew/drc/drc_provider.h b/pcbnew/drc/drc_provider.h new file mode 100644 index 0000000000..65080d0445 --- /dev/null +++ b/pcbnew/drc/drc_provider.h @@ -0,0 +1,84 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2018 KiCad Developers, see change_log.txt for contributors. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + + +#ifndef DRC_PROVIDER__H +#define DRC_PROVIDER__H + +#include +#include + +#include + +#include + + +/** + * Class that represents a DRC "provider" which runs some + * DRC functions over a #BOARD and spits out #PCB_MARKERs as needed. + */ +class DRC_PROVIDER +{ +public: + /** + * A callable that can handle a single generated PCB_MARKER + */ + using MARKER_HANDLER = std::function; + + // This class can be handled by base pointer + virtual ~DRC_PROVIDER() + { + } + + /** + * Runs this provider against the given PCB with + * configured options (if any). + * + * Note: Board is non-const, as some DRC functions modify the board + * (e.g. zone fill or polygon coalescing) + */ + virtual bool RunDRC( BOARD& aBoard ) const = 0; + +protected: + DRC_PROVIDER( const DRC_MARKER_FACTORY& aMarkerMaker, MARKER_HANDLER aMarkerHandler ); + + /** + * Access to the stored reference to a marker constructor + */ + const DRC_MARKER_FACTORY& GetMarkerFactory() const; + + /** + * Pass a given marker to the marker handler + */ + void HandleMarker( std::unique_ptr aMarker ) const; + +private: + + /// A marker generator to make markers in the right context + const DRC_MARKER_FACTORY& m_marker_factory; + + /// The handler for any generated markers + MARKER_HANDLER m_marker_handler; +}; + +#endif // DRC_PROVIDER__H \ No newline at end of file diff --git a/qa/CMakeLists.txt b/qa/CMakeLists.txt index 1c774d8385..1f22f174ab 100644 --- a/qa/CMakeLists.txt +++ b/qa/CMakeLists.txt @@ -14,6 +14,7 @@ endif() # Shared QA helper libraries add_subdirectory( qa_utils ) +add_subdirectory( pcbnew_utils ) add_subdirectory( unit_test_utils ) # Unit tests diff --git a/qa/pcbnew/CMakeLists.txt b/qa/pcbnew/CMakeLists.txt index abaaa9f1f9..e8cca0356c 100644 --- a/qa/pcbnew/CMakeLists.txt +++ b/qa/pcbnew/CMakeLists.txt @@ -19,14 +19,14 @@ # or you may write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA +if( BUILD_GITHUB_PLUGIN ) + set( GITHUB_PLUGIN_LIBRARIES github_plugin ) +endif() add_executable( qa_pcbnew # A single top to load the pcnew kiface # ../../common/single_top.cpp - # The main test entry points - test_module.cpp - # stuff from common due to...units? ../../common/eda_text.cpp @@ -34,42 +34,35 @@ add_executable( qa_pcbnew ../../common/colors.cpp ../../common/observable.cpp + # The main test entry points + test_module.cpp + + # testing utility routines + board_test_utils.cpp + drc/drc_test_utils.cpp + + # test compilation units (start test_) test_graphics_import_mgr.cpp test_pad_naming.cpp + drc/test_drc_courtyard_invalid.cpp + drc/test_drc_courtyard_overlap.cpp + # Older CMakes cannot link OBJECT libraries # https://cmake.org/pipermail/cmake/2013-November/056263.html $ ) -if( BUILD_GITHUB_PLUGIN ) - set( GITHUB_PLUGIN_LIBRARIES github_plugin ) -endif() - -set_source_files_properties( ../common/single_top.cpp pcbnew.cpp PROPERTIES - COMPILE_DEFINITIONS "TOP_FRAME=FRAME_PCB;PGM_DATA_FILE_EXT=\"kicad_pcb\";BUILD_KIWAY_DLL" -) - -include_directories( BEFORE ${INC_BEFORE} ) -include_directories( - ${CMAKE_SOURCE_DIR} - ${CMAKE_SOURCE_DIR}/include - ${CMAKE_SOURCE_DIR}/polygon - ${CMAKE_SOURCE_DIR}/pcbnew - ${CMAKE_SOURCE_DIR}/common - ${CMAKE_SOURCE_DIR}/pcbnew/router - ${CMAKE_SOURCE_DIR}/pcbnew/tools - ${CMAKE_SOURCE_DIR}/pcbnew/dialogs - ${INC_AFTER} -) - target_link_libraries( qa_pcbnew + qa_pcbnew_utils 3d-viewer connectivity pcbcommon pnsrouter pcad2kicadpcb + bitmaps common + pcbcommon legacy_wx polygon bitmaps @@ -86,11 +79,6 @@ target_link_libraries( qa_pcbnew ${PCBNEW_EXTRA_LIBS} # -lrt must follow Boost ) -# we need to pretend to be something to appease the units code -target_compile_definitions( qa_pcbnew - PRIVATE PCBNEW -) - add_test( NAME pcbnew COMMAND qa_pcbnew ) \ No newline at end of file diff --git a/qa/pcbnew/board_test_utils.cpp b/qa/pcbnew/board_test_utils.cpp new file mode 100644 index 0000000000..0aca168e2c --- /dev/null +++ b/qa/pcbnew/board_test_utils.cpp @@ -0,0 +1,53 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2019 KiCad Developers, see AUTHORS.txt for contributors. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include "board_test_utils.h" + +#include + +// For the temp directory logic: can be std::filesystem in C++17 +#include +#include + + +namespace KI_TEST +{ + +BOARD_DUMPER::BOARD_DUMPER() : m_dump_boards( std::getenv( "KICAD_TEST_DUMP_BOARD_FILES" ) ) +{ +} + + +void BOARD_DUMPER::DumpBoardToFile( BOARD& aBoard, const std::string& aName ) const +{ + if( !m_dump_boards ) + return; + + auto path = boost::filesystem::temp_directory_path() / aName; + path += ".kicad_pcb"; + + BOOST_TEST_MESSAGE( "Dumping board file: " << path.string() ); + ::KI_TEST::DumpBoardToFile( aBoard, path.string() ); +} + +} // namespace KI_TEST \ No newline at end of file diff --git a/qa/pcbnew/board_test_utils.h b/qa/pcbnew/board_test_utils.h new file mode 100644 index 0000000000..1cce18371a --- /dev/null +++ b/qa/pcbnew/board_test_utils.h @@ -0,0 +1,63 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2019 KiCad Developers, see AUTHORS.txt for contributors. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +/** + * @file board_test_utils.h + * General utilities for PCB tests + */ + +#ifndef QA_PCBNEW_BOARD_TEST_UTILS__H +#define QA_PCBNEW_BOARD_TEST_UTILS__H + +#include + +class BOARD; +class BOARD_ITEM; + + +namespace KI_TEST +{ +/** + * A helper that contains logic to assist in dumping boards to + * disk depending on some environment variables. + * + * This is useful when setting up or verifying unit tests that work on BOARD + * objects. + * + * To dump files set the KICAD_TEST_DUMP_BOARD_FILES environment variable. + * Files will be written to the system temp directory (/tmp on Linux, or as set + * by $TMP and friends). + */ +class BOARD_DUMPER +{ +public: + BOARD_DUMPER(); + + void DumpBoardToFile( BOARD& aBoard, const std::string& aName ) const; + + const bool m_dump_boards; +}; + +} // namespace KI_TEST + +#endif // QA_PCBNEW_BOARD_TEST_UTILS__H \ No newline at end of file diff --git a/qa/pcbnew/drc/drc_test_utils.cpp b/qa/pcbnew/drc/drc_test_utils.cpp new file mode 100644 index 0000000000..b5455f4297 --- /dev/null +++ b/qa/pcbnew/drc/drc_test_utils.cpp @@ -0,0 +1,46 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2019 KiCad Developers, see AUTHORS.txt for contributors. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include "drc_test_utils.h" + + +std::ostream& operator<<( std::ostream& os, const MARKER_PCB& aMarker ) +{ + const auto& reporter = aMarker.GetReporter(); + os << "MARKER_PCB[\n"; + os << " type=" << reporter.GetErrorCode() << " (" << reporter.GetErrorText() << ")" + << "\n"; + os << "]"; + return os; +} + + +namespace KI_TEST +{ + +bool IsDrcMarkerOfType( const MARKER_PCB& aMarker, int aErrorCode ) +{ + return aMarker.GetReporter().GetErrorCode() == aErrorCode; +} + +} // namespace KI_TEST \ No newline at end of file diff --git a/qa/pcbnew/drc/drc_test_utils.h b/qa/pcbnew/drc/drc_test_utils.h new file mode 100644 index 0000000000..8e1c3428ae --- /dev/null +++ b/qa/pcbnew/drc/drc_test_utils.h @@ -0,0 +1,63 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2019 KiCad Developers, see AUTHORS.txt for contributors. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +/** + * @file drc_test_utils.h + * General utilities for DRC-related PCB tests + */ + +#ifndef QA_PCBNEW_DRC_TEST_UTILS__H +#define QA_PCBNEW_DRC_TEST_UTILS__H + +#include + +#include + +/** + * Define a stream function for logging #MARKER_PCB test assertions. + * + * This has to be in the same namespace as #MARKER_PCB + * + * Note: this assumes there is not a operator<< for this type in the main + * Pcbnew library. If one is introduced there, this one should be removed. + * + * TODO: convert to boost_test_print_type when Boost minver > 1.64. This + * will keep testing logging and application-level operator<< implementations + * separate, as they should be. + */ +std::ostream& operator<<( std::ostream& os, const MARKER_PCB& aMarker ); + + +namespace KI_TEST +{ +/** + * Predicate for testing the type of a DRC marker + * @param aMarker the marker to test + * @param aErrorCode the expected DRC violation code + * @return true if the marker has this code + */ +bool IsDrcMarkerOfType( const MARKER_PCB& aMarker, int aErrorCode ); + +} // namespace KI_TEST + +#endif // QA_PCBNEW_DRC_TEST_UTILS__H \ No newline at end of file diff --git a/qa/pcbnew/drc/test_drc_courtyard_invalid.cpp b/qa/pcbnew/drc/test_drc_courtyard_invalid.cpp new file mode 100644 index 0000000000..4f9be1fc6c --- /dev/null +++ b/qa/pcbnew/drc/test_drc_courtyard_invalid.cpp @@ -0,0 +1,341 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2018 KiCad Developers, see CHANGELOG.TXT for contributors. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include + +#include +#include + +#include +#include + +#include + +#include "../board_test_utils.h" +#include "drc_test_utils.h" + + +struct COURTYARD_TEST_FIXTURE +{ + const KI_TEST::BOARD_DUMPER m_dumper; +}; + + +BOOST_FIXTURE_TEST_SUITE( DrcCourtyardInvalid, COURTYARD_TEST_FIXTURE ) + + +/* + * A simple mock module with a set of courtyard rectangles and some other + * information + */ +struct COURTYARD_INVALID_TEST_MODULE +{ + /// Module Ref-Des (for identifying DRC errors) + std::string m_refdes; + /// List of segments that will be placed on the courtyard + std::vector m_segs; + /// Module position + VECTOR2I m_pos; +}; + + +struct COURTYARD_INVALID_INFO +{ + std::string m_refdes; + + int m_drc_error_code; +}; + + +std::ostream& operator<<( std::ostream& os, const COURTYARD_INVALID_INFO& aInvalid ) +{ + os << "COURTYARD_INVALID_INFO[ " << aInvalid.m_refdes; + os << ", code: " << aInvalid.m_drc_error_code << "]"; + return os; +} + + +struct COURTYARD_INVALID_CASE +{ + std::string m_case_name; + + std::vector m_mods; + + std::vector m_exp_errors; +}; + +// clang-format off +static const std::vector courtyard_invalid_cases = +{ + { + // Empty board has no modules to be invalid + "empty board", + {}, + {}, + }, + { + "single mod, no courtyard", + { + { + "U1", + {}, // Empty courtyard layer + { 0, 0 }, + }, + }, + { // one error: the module has no courtyard + { + "U1", + DRCE_MISSING_COURTYARD_IN_FOOTPRINT, + }, + }, + }, + { + "single mod, unclosed courtyard", + { + { + "U1", + { // Unclosed polygon + { { 0, 0 }, { 0, Millimeter2iu( 10 ) } }, + { { 0, Millimeter2iu( 10 ) }, { Millimeter2iu( 10 ), Millimeter2iu( 10 ) } }, + }, + { 0, 0 }, + }, + }, + { // one error: the module has malformed courtyard + { + "U1", + DRCE_MALFORMED_COURTYARD_IN_FOOTPRINT, + }, + }, + }, + { + "single mod, disjoint courtyard", + { + { + "U1", + { // Unclosed polygon - two disjoint segments + { { 0, 0 }, { 0, Millimeter2iu( 10 ) } }, + { { Millimeter2iu( 10 ), 0 }, { Millimeter2iu( 10 ), Millimeter2iu( 10 ) } }, + }, + { 0, 0 }, + }, + }, + { // one error: the module has malformed courtyard + { + "U1", + DRCE_MALFORMED_COURTYARD_IN_FOOTPRINT, + }, + }, + }, + { + "two mods, one OK, one malformed", + { + { + "U1", + { // Closed polygon - triangle + { + { 0, 0 }, + { 0, Millimeter2iu( 10 ) }, + }, + { + { 0, Millimeter2iu( 10 ) }, + { Millimeter2iu( 10 ), Millimeter2iu( 10 ) } + }, + { + { Millimeter2iu( 10 ), Millimeter2iu( 10 ) }, + { 0, 0 } + }, + }, + { 0, 0 }, + }, + { + "U2", + { // Un-Closed polygon - one seg + { + { 0, 0 }, + { 0, Millimeter2iu( 10 ) }, + }, + }, + { 0, 0 }, + }, + }, + { // one error: the second module has malformed courtyard + { + "U2", + DRCE_MALFORMED_COURTYARD_IN_FOOTPRINT, + }, + }, + }, +}; +// clang-format on + + +/** + * Construct a #MODULE to use in a courtyard test from a #COURTYARD_TEST_MODULE + * definition. + */ +std::unique_ptr MakeInvalidCourtyardTestModule( + BOARD& aBoard, const COURTYARD_INVALID_TEST_MODULE& aMod ) +{ + auto module = std::make_unique( &aBoard ); + + for( const auto& seg : aMod.m_segs ) + { + const PCB_LAYER_ID layer = F_CrtYd; // aRect.m_front ? F_CrtYd : B_CrtYd; + const int width = Millimeter2iu( 0.1 ); + + KI_TEST::DrawSegment( *module, seg, width, layer ); + } + + module->SetReference( aMod.m_refdes ); + + // As of 2019-01-17, this has to go after adding the courtyards, + // or all the poly sets are empty when DRC'd + module->SetPosition( (wxPoint) aMod.m_pos ); + + return module; +} + + +std::unique_ptr MakeBoard( const std::vector& aMods ) +{ + auto board = std::make_unique(); + + for( const auto& mod : aMods ) + { + auto module = MakeInvalidCourtyardTestModule( *board, mod ); + + board->Add( module.release() ); + } + + return board; +} + + +/** + * Get a #BOARD_DESIGN_SETTINGS object that will cause DRC to + * check for courtyard invalidity + */ +static BOARD_DESIGN_SETTINGS GetOverlapCheckDesignSettings() +{ + BOARD_DESIGN_SETTINGS des_settings; + + // do the overlap tests - that's a different test, but if not set, + // the invalid courtyard checks don't run either + des_settings.m_ProhibitOverlappingCourtyards = true; + + // we will also check for missing courtyards here + des_settings.m_RequireCourtyards = true; + + return des_settings; +} + + +/** + * Check if a #MARKER_PCB is described by a particular #COURTYARD_INVALID_INFO + * object. + */ +static bool InvalidMatchesExpected( + BOARD& aBoard, const MARKER_PCB& aMarker, const COURTYARD_INVALID_INFO& aInvalid ) +{ + const DRC_ITEM& reporter = aMarker.GetReporter(); + + const MODULE* item_a = dynamic_cast( reporter.GetMainItem( &aBoard ) ); + + // This one is more than just a mis-match! + if( reporter.HasSecondItem() ) + { + BOOST_WARN_MESSAGE( false, "Expected no auxiliary item for invalid courtyard DRC." ); + return false; + } + + if( item_a->GetReference() != aInvalid.m_refdes ) + { + return false; + } + + if( reporter.GetErrorCode() != aInvalid.m_drc_error_code ) + { + return false; + } + + return true; +} + + +/** + * Check that the produced markers match the expected. This does NOT + * check ordering, as that is not part of the contract of the DRC function. + * + * @param aMarkers list of markers produced by the DRC + * @param aCollisions list of expected collisions + */ +static void CheckInvalidsMatchExpected( BOARD& aBoard, + const std::vector>& aMarkers, + const std::vector& aExpInvalids ) +{ + KI_TEST::CheckUnorderedMatches( aExpInvalids, aMarkers, + [&]( const COURTYARD_INVALID_INFO& aInvalid, + const std::unique_ptr& aMarker ) { + return InvalidMatchesExpected( aBoard, *aMarker, aInvalid ); + } ); +} + + +void DoCourtyardInvalidTest( + const COURTYARD_INVALID_CASE& aCase, const KI_TEST::BOARD_DUMPER& aDumper ) +{ + DRC_MARKER_FACTORY marker_factory; + + auto board = MakeBoard( aCase.m_mods ); + + // Dump if env var set + aDumper.DumpBoardToFile( *board, aCase.m_case_name ); + + board->SetDesignSettings( GetOverlapCheckDesignSettings() ); + + // list of markers to collect + std::vector> markers; + + DRC_COURTYARD_OVERLAP drc_overlap( marker_factory, [&]( MARKER_PCB* aMarker ) { + markers.push_back( std::unique_ptr( aMarker ) ); + } ); + + drc_overlap.RunDRC( *board ); + + CheckInvalidsMatchExpected( *board, markers, aCase.m_exp_errors ); +} + + +BOOST_AUTO_TEST_CASE( InvalidCases ) +{ + for( const auto& c : courtyard_invalid_cases ) + { + BOOST_TEST_CONTEXT( c.m_case_name ) + { + DoCourtyardInvalidTest( c, m_dumper ); + } + } +} + +BOOST_AUTO_TEST_SUITE_END() \ No newline at end of file diff --git a/qa/pcbnew/drc/test_drc_courtyard_overlap.cpp b/qa/pcbnew/drc/test_drc_courtyard_overlap.cpp new file mode 100644 index 0000000000..6b03d32427 --- /dev/null +++ b/qa/pcbnew/drc/test_drc_courtyard_overlap.cpp @@ -0,0 +1,499 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2018 KiCad Developers, see CHANGELOG.TXT for contributors. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include + +#include +#include + +#include +#include + +#include + +#include "../board_test_utils.h" +#include "drc_test_utils.h" + +/** + * Simple definition of a rectangle, can be rounded + */ +struct RECT_DEFINITION +{ + VECTOR2I m_centre; + VECTOR2I m_size; + int m_corner_rad; + + // On front or back layer (the exact layer is context-dependent) + bool m_front; +}; + + +/* + * A simple mock module with a set of courtyard rectangles and some other + * information + */ +struct COURTYARD_TEST_MODULE +{ + std::string m_refdes; + std::vector m_rects; + VECTOR2I m_pos; +}; + + +/* + * Struct holding information about a courtyard collision + */ +struct COURTYARD_COLLISION +{ + // The two colliding parts + std::string m_refdes_a; + std::string m_refdes_b; +}; + + +std::ostream& operator<<( std::ostream& os, const COURTYARD_COLLISION& aColl ) +{ + os << "COURTYARD_COLLISION[ " << aColl.m_refdes_a << " -> " << aColl.m_refdes_b << "]"; + return os; +} + + +/** + * A complete courtyard overlap test case: a name, the board modules list + * and the expected collisions. + */ +struct COURTYARD_OVERLAP_TEST_CASE +{ + std::string m_case_name; + + // The modules in the test case + std::vector m_mods; + + // The expected number of collisions + std::vector m_collisions; +}; + + +/** + * Add a rectangular courtyard outline to a module. + */ +void AddRectCourtyard( MODULE& aMod, const RECT_DEFINITION& aRect ) +{ + const PCB_LAYER_ID layer = aRect.m_front ? F_CrtYd : B_CrtYd; + + const int width = Millimeter2iu( 0.1 ); + + KI_TEST::DrawRect( aMod, aRect.m_centre, aRect.m_size, aRect.m_corner_rad, width, layer ); +} + + +/** + * Construct a #MODULE to use in a courtyard test from a #COURTYARD_TEST_MODULE + * definition. + */ +std::unique_ptr MakeCourtyardTestModule( BOARD& aBoard, const COURTYARD_TEST_MODULE& aMod ) +{ + auto module = std::make_unique( &aBoard ); + + for( const auto& rect : aMod.m_rects ) + { + AddRectCourtyard( *module, rect ); + } + + module->SetReference( aMod.m_refdes ); + + // As of 2019-01-17, this has to go after adding the courtyards, + // or all the poly sets are empty when DRC'd + module->SetPosition( (wxPoint) aMod.m_pos ); + + return module; +} + +/** + * Make a board for courtyard testing. + * + * @param aMods the list of module definitions to add to the board + */ +std::unique_ptr MakeBoard( const std::vector& aMods ) +{ + auto board = std::make_unique(); + + for( const auto& mod : aMods ) + { + auto module = MakeCourtyardTestModule( *board, mod ); + + board->Add( module.release() ); + } + + return board; +} + + +struct COURTYARD_TEST_FIXTURE +{ + const KI_TEST::BOARD_DUMPER m_dumper; +}; + + +BOOST_FIXTURE_TEST_SUITE( DrcCourtyardOverlap, COURTYARD_TEST_FIXTURE ) + +// clang-format off +static std::vector courtyard_cases = { + { + "empty board", + {}, // no modules + {}, // no collisions + }, + { + "single empty mod", + { + { + "U1", + {}, // no courtyard + { 0, 0 }, // at origin + }, + }, + {}, // no collisions + }, + { + // A single module can't overlap itself + "single mod, single courtyard", + { + { + "U1", + { + { + { 0, 0 }, + { Millimeter2iu( 1 ), Millimeter2iu( 1 ) }, + 0, + true, + }, + }, + { 0, 0 }, + }, + }, + {}, // no collisions + }, + { + "two modules, no overlap", + { + { + "U1", + { + { + { 0, 0 }, + { Millimeter2iu( 1 ), Millimeter2iu( 1 ) }, + 0, + true, + }, + }, + { 0, 0 }, + }, + { + "U2", + { + { + { 0, 0 }, + { Millimeter2iu( 1 ), Millimeter2iu( 1 ) }, + 0, + true, + }, + }, + { Millimeter2iu( 3 ), Millimeter2iu( 1 ) }, // One module is far from the other + }, + }, + {}, // no collisions + }, + { + "two modules, touching, no overlap", + { + { + "U1", + { + { + { 0, 0 }, + { Millimeter2iu( 1 ), Millimeter2iu( 1 ) }, + 0, + true, + }, + }, + { 0, 0 }, + }, + { + "U2", + { + { + { 0, 0 }, + { Millimeter2iu( 1 ), Millimeter2iu( 1 ) }, + 0, + true, + }, + }, + { Millimeter2iu( 1 ), Millimeter2iu( 0 ) }, // Just touching + }, + }, + {}, // Touching means not colliding + }, + { + "two modules, overlap", + { + { + "U1", + { + { + { 0, 0 }, + { Millimeter2iu( 1 ), Millimeter2iu( 1 ) }, + 0, + true, + }, + }, + { 0, 0 }, + }, + { + "U2", + { + { + { 0, 0 }, + { Millimeter2iu( 1 ), Millimeter2iu( 1 ) }, + 0, + true, + }, + }, + { Millimeter2iu( 0.5 ), Millimeter2iu( 0 ) }, // Partial overlap + }, + }, + { + { "U1", "U2" }, // These two collide + }, + }, + { + "two modules, overlap, different sides", + { + { + "U1", + { + { + { 0, 0 }, + { Millimeter2iu( 1 ), Millimeter2iu( 1 ) }, + 0, + true, + }, + }, + { 0, 0 }, + }, + { + "U2", + { + { + { 0, 0 }, + { Millimeter2iu( 1 ), Millimeter2iu( 1 ) }, + 0, + false, + }, + }, + { 0, 0 }, // complete overlap + }, + }, + {}, // but on different sides + }, + { + "two modules, multiple courtyards, overlap", + { + { + "U1", + { + { + { 0, 0 }, + { Millimeter2iu( 1 ), Millimeter2iu( 1 ) }, + 0, + true, + }, + { + { Millimeter2iu( 2 ), Millimeter2iu( 0 ) }, + { Millimeter2iu( 1 ), Millimeter2iu( 1 ) }, + 0, + true, + }, + }, + { 0, 0 }, + }, + { + "U2", + { + { + { 0, 0 }, + { Millimeter2iu( 1 ), Millimeter2iu( 1 ) }, + 0, + true, + }, + }, + { 0, 0 }, // complete overlap with one of the others + }, + }, + { + { "U1", "U2" }, + }, + }, + { + // The courtyards do not overlap, but their bounding boxes do + "two modules, no overlap, bbox overlap", + { + { + "U1", + { + { + { 0, 0 }, + { Millimeter2iu( 1 ), Millimeter2iu( 1 ) }, + Millimeter2iu( 0.5 ), + true, + }, + }, + { 0, 0 }, + }, + { + "U2", + { + { + { Millimeter2iu( 0.9 ), Millimeter2iu( 0.9 ) }, + { Millimeter2iu( 1 ), Millimeter2iu( 1 ) }, + Millimeter2iu( 0.5 ), + true, + }, + }, + { 0, 0 }, + }, + }, + {}, + }, +}; +// clang-format on + + +/** + * Check if a #MARKER_PCB is described by a particular #COURTYARD_COLLISION + * object. + */ +static bool CollisionMatchesExpected( + BOARD& aBoard, const MARKER_PCB& aMarker, const COURTYARD_COLLISION& aCollision ) +{ + const DRC_ITEM& reporter = aMarker.GetReporter(); + + const MODULE* item_a = dynamic_cast( reporter.GetMainItem( &aBoard ) ); + const MODULE* item_b = dynamic_cast( reporter.GetAuxiliaryItem( &aBoard ) ); + + // cant' find the items! + if( !item_a || !item_b ) + return false; + + const bool ref_match_aa_bb = ( item_a->GetReference() == aCollision.m_refdes_a ) + && ( item_b->GetReference() == aCollision.m_refdes_b ); + + const bool ref_match_ab_ba = ( item_a->GetReference() == aCollision.m_refdes_b ) + && ( item_b->GetReference() == aCollision.m_refdes_a ); + + // Doesn't matter which way around it is, but both have to match somehow + return ref_match_aa_bb || ref_match_ab_ba; +} + + +/** + * Check that the produced markers match the expected. This does NOT + * check ordering, as that is not part of the contract of the DRC function. + * + * @param aMarkers list of markers produced by the DRC + * @param aCollisions list of expected collisions + */ +static void CheckCollisionsMatchExpected( BOARD& aBoard, + const std::vector>& aMarkers, + const std::vector& aExpCollisions ) +{ + for( const auto& marker : aMarkers ) + { + BOOST_CHECK_PREDICATE( + KI_TEST::IsDrcMarkerOfType, ( *marker )( DRCE_OVERLAPPING_FOOTPRINTS ) ); + } + + KI_TEST::CheckUnorderedMatches( aExpCollisions, aMarkers, + [&]( const COURTYARD_COLLISION& aColl, const std::unique_ptr& aMarker ) { + return CollisionMatchesExpected( aBoard, *aMarker, aColl ); + } ); +} + + +/** + * Get a #BOARD_DESIGN_SETTINGS object that will cause DRC to + * check for courtyard overlaps + */ +static BOARD_DESIGN_SETTINGS GetOverlapCheckDesignSettings() +{ + BOARD_DESIGN_SETTINGS des_settings; + des_settings.m_ProhibitOverlappingCourtyards = true; + + // we might not always have courtyards - that's a separate test + des_settings.m_RequireCourtyards = false; + + return des_settings; +} + + +/** + * Run a single courtyard overlap testcase + * @param aCase The testcase to run. + */ +static void DoCourtyardOverlapTest( + const COURTYARD_OVERLAP_TEST_CASE& aCase, const KI_TEST::BOARD_DUMPER& aDumper ) +{ + DRC_MARKER_FACTORY marker_factory; + + auto board = MakeBoard( aCase.m_mods ); + + // Dump if env var set + aDumper.DumpBoardToFile( *board, aCase.m_case_name ); + + board->SetDesignSettings( GetOverlapCheckDesignSettings() ); + + // list of markers to collect + std::vector> markers; + + DRC_COURTYARD_OVERLAP drc_overlap( marker_factory, [&]( MARKER_PCB* aMarker ) { + markers.push_back( std::unique_ptr( aMarker ) ); + } ); + + drc_overlap.RunDRC( *board ); + + CheckCollisionsMatchExpected( *board, markers, aCase.m_collisions ); +} + + +BOOST_AUTO_TEST_CASE( OverlapCases ) +{ + for( const auto& c : courtyard_cases ) + { + BOOST_TEST_CONTEXT( c.m_case_name ) + { + DoCourtyardOverlapTest( c, m_dumper ); + } + } +} + +BOOST_AUTO_TEST_SUITE_END() \ No newline at end of file diff --git a/qa/pcbnew_utils/CMakeLists.txt b/qa/pcbnew_utils/CMakeLists.txt new file mode 100644 index 0000000000..f67eb70111 --- /dev/null +++ b/qa/pcbnew_utils/CMakeLists.txt @@ -0,0 +1,85 @@ +# This program source code file is part of KiCad, a free EDA CAD application. +# +# Copyright (C) 2019 KiCad Developers, see CHANGELOG.TXT for contributors. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, you may find one here: +# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html +# or you may search the http://www.gnu.org website for the version 2 license, +# or you may write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +# Pcbnew-related auxiliary functions that are useful for QA purposes +# (both unit tests ans utility programs) + +# This is a massive hack in the CMake as I have no idea how to get this to +# link against pcbnew/pcbcommon/etc and still allow a dependency to also link +# THis should really be an add_library() and all the variables instead set using +# target_*() functions. + +set( QA_PCBNEW_UTILS_SRCS + + ${CMAKE_CURRENT_SOURCE_DIR}/board_construction_utils.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/board_file_utils.cpp +) + +add_library( qa_pcbnew_utils STATIC ${QA_PCBNEW_UTILS_SRCS} ) + + +target_include_directories( qa_pcbnew_utils PUBLIC BEFORE ${INC_BEFORE} ) + +target_include_directories( qa_pcbnew_utils PUBLIC + include + + # Paths for pcbnew lib usage (should really be in pcbnew/common + # target_include_directories and made PUBLIC) + ${CMAKE_SOURCE_DIR} + ${CMAKE_SOURCE_DIR}/include + ${CMAKE_SOURCE_DIR}/polygon + ${CMAKE_SOURCE_DIR}/pcbnew + ${CMAKE_SOURCE_DIR}/common + ${CMAKE_SOURCE_DIR}/pcbnew/router + ${CMAKE_SOURCE_DIR}/pcbnew/tools + ${CMAKE_SOURCE_DIR}/pcbnew/dialogs + ${INC_AFTER} +) + +# target_link_libraries( qa_pcbnew_utils PUBLIC +# 3d-viewer +# connectivity +# pcbcommon +# pnsrouter +# pcad2kicadpcb +# bitmaps +# common +# pcbcommon +# legacy_wx +# polygon +# bitmaps +# gal +# qa_utils +# lib_dxf +# idf3 +# unit_test_utils +# ${wxWidgets_LIBRARIES} +# ${GITHUB_PLUGIN_LIBRARIES} +# ${GDI_PLUS_LIBRARIES} +# ${PYTHON_LIBRARIES} +# ${Boost_LIBRARIES} # must follow GITHUB +# ${PCBNEW_EXTRA_LIBS} # -lrt must follow Boost +#) + +# # we need to pretend to be something to appease the units code +target_compile_definitions( qa_pcbnew_utils + PUBLIC PCBNEW +) \ No newline at end of file diff --git a/qa/pcbnew_utils/board_construction_utils.cpp b/qa/pcbnew_utils/board_construction_utils.cpp new file mode 100644 index 0000000000..46e1e43c9f --- /dev/null +++ b/qa/pcbnew_utils/board_construction_utils.cpp @@ -0,0 +1,112 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2019 KiCad Developers, see AUTHORS.txt for contributors. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include + +#include +#include +#include + +#include +#include + + +namespace KI_TEST +{ + +void DrawSegment( MODULE& aMod, const SEG& aSeg, int aWidth, PCB_LAYER_ID aLayer ) +{ + auto seg = std::make_unique( &aMod, STROKE_T::S_SEGMENT ); + + seg->SetStart0( (wxPoint) aSeg.A ); + seg->SetEnd0( (wxPoint) aSeg.B ); + + seg->SetWidth( aWidth ); + seg->SetLayer( aLayer ); + + aMod.GraphicalItemsList().PushBack( seg.release() ); +} + + +void DrawPolyline( + MODULE& aMod, const std::vector& aPts, int aWidth, PCB_LAYER_ID aLayer ) +{ + for( unsigned i = 0; i < aPts.size() - 1; ++i ) + { + DrawSegment( aMod, { aPts[i], aPts[i + 1] }, aWidth, aLayer ); + } +} + + +void DrawArc( MODULE& aMod, const VECTOR2I& aCentre, const VECTOR2I& aStart, double aAngle, + int aWidth, PCB_LAYER_ID aLayer ) +{ + auto seg = std::make_unique( &aMod, STROKE_T::S_ARC ); + + seg->SetStart0( (wxPoint) aCentre ); + seg->SetEnd0( (wxPoint) aStart ); + seg->SetAngle( aAngle * 10 ); + + seg->SetWidth( aWidth ); + seg->SetLayer( aLayer ); + + aMod.GraphicalItemsList().PushBack( seg.release() ); +} + + +void DrawRect( MODULE& aMod, const VECTOR2I& aPos, const VECTOR2I& aSize, int aRadius, int aWidth, + PCB_LAYER_ID aLayer ) +{ + const auto x_r = aPos.x + aSize.x / 2; + const auto x_l = aPos.x - aSize.x / 2; + const auto y_t = aPos.y + aSize.y / 2; + const auto y_b = aPos.y - aSize.y / 2; + + const auto xin_r = x_r - aRadius; + const auto xin_l = x_l + aRadius; + const auto yin_t = y_t - aRadius; + const auto yin_b = y_b + aRadius; + + // If non-zero (could be zero if it's a stadium shape) + if( xin_l != xin_r ) + { + DrawSegment( aMod, { { xin_l, y_t }, { xin_r, y_t } }, aWidth, aLayer ); + DrawSegment( aMod, { { xin_l, y_b }, { xin_r, y_b } }, aWidth, aLayer ); + } + + if( yin_b != yin_t ) + { + DrawSegment( aMod, { { x_l, yin_b }, { x_l, yin_t } }, aWidth, aLayer ); + DrawSegment( aMod, { { x_r, yin_b }, { x_r, yin_t } }, aWidth, aLayer ); + } + + if( aRadius > 0 ) + { + DrawArc( aMod, { xin_r, yin_t }, { x_r, yin_t }, 90, aWidth, aLayer ); + DrawArc( aMod, { xin_l, yin_t }, { xin_l, y_t }, 90, aWidth, aLayer ); + DrawArc( aMod, { xin_l, yin_b }, { x_l, yin_b }, 90, aWidth, aLayer ); + DrawArc( aMod, { xin_r, yin_b }, { xin_r, y_b }, 90, aWidth, aLayer ); + } +} + +} // namespace KI_TEST diff --git a/qa/pcbnew_utils/board_file_utils.cpp b/qa/pcbnew_utils/board_file_utils.cpp new file mode 100644 index 0000000000..c278c22edb --- /dev/null +++ b/qa/pcbnew_utils/board_file_utils.cpp @@ -0,0 +1,53 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2019 KiCad Developers, see AUTHORS.txt for contributors. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include + +// For PCB parsing +#include +#include +#include + +#include + +namespace KI_TEST +{ + +void DumpBoardToFile( BOARD& board, const std::string& aFilename ) +{ + PCB_IO io; + io.Save( aFilename, &board ); +} + + +std::unique_ptr ReadBoardItemFromFile( const std::string& aFilename ) +{ + FILE_LINE_READER reader( aFilename ); + + PCB_PARSER parser; + parser.SetLineReader( &reader ); + + return std::unique_ptr( parser.Parse() ); +} + +} // namespace KI_TEST \ No newline at end of file diff --git a/qa/pcbnew_utils/include/pcbnew_utils/board_construction_utils.h b/qa/pcbnew_utils/include/pcbnew_utils/board_construction_utils.h new file mode 100644 index 0000000000..fa0229e75f --- /dev/null +++ b/qa/pcbnew_utils/include/pcbnew_utils/board_construction_utils.h @@ -0,0 +1,89 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2019 KiCad Developers, see AUTHORS.txt for contributors. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +/** + * @file board_construction_utils.h + * Construction utilities for PCB tests + */ + +#ifndef QA_PCBNEW_BOARD_CONSTRUCTION_UTILS__H +#define QA_PCBNEW_BOARD_CONSTRUCTION_UTILS__H + +#include + +#include +#include + +class MODULE; +class SEG; + + +namespace KI_TEST +{ + +/** + * Draw a segment in the given module. + * @param aMod The module to add the segment to + * @param aSeg The segment geometry + * @param aWidth The width of the segment + * @param aLayer The layer to draw on + */ +void DrawSegment( MODULE& aMod, const SEG& aSeg, int aWidth, PCB_LAYER_ID aLayer ); + +/** + * Draw a polyline - a set of linked segments + * @param aMod The module to add the segment to + * @param aPts The polyline points + * @param aWidth The width of the segments + * @param aLayer The layer to draw on + */ +void DrawPolyline( + MODULE& aMod, const std::vector& aPts, int aWidth, PCB_LAYER_ID aLayer ); + +/** + * Draw an arc on a module + * @param aMod The module to add the segment to + * @param aCentre The arc centre + * @param aStart The arc start point + * @param aAngle The arc angle (degrees, NOT deci-degrees) + * @param aWidth The width of the arc segment + * @param aLayer The layer to draw on + */ +void DrawArc( MODULE& aMod, const VECTOR2I& aCentre, const VECTOR2I& aStart, double aAngle, + int aWidth, PCB_LAYER_ID aLayer ); + +/** + * Draw a rectangle on a module + * @param aMod The module to add the rectangle to + * @param aPos Rectangle centre point + * @param aSize Rectangle size (x, y) + * @param aRadius Corner radius (0 for a normal rect) + * @param aWidth Line width + * @param aLayer Layer to draw on + */ +void DrawRect( MODULE& aMod, const VECTOR2I& aPos, const VECTOR2I& aSize, int aRadius, int aWidth, + PCB_LAYER_ID aLayer ); + +} // namespace KI_TEST + +#endif // QA_PCBNEW_BOARD_CONSTRUCTION_UTILS__H \ No newline at end of file diff --git a/qa/pcbnew_utils/include/pcbnew_utils/board_file_utils.h b/qa/pcbnew_utils/include/pcbnew_utils/board_file_utils.h new file mode 100644 index 0000000000..8543487fd0 --- /dev/null +++ b/qa/pcbnew_utils/include/pcbnew_utils/board_file_utils.h @@ -0,0 +1,67 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2019 KiCad Developers, see AUTHORS.txt for contributors. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + + +#ifndef QA_PCBNEW_UTILS_BOARD_FILE_UTILS__H +#define QA_PCBNEW_UTILS_BOARD_FILE_UTILS__H + +#include +#include + +class BOARD; +class BOARD_ITEM; + +/** + * @file board_file_utils.h + * General utilities for PCB file IO for QA programs + */ +namespace KI_TEST +{ +/** + * Utility function to simply write a Board out to a file. + * + * Helps debug tests and utility programs by making it easy to quickly + * write to disk without directly using the PCB_IO API. + * + * Note: The aBoard param is non-const because PCB_IO::Save demands it + * and I am not confident a const_cast will be a true assurance. + * + * @param aBoard the board to write out + * @param aFilename the filename to write to + */ +void DumpBoardToFile( BOARD& aBoard, const std::string& aFilename ); + +/** + * Utility function to read a #BOARD_ITEM (probably a #MODULE or a #BOARD) + * from a file. + * + * Helps when writing tests or utilities that can be fed an external file. + * + * @param aFilename the file to read in + * @returns a new #BOARD_ITEM, which is nullptr if the read or parse failed. + */ +std::unique_ptr ReadBoardItemFromFile( const std::string& aFilename ); + +} // namespace KI_TEST + +#endif // QA_PCBNEW_UTILS_BOARD_FILE_UTILS__H \ No newline at end of file diff --git a/qa/unit_test_utils/CMakeLists.txt b/qa/unit_test_utils/CMakeLists.txt index 3fbfa05b86..8ad23bb172 100644 --- a/qa/unit_test_utils/CMakeLists.txt +++ b/qa/unit_test_utils/CMakeLists.txt @@ -25,9 +25,9 @@ # Code that is useful for QA purposes outside of the unit-testing context # belongs in qa_utils. -find_package(Boost COMPONENTS unit_test_framework REQUIRED) +find_package( Boost COMPONENTS unit_test_framework filesystem REQUIRED ) -set(SRCS +set( SRCS unit_test_utils.cpp ) @@ -35,6 +35,8 @@ add_library( unit_test_utils STATIC ${SRCS} ) target_link_libraries( unit_test_utils PUBLIC ${Boost_UNIT_TEST_FRAMEWORK_LIBRARY} + ${Boost_FILESYSTEM_LIBRARY} + ${Boost_SYSTEM_LIBRARY} ) target_include_directories( unit_test_utils PUBLIC diff --git a/qa/unit_test_utils/include/unit_test_utils/unit_test_utils.h b/qa/unit_test_utils/include/unit_test_utils/unit_test_utils.h index 160d01d55a..e6523ea96c 100644 --- a/qa/unit_test_utils/include/unit_test_utils/unit_test_utils.h +++ b/qa/unit_test_utils/include/unit_test_utils/unit_test_utils.h @@ -28,6 +28,7 @@ #include #include +#include /** * If HAVE_EXPECTED_FAILURES is defined, this means that @@ -77,4 +78,84 @@ #endif +namespace KI_TEST +{ +/** + * Check that a container of "found" objects matches a container of "expected" + * objects. This means that: + * + * * Every "expected" object is "found" + * * Every "found" object is "expected" + * + * This is a very generic function: all you need are two containers of any type + * and a function to check if a given "found" object corresponds to a given + * "expected object". Conditions: + * + * * The expected object type needs operator<< + * * The expected object container does not contain multiple references to the + * same object. + * * Identical values are also can't be present as the predicate can't tell which + * one to match up. + * + * Not needed: + * + * * Equality or ordering operators + * + * This is a slightly more complex way of doing it that, say, sorting both + * lists and checking element-by-element matches. However, it can tell you + * exactly which objects are problematic, as well as a simple go/no-go. + * + *@param aExpected a container of "expected" items, usually from a test case + *@param aMatched a container of "found" items, usually the result of some + * routine under test + *@param aMatchPredicate a predicate that determines if a given "found" object + * matches a given "expected" object. + */ +template using EXP_OBJ = typename EXP_CONT::value_type; +template using FOUND_OBJ = typename FOUND_CONT::value_type; +template +using MATCH_PRED = std::function; +template +void CheckUnorderedMatches( + const EXP_CONT& aExpected, const FOUND_CONT& aFound, MATCH_PRED aMatchPredicate ) +{ + using EXP_OBJ = typename EXP_CONT::value_type; + + // set of object we've already found + std::set matched; + + // fill the set of object that match + for( const auto& found : aFound ) + { + for( const auto& expected : aExpected ) + { + if( aMatchPredicate( expected, found ) ) + { + matched.insert( &expected ); + break; + } + } + } + + // first check every expected object was "found" + for( const EXP_OBJ& exp : aExpected ) + { + BOOST_CHECK_MESSAGE( matched.count( &exp ) > 0, "Expected item was not found. Expected: \n" + << exp ); + } + + // check every "found" object was expected + for( const EXP_OBJ* found : matched ) + { + const bool was_expected = + std::find_if( aExpected.begin(), aExpected.end(), + [found]( const EXP_OBJ& aObj ) { return &aObj == found; } ) + != aExpected.end(); + + BOOST_CHECK_MESSAGE( was_expected, "Found item was not expected. Found: \n" << *found ); + } +} + +} // namespace KI_TEST + #endif // UNIT_TEST_UTILS__H \ No newline at end of file