DRC: Break out courtyard overlap function

Introduce the concept of a DRC_PROVIDER which allows
to separate the various DRC functions to their own
areas. This allows, amongst other things, a slimmer core
DRC class, and allows DRC functions to be separately testable.

The courtyard DRCs (overlap, missing and malformed)
are the first victims, so instrumentation can be added to this function.

Add some unit tests on this DRC function, as well a few re-usable PCB-based
utility functions in a library (qa_pcbnew_utils) that could be shared between
unit tests and other utilities.
This commit is contained in:
John Beard 2019-01-11 22:03:45 +00:00 committed by Wayne Stambaugh
parent e6edc1b670
commit 8297ab24e4
23 changed files with 1924 additions and 133 deletions

View File

@ -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}

View File

@ -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

View File

@ -58,6 +58,8 @@
#include <geometry/shape_segment.h>
#include <geometry/shape_arc.h>
#include <drc/courtyard_overlap.h>
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 );
}

View File

@ -342,10 +342,8 @@ private:
/**
* Test for footprint courtyard overlaps.
*
* @return bool - false if DRC error or true if OK
*/
bool doFootprintOverlappingDrc();
void doFootprintOverlappingDrc();
//-----<single tests>----------------------------------------------

View File

@ -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 <drc/courtyard_overlap.h>
#include <class_module.h>
#include <drc.h>
#include <drc/drc_marker_factory.h>
/**
* 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_PCB>( 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_PCB>( 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_PCB>(
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_PCB>(
marker_factory.NewMarker( wxPoint( pos.x, pos.y ), footprint, candidate,
DRCE_OVERLAPPING_FOOTPRINTS ) );
HandleMarker( std::move( marker ) );
success = false;
}
}
}
return success;
}

View File

@ -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 <class_board.h>
#include <drc/drc_provider.h>
/**
* 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

View File

@ -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/drc_provider.h>
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<MARKER_PCB> aMarker ) const
{
// The marker hander currently takes a raw pointer,
// but it also assumes ownership
m_marker_handler( aMarker.release() );
}

84
pcbnew/drc/drc_provider.h Normal file
View File

@ -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 <class_board.h>
#include <class_marker_pcb.h>
#include <drc/drc_marker_factory.h>
#include <functional>
/**
* 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<void( MARKER_PCB* )>;
// 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<MARKER_PCB> 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

View File

@ -14,6 +14,7 @@ endif()
# Shared QA helper libraries
add_subdirectory( qa_utils )
add_subdirectory( pcbnew_utils )
add_subdirectory( unit_test_utils )
# Unit tests

View File

@ -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
$<TARGET_OBJECTS:pcbnew_kiface_objects>
)
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
)

View File

@ -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 <pcbnew_utils/board_file_utils.h>
// For the temp directory logic: can be std::filesystem in C++17
#include <boost/filesystem.hpp>
#include <boost/test/unit_test.hpp>
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

View File

@ -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 <string>
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

View File

@ -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

View File

@ -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 <iostream>
#include <class_marker_pcb.h>
/**
* 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

View File

@ -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 <unit_test_utils/unit_test_utils.h>
#include <pcbnew_utils/board_construction_utils.h>
#include <pcbnew_utils/board_file_utils.h>
#include <class_module.h>
#include <drc.h>
#include <drc/courtyard_overlap.h>
#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<SEG> 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<COURTYARD_INVALID_TEST_MODULE> m_mods;
std::vector<COURTYARD_INVALID_INFO> m_exp_errors;
};
// clang-format off
static const std::vector<COURTYARD_INVALID_CASE> 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<MODULE> MakeInvalidCourtyardTestModule(
BOARD& aBoard, const COURTYARD_INVALID_TEST_MODULE& aMod )
{
auto module = std::make_unique<MODULE>( &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<BOARD> MakeBoard( const std::vector<COURTYARD_INVALID_TEST_MODULE>& aMods )
{
auto board = std::make_unique<BOARD>();
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<MODULE*>( 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<std::unique_ptr<MARKER_PCB>>& aMarkers,
const std::vector<COURTYARD_INVALID_INFO>& aExpInvalids )
{
KI_TEST::CheckUnorderedMatches( aExpInvalids, aMarkers,
[&]( const COURTYARD_INVALID_INFO& aInvalid,
const std::unique_ptr<MARKER_PCB>& 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<std::unique_ptr<MARKER_PCB>> markers;
DRC_COURTYARD_OVERLAP drc_overlap( marker_factory, [&]( MARKER_PCB* aMarker ) {
markers.push_back( std::unique_ptr<MARKER_PCB>( 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()

View File

@ -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 <unit_test_utils/unit_test_utils.h>
#include <pcbnew_utils/board_construction_utils.h>
#include <pcbnew_utils/board_file_utils.h>
#include <class_module.h>
#include <drc.h>
#include <drc/courtyard_overlap.h>
#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<RECT_DEFINITION> 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<COURTYARD_TEST_MODULE> m_mods;
// The expected number of collisions
std::vector<COURTYARD_COLLISION> 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<MODULE> MakeCourtyardTestModule( BOARD& aBoard, const COURTYARD_TEST_MODULE& aMod )
{
auto module = std::make_unique<MODULE>( &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<BOARD> MakeBoard( const std::vector<COURTYARD_TEST_MODULE>& aMods )
{
auto board = std::make_unique<BOARD>();
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_OVERLAP_TEST_CASE> 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<MODULE*>( reporter.GetMainItem( &aBoard ) );
const MODULE* item_b = dynamic_cast<MODULE*>( 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<std::unique_ptr<MARKER_PCB>>& aMarkers,
const std::vector<COURTYARD_COLLISION>& 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<MARKER_PCB>& 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<std::unique_ptr<MARKER_PCB>> markers;
DRC_COURTYARD_OVERLAP drc_overlap( marker_factory, [&]( MARKER_PCB* aMarker ) {
markers.push_back( std::unique_ptr<MARKER_PCB>( 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()

View File

@ -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
)

View File

@ -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 <pcbnew_utils/board_construction_utils.h>
#include <class_edge_mod.h>
#include <class_module.h>
#include <drc.h>
#include <geometry/seg.h>
#include <math/vector2d.h>
namespace KI_TEST
{
void DrawSegment( MODULE& aMod, const SEG& aSeg, int aWidth, PCB_LAYER_ID aLayer )
{
auto seg = std::make_unique<EDGE_MODULE>( &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<VECTOR2I>& 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<EDGE_MODULE>( &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

View File

@ -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 <pcbnew_utils/board_file_utils.h>
// For PCB parsing
#include <kicad_plugin.h>
#include <pcb_parser.h>
#include <richio.h>
#include <class_board.h>
namespace KI_TEST
{
void DumpBoardToFile( BOARD& board, const std::string& aFilename )
{
PCB_IO io;
io.Save( aFilename, &board );
}
std::unique_ptr<BOARD_ITEM> ReadBoardItemFromFile( const std::string& aFilename )
{
FILE_LINE_READER reader( aFilename );
PCB_PARSER parser;
parser.SetLineReader( &reader );
return std::unique_ptr<BOARD_ITEM>( parser.Parse() );
}
} // namespace KI_TEST

View File

@ -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 <vector>
#include <layers_id_colors_and_visibility.h>
#include <math/vector2d.h>
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<VECTOR2I>& 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

View File

@ -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 <memory>
#include <string>
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<BOARD_ITEM> ReadBoardItemFromFile( const std::string& aFilename );
} // namespace KI_TEST
#endif // QA_PCBNEW_UTILS_BOARD_FILE_UTILS__H

View File

@ -25,7 +25,7 @@
# 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
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

View File

@ -28,6 +28,7 @@
#include <boost/test/unit_test.hpp>
#include <functional>
#include <set>
/**
* 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 <typename EXP_CONT> using EXP_OBJ = typename EXP_CONT::value_type;
template <typename FOUND_CONT> using FOUND_OBJ = typename FOUND_CONT::value_type;
template <typename EXP_OBJ, typename FOUND_OBJ>
using MATCH_PRED = std::function<bool( const EXP_OBJ&, const FOUND_OBJ& )>;
template <typename EXP_CONT, typename FOUND_CONT, typename MATCH_PRED>
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<const EXP_OBJ*> 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