← Back to team overview

kicad-developers team mailing list archive

[RFC] Pcbnew tests/utils

 

Hi,

Thanks Wayne for merging the utility program stuff.

This brings me onto the subject of PCB utility programs in the same
vein. Attached are some fun patches:

1) Some DRC-related groundwork
2) A Pcbnew unit test that can test a DRC function and the breaking
out of the courtyard DRC functions. Also makes a so called
"qa_pcbnew_utils" "library" *In theory* that can be shared between
Pcbnew unit tests and Pcbnew utility programs.
3) A Pcbnew DRC util that allows to run the courtyard DRC (broken out
in #2) from the command line. This is essentially what I used to
generate the benchmarks the other day. Also can be used for fuzz
testing, as well as debugging, developing and profiling.
4) Port the PCB parser utility over to the same executable.

To run the DRC util, for example, with timing and marker information:

$ qa/pcbnew_tools/qa_pcbnew_tools drc -vtm --courtyard-overlap
--courtyard-missing /path/to/pcb.kicad_pcb
Parsing: /path/to/pcb.kicad_pcb
Running DRC check: Courtyard overlap
Took: 1736us
DRC markers: 1
0: ErrType(45): Courtyards overlap
    @(24.500 mm, 20.000 mm): Footprint <no reference> on F.Cu
    @(23.600 mm, 20.000 mm): Footprint <no reference> on F.Cu
Running DRC check: Courtyard missing
Took: 131us
DRC markers: 0

However, I have had the traditional difficulty getting this to link
using a library for "qa_pcbnew_utils". What is in the patch #2 is a
nasty and heinous hack that just about gets it working on
Linux/Msys2/MSVC, but it's highly sub-optimal (it just puts the files
and link_libraries in variables :s) So, that's why this is an "RFC":
any ideas for getting this to work properly will be highly
appreciated.

Also, because this uses the same way of linking into pcbnew, it's even
*more* brutal on the RAM needs at link-time: the targets "pcbnew",
"qa_pcbnew" (unit tests) and "qa_pcbnew_tools" (the discussed utility
tools) are all quite slow.

Cheers,

John
From da9337b5a91c242e6568fc1c5e58cd580b68b4af Mon Sep 17 00:00:00 2001
From: John Beard <john.j.beard@xxxxxxxxx>
Date: Tue, 22 Jan 2019 14:47:03 +0000
Subject: [PATCH 3/4] QA: Pcbnew utility tools: DRC tool

Add a tool that allows a user to run DRC functions
on a given KiCad PCB file.

The first available functions are the courtyard functions.
---
 qa/CMakeLists.txt                           |   1 +
 qa/pcbnew_tools/CMakeLists.txt              |  55 +++
 qa/pcbnew_tools/pcbnew_tools.cpp            | 123 ++++++
 qa/pcbnew_tools/tools/drc_tool/drc_tool.cpp | 394 ++++++++++++++++++++
 qa/pcbnew_tools/tools/drc_tool/drc_tool.h   |  32 ++
 5 files changed, 605 insertions(+)
 create mode 100644 qa/pcbnew_tools/CMakeLists.txt
 create mode 100644 qa/pcbnew_tools/pcbnew_tools.cpp
 create mode 100644 qa/pcbnew_tools/tools/drc_tool/drc_tool.cpp
 create mode 100644 qa/pcbnew_tools/tools/drc_tool/drc_tool.h

diff --git a/qa/CMakeLists.txt b/qa/CMakeLists.txt
index 1f22f174a..d132ac030 100644
--- a/qa/CMakeLists.txt
+++ b/qa/CMakeLists.txt
@@ -25,6 +25,7 @@ add_subdirectory( shape_poly_set_refactor )
 
 # Utility/debugging/profiling programs
 add_subdirectory( common_tools )
+add_subdirectory( pcbnew_tools )
 add_subdirectory( pcb_parse_input )
 
 # add_subdirectory( pcb_test_window )
diff --git a/qa/pcbnew_tools/CMakeLists.txt b/qa/pcbnew_tools/CMakeLists.txt
new file mode 100644
index 000000000..ada6ce957
--- /dev/null
+++ b/qa/pcbnew_tools/CMakeLists.txt
@@ -0,0 +1,55 @@
+# 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
+
+add_executable( qa_pcbnew_tools
+
+    # The main entry point
+    pcbnew_tools.cpp
+
+    tools/drc_tool/drc_tool.cpp
+
+    # Unsure how to make this a proper library - can't make it link
+    # and find Pgm() and friends, so we'll just have to build twice...
+    ${QA_PCBNEW_UTILS_SRCS}
+
+    # Older CMakes cannot link OBJECT libraries
+    # https://cmake.org/pipermail/cmake/2013-November/056263.html
+    $<TARGET_OBJECTS:pcbnew_kiface_objects>
+)
+
+target_include_directories( qa_pcbnew_tools PRIVATE
+    BEFORE ${INC_BEFORE}
+)
+
+target_include_directories( qa_pcbnew_tools PRIVATE
+    ${QA_PCBNEW_UTILS_INCLUDE_DIRS}
+)
+
+target_link_libraries( qa_pcbnew_tools
+    # qa_pcbnew_utils
+    ${QA_PCBNEW_UTILS_LIBS}
+)
+
+
+# we need to pretend to be something to appease the units code
+target_compile_definitions( qa_pcbnew_tools
+    PRIVATE PCBNEW
+)
\ No newline at end of file
diff --git a/qa/pcbnew_tools/pcbnew_tools.cpp b/qa/pcbnew_tools/pcbnew_tools.cpp
new file mode 100644
index 000000000..b902fb049
--- /dev/null
+++ b/qa/pcbnew_tools/pcbnew_tools.cpp
@@ -0,0 +1,123 @@
+/*
+ * 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 <common.h>
+
+#include <wx/cmdline.h>
+
+#include "tools/drc_tool/drc_tool.h"
+
+/**
+ * List of registered tools.
+ *
+ * This is a pretty rudimentary way to register, but for a simple purpose,
+ * it's effective enough. When you have a new tool, add it to this list.
+ */
+const static std::vector<UTILITY_PROGRAM*> known_tools = {
+    &drc_tool,
+};
+
+
+/**
+ * Print the names and descriptions of the registered tools
+ */
+static void show_tool_list()
+{
+    for( const auto& tool : known_tools )
+    {
+        std::cout << tool->m_name << ": \t" << tool->m_desc << std::endl;
+    }
+}
+
+
+/**
+ * Get the utility program that matches a tool name
+ * @param  aName the name to look for
+ * @return       the tool function
+ */
+UTILITY_PROGRAM::FUNC* get_program( const std::string& aName )
+{
+    for( const auto& tool : known_tools )
+    {
+        if( tool->m_name == aName )
+            return &tool->m_func;
+    }
+
+    return nullptr;
+}
+
+
+void print_usage( char* name )
+{
+    std::cout << "Run a utility tool." << std::endl;
+
+    std::cout << "Usage: " << name << " [-h] [-l] [TOOL [TOOL_OPTIONS]]" << std::endl;
+
+    std::cout << "  -h      show this message and exit." << std::endl
+              << "  -l      print known tools and exit." << std::endl;
+
+    std::cout << std::endl;
+    std::cout << "Known tools: " << std::endl;
+
+    show_tool_list();
+}
+
+
+int main( int argc, char** argv )
+{
+    wxMessageOutput::Set( new wxMessageOutputStderr );
+
+    // Need at least one parameter
+    if( argc < 2 )
+    {
+        print_usage( argv[0] );
+        return RET_CODES::BAD_CMDLINE;
+    }
+
+    const std::string arg1( argv[1] );
+
+    if( argc == 2 )
+    {
+        if( arg1 == "-h" )
+        {
+            print_usage( argv[0] );
+            return RET_CODES::OK;
+        }
+        else if( arg1 == "-l" )
+        {
+            show_tool_list();
+            return RET_CODES::OK;
+        }
+    }
+
+    auto func = get_program( arg1 );
+
+    if( !func )
+    {
+        std::cout << "Tool " << arg1 << " not found." << std::endl;
+        return RET_CODES::UNKNOWN_TOOL;
+    }
+
+    // pass on the rest of the commands
+    return ( *func )( argc - 1, argv + 1 );
+}
\ No newline at end of file
diff --git a/qa/pcbnew_tools/tools/drc_tool/drc_tool.cpp b/qa/pcbnew_tools/tools/drc_tool/drc_tool.cpp
new file mode 100644
index 000000000..6af5db505
--- /dev/null
+++ b/qa/pcbnew_tools/tools/drc_tool/drc_tool.cpp
@@ -0,0 +1,394 @@
+/*
+ * 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 "drc_tool.h"
+
+#include <cstdio>
+#include <string>
+
+#include <common.h>
+
+#include <wx/cmdline.h>
+
+// Parsing
+#include <class_board.h>
+#include <class_board_item.h>
+#include <kicad_plugin.h>
+#include <pcb_parser.h>
+#include <richio.h>
+
+// DRC
+#include <drc/courtyard_overlap.h>
+#include <drc/drc_marker_factory.h>
+
+#include <scoped_timer.h>
+#include <stdstream_line_reader.h>
+
+
+/**
+ * Parse a PCB from the given stream
+ *
+ * @param aStream the input stream to read from
+ */
+std::unique_ptr<BOARD> parse( std::istream& aStream )
+{
+    // Take input from stdin
+    STDISTREAM_LINE_READER reader;
+    reader.SetStream( aStream );
+
+    PCB_PARSER parser;
+
+    parser.SetLineReader( &reader );
+
+    std::unique_ptr<BOARD> board;
+
+    try
+    {
+        board.reset( dynamic_cast<BOARD*>( parser.Parse() ) );
+    }
+    catch( const IO_ERROR& parse_error )
+    {
+        std::cerr << parse_error.Problem() << std::endl;
+        std::cerr << parse_error.Where() << std::endl;
+    }
+
+    return board;
+}
+
+using DRC_DURATION = std::chrono::microseconds;
+
+/**
+ * DRC runner: provides a simple framework to run some DRC checks on #BOARDS.
+ * The DRC_RUNNER can be set up as needed to instantiate a #DRC_PROVIDER to
+ * perform the desired DRC on the #BOARD and provide some basic information
+ * about what happened.
+ */
+class DRC_RUNNER
+{
+public:
+    /**
+     * How the DRC runner behaves (e.g. what is printed and how)
+     */
+    struct EXECUTION_CONTEXT
+    {
+        bool m_verbose;
+        bool m_print_times;
+        bool m_print_markers;
+    };
+
+    DRC_RUNNER( const EXECUTION_CONTEXT& aExecCtx ) : m_exec_context( aExecCtx )
+    {
+    }
+
+    void Execute( BOARD& aBoard )
+    {
+        if( m_exec_context.m_verbose )
+        {
+            std::cout << "Running DRC check: " << getRunnerIntro() << std::endl;
+        }
+
+        aBoard.SetDesignSettings( getDesignSettings() );
+
+        std::vector<std::unique_ptr<MARKER_PCB>> markers;
+
+        auto marker_handler = [&]( MARKER_PCB* aMarker ) {
+            markers.push_back( std::unique_ptr<MARKER_PCB>( aMarker ) );
+        };
+
+        std::unique_ptr<DRC_PROVIDER> drc_prov = createDrcProvider( aBoard, marker_handler );
+
+        DRC_DURATION duration;
+        {
+            SCOPED_TIMER<DRC_DURATION> timer( duration );
+            drc_prov->RunDRC( aBoard );
+        }
+
+        // report results
+        if( m_exec_context.m_print_times )
+            reportDuration( duration );
+
+        if( m_exec_context.m_print_markers )
+            reportMarkers( markers );
+    }
+
+protected:
+    const DRC_MARKER_FACTORY& getMarkerFactory() const
+    {
+        return m_marker_factory;
+    }
+
+private:
+    /**
+     * Get the introduction text for this DRC runner
+     */
+    virtual std::string getRunnerIntro() const = 0;
+
+    /**
+     * Get suitable design settings for this DRC runner
+     */
+    virtual BOARD_DESIGN_SETTINGS getDesignSettings() const = 0;
+
+    virtual std::unique_ptr<DRC_PROVIDER> createDrcProvider(
+            BOARD& aBoard, DRC_PROVIDER::MARKER_HANDLER aHandler ) = 0;
+
+    void reportDuration( const DRC_DURATION& aDuration ) const
+    {
+        std::cout << "Took: " << aDuration.count() << "us" << std::endl;
+    }
+
+    void reportMarkers( const std::vector<std::unique_ptr<MARKER_PCB>>& aMarkers ) const
+    {
+        std::cout << "DRC markers: " << aMarkers.size() << std::endl;
+
+        int index = 0;
+        for( const auto& m : aMarkers )
+        {
+            std::cout << index++ << ": " << m->GetReporter().ShowReport( EDA_UNITS_T::MILLIMETRES );
+        }
+
+        if( index )
+        {
+            std::cout << std::endl;
+        }
+    }
+
+    const EXECUTION_CONTEXT m_exec_context;
+    DRC_MARKER_FACTORY      m_marker_factory;
+};
+
+
+/**
+ * DRC runner to run only DRC courtyard-overlap checks
+ */
+class DRC_COURTYARD_OVERLAP_RUNNER : public DRC_RUNNER
+{
+public:
+    DRC_COURTYARD_OVERLAP_RUNNER( const EXECUTION_CONTEXT& aCtx ) : DRC_RUNNER( aCtx )
+    {
+    }
+
+private:
+    std::string getRunnerIntro() const override
+    {
+        return "Courtyard overlap";
+    }
+
+    BOARD_DESIGN_SETTINGS getDesignSettings() const override
+    {
+        BOARD_DESIGN_SETTINGS des_settings;
+        des_settings.m_RequireCourtyards = false;
+        des_settings.m_ProhibitOverlappingCourtyards = true;
+
+        return des_settings;
+    }
+
+    std::unique_ptr<DRC_PROVIDER> createDrcProvider(
+            BOARD& aBoard, DRC_PROVIDER::MARKER_HANDLER aHandler ) override
+    {
+        return std::make_unique<DRC_COURTYARD_OVERLAP>( getMarkerFactory(), aHandler );
+    }
+};
+
+
+/**
+ * DRC runner to run only DRC courtyard-missing checks
+ */
+class DRC_COURTYARD_MISSING_RUNNER : public DRC_RUNNER
+{
+public:
+    DRC_COURTYARD_MISSING_RUNNER( const EXECUTION_CONTEXT& aCtx ) : DRC_RUNNER( aCtx )
+    {
+    }
+
+private:
+    std::string getRunnerIntro() const override
+    {
+        return "Courtyard missing";
+    }
+
+    BOARD_DESIGN_SETTINGS getDesignSettings() const override
+    {
+        BOARD_DESIGN_SETTINGS des_settings;
+        des_settings.m_RequireCourtyards = true;
+        des_settings.m_ProhibitOverlappingCourtyards = false;
+
+        return des_settings;
+    }
+
+    std::unique_ptr<DRC_PROVIDER> createDrcProvider(
+            BOARD& aBoard, DRC_PROVIDER::MARKER_HANDLER aHandler ) override
+    {
+        return std::make_unique<DRC_COURTYARD_OVERLAP>( getMarkerFactory(), aHandler );
+    }
+};
+
+
+static const wxCmdLineEntryDesc g_cmdLineDesc[] = {
+    {
+            wxCMD_LINE_SWITCH,
+            "h",
+            "help",
+            _( "displays help on the command line parameters" ).mb_str(),
+            wxCMD_LINE_VAL_NONE,
+            wxCMD_LINE_OPTION_HELP,
+    },
+    {
+            wxCMD_LINE_SWITCH,
+            "v",
+            "verbose",
+            _( "print parsing information" ).mb_str(),
+    },
+    {
+            wxCMD_LINE_SWITCH,
+            "t",
+            "timings",
+            _( "print DRC timings" ).mb_str(),
+    },
+    {
+            wxCMD_LINE_SWITCH,
+            "m",
+            "print-markers",
+            _( "print DRC marker information" ).mb_str(),
+    },
+    {
+            wxCMD_LINE_SWITCH,
+            "A",
+            "all-checks",
+            _( "perform all available DRC checks" ).mb_str(),
+    },
+    {
+            wxCMD_LINE_SWITCH,
+            "C",
+            "courtyard-overlap",
+            _( "perform courtyard-overlap (and malformation) checking" ).mb_str(),
+    },
+    {
+            wxCMD_LINE_SWITCH,
+            "c",
+            "courtyard-missing",
+            _( "perform courtyard-missing checking" ).mb_str(),
+    },
+    {
+            wxCMD_LINE_PARAM,
+            nullptr,
+            nullptr,
+            _( "input file" ).mb_str(),
+            wxCMD_LINE_VAL_STRING,
+            wxCMD_LINE_PARAM_OPTIONAL,
+    },
+    { wxCMD_LINE_NONE }
+};
+
+/**
+ * Tool=specific return codes
+ */
+enum PARSER_RET_CODES
+{
+    PARSE_FAILED = RET_CODES::TOOL_SPECIFIC,
+};
+
+
+int drc_main_func( int argc, char** argv )
+{
+#ifdef __AFL_COMPILER
+    __AFL_INIT();
+#endif
+
+    wxMessageOutput::Set( new wxMessageOutputStderr );
+    wxCmdLineParser cl_parser( argc, argv );
+    cl_parser.SetDesc( g_cmdLineDesc );
+    cl_parser.AddUsageText(
+            _( "This program runs DRC tools on given PCB files. "
+               "This can be used for debugging, fuzz testing or development, etc." ) );
+
+    int cmd_parsed_ok = cl_parser.Parse();
+    if( cmd_parsed_ok != 0 )
+    {
+        // Help and invalid input both stop here
+        return ( cmd_parsed_ok == -1 ) ? RET_CODES::OK : RET_CODES::BAD_CMDLINE;
+    }
+
+    const bool verbose = cl_parser.Found( "verbose" );
+
+    const auto file_count = cl_parser.GetParamCount();
+
+    std::unique_ptr<BOARD> board;
+
+    if( file_count == 0 )
+    {
+        // Parse the file provided on stdin - used by AFL to drive the
+        // program
+        // while (__AFL_LOOP(2))
+        {
+            board = parse( std::cin );
+        }
+    }
+    else
+    {
+        const auto filename = cl_parser.GetParam( 0 ).ToStdString();
+
+        if( verbose )
+            std::cout << "Parsing: " << filename << std::endl;
+
+        std::ifstream fin;
+        fin.open( filename );
+
+        board = parse( fin );
+    }
+
+    if( !board )
+        return PARSER_RET_CODES::PARSE_FAILED;
+
+    DRC_RUNNER::EXECUTION_CONTEXT exec_context{
+        verbose,
+        cl_parser.Found( "timings" ),
+        cl_parser.Found( "print-markers" ),
+    };
+
+    const bool all = cl_parser.Found( "all-checks" );
+
+    // Run the DRC on the board
+    if( all || cl_parser.Found( "courtyard-overlap" ) )
+    {
+        DRC_COURTYARD_OVERLAP_RUNNER runner( exec_context );
+        runner.Execute( *board );
+    }
+
+    if( all || cl_parser.Found( "courtyard-missing" ) )
+    {
+        DRC_COURTYARD_MISSING_RUNNER runner( exec_context );
+        runner.Execute( *board );
+    }
+
+    return RET_CODES::OK;
+}
+
+
+/*
+ * Define the tool interface
+ */
+UTILITY_PROGRAM drc_tool = {
+    "drc",
+    "Run selected DRC function on a PCB",
+    drc_main_func,
+};
\ No newline at end of file
diff --git a/qa/pcbnew_tools/tools/drc_tool/drc_tool.h b/qa/pcbnew_tools/tools/drc_tool/drc_tool.h
new file mode 100644
index 000000000..aa4804994
--- /dev/null
+++ b/qa/pcbnew_tools/tools/drc_tool/drc_tool.h
@@ -0,0 +1,32 @@
+/*
+ * 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
+ */
+
+#ifndef PCBNEW_TOOLS_DRC_TOOL_H
+#define PCBNEW_TOOLS_DRC_TOOL_H
+
+#include <utility_program.h>
+
+/// A tool to run DRC tools on KiCad PCBs from the command line
+extern UTILITY_PROGRAM drc_tool;
+
+#endif //PCBNEW_TOOLS_DRC_TOOL_H
\ No newline at end of file
-- 
2.20.1

From 66cfeefcf951f547a31e0fd4825279d260bb2d7e Mon Sep 17 00:00:00 2001
From: John Beard <john.j.beard@xxxxxxxxx>
Date: Tue, 22 Jan 2019 16:38:09 +0000
Subject: [PATCH 4/4] QA: Move PCB parse util to combined tools exec

This means all the current Pcbnew utilities are all in a single
executable.

The tool is now run by:

$ qa/pcbnew_tools/qa_pcbnew_tools pcb_parser <same arguments as before>
---
 qa/CMakeLists.txt                             |  1 -
 qa/pcb_parse_input/CMakeLists.txt             | 90 -------------------
 qa/pcbnew_tools/CMakeLists.txt                |  2 +
 qa/pcbnew_tools/pcbnew_tools.cpp              |  2 +
 .../tools/pcb_parser/pcb_parser_tool.cpp}     | 67 ++++++++------
 .../tools/pcb_parser/pcb_parser_tool.h        | 32 +++++++
 6 files changed, 76 insertions(+), 118 deletions(-)
 delete mode 100644 qa/pcb_parse_input/CMakeLists.txt
 rename qa/{pcb_parse_input/main.cpp => pcbnew_tools/tools/pcb_parser/pcb_parser_tool.cpp} (70%)
 create mode 100644 qa/pcbnew_tools/tools/pcb_parser/pcb_parser_tool.h

diff --git a/qa/CMakeLists.txt b/qa/CMakeLists.txt
index d132ac030..f178e775f 100644
--- a/qa/CMakeLists.txt
+++ b/qa/CMakeLists.txt
@@ -26,7 +26,6 @@ add_subdirectory( shape_poly_set_refactor )
 # Utility/debugging/profiling programs
 add_subdirectory( common_tools )
 add_subdirectory( pcbnew_tools )
-add_subdirectory( pcb_parse_input )
 
 # add_subdirectory( pcb_test_window )
 # add_subdirectory( polygon_triangulation )
diff --git a/qa/pcb_parse_input/CMakeLists.txt b/qa/pcb_parse_input/CMakeLists.txt
deleted file mode 100644
index 75ce4baff..000000000
--- a/qa/pcb_parse_input/CMakeLists.txt
+++ /dev/null
@@ -1,90 +0,0 @@
-# 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
-
-
-if( BUILD_GITHUB_PLUGIN )
-    set( GITHUB_PLUGIN_LIBRARIES github_plugin )
-endif()
-
-add_dependencies( pnsrouter pcbcommon pcad2kicadpcb ${GITHUB_PLUGIN_LIBRARIES} )
-
-add_executable( qa_pcb_parse_input
-    # This is needed for the global mock objects
-    ../qa_utils/mocks.cpp
-
-    main.cpp
-
-  ../../common/base_units.cpp
-  ../../common/xnode.cpp
-  ../../common/base_screen.cpp
-)
-
-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
-    ${Boost_INCLUDE_DIR}
-    ${INC_AFTER}
-)
-
-target_link_libraries( qa_pcb_parse_input
-    pcbcommon
-    legacy_wx
-    polygon
-    pnsrouter
-    common
-    pcbcommon
-    bitmaps
-    polygon
-    pnsrouter
-    common
-    pcbcommon
-    bitmaps
-    polygon
-    pnsrouter
-    common
-    pcbcommon
-    bitmaps
-    polygon
-    pnsrouter
-    common
-    pcbcommon
-    3d-viewer
-    bitmaps
-    gal
-    pcad2kicadpcb
-    common
-    pcbcommon
-    ${GITHUB_PLUGIN_LIBRARIES}
-    qa_utils
-    ${wxWidgets_LIBRARIES}
-)
-
-# we need to pretend to be something to appease the units code
-target_compile_definitions( qa_pcb_parse_input
-    PRIVATE PCBNEW
-)
\ No newline at end of file
diff --git a/qa/pcbnew_tools/CMakeLists.txt b/qa/pcbnew_tools/CMakeLists.txt
index ada6ce957..440f8b632 100644
--- a/qa/pcbnew_tools/CMakeLists.txt
+++ b/qa/pcbnew_tools/CMakeLists.txt
@@ -26,6 +26,8 @@ add_executable( qa_pcbnew_tools
 
     tools/drc_tool/drc_tool.cpp
 
+    tools/pcb_parser/pcb_parser_tool.cpp
+
     # Unsure how to make this a proper library - can't make it link
     # and find Pgm() and friends, so we'll just have to build twice...
     ${QA_PCBNEW_UTILS_SRCS}
diff --git a/qa/pcbnew_tools/pcbnew_tools.cpp b/qa/pcbnew_tools/pcbnew_tools.cpp
index b902fb049..c87a38f30 100644
--- a/qa/pcbnew_tools/pcbnew_tools.cpp
+++ b/qa/pcbnew_tools/pcbnew_tools.cpp
@@ -26,6 +26,7 @@
 #include <wx/cmdline.h>
 
 #include "tools/drc_tool/drc_tool.h"
+#include "tools/pcb_parser/pcb_parser_tool.h"
 
 /**
  * List of registered tools.
@@ -35,6 +36,7 @@
  */
 const static std::vector<UTILITY_PROGRAM*> known_tools = {
     &drc_tool,
+    &pcb_parser_tool,
 };
 
 
diff --git a/qa/pcb_parse_input/main.cpp b/qa/pcbnew_tools/tools/pcb_parser/pcb_parser_tool.cpp
similarity index 70%
rename from qa/pcb_parse_input/main.cpp
rename to qa/pcbnew_tools/tools/pcb_parser/pcb_parser_tool.cpp
index 9855b177d..3e763a507 100644
--- a/qa/pcb_parse_input/main.cpp
+++ b/qa/pcbnew_tools/tools/pcb_parser/pcb_parser_tool.cpp
@@ -1,7 +1,7 @@
 /*
  * This program source code file is part of KiCad, a free EDA CAD application.
  *
- * Copyright (C) 2018 KiCad Developers, see CHANGELOG.TXT for contributors.
+ * 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
@@ -21,26 +21,35 @@
  * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
  */
 
+#include "pcb_parser_tool.h"
 
+#include <cstdio>
+#include <string>
+
+#include <common.h>
+
+#include <wx/cmdline.h>
+
+#include <class_board_item.h>
 #include <kicad_plugin.h>
 #include <pcb_parser.h>
 #include <richio.h>
-#include <class_board_item.h>
 
 #include <wx/cmdline.h>
 
-#include <stdstream_line_reader.h>
 #include <scoped_timer.h>
+#include <stdstream_line_reader.h>
 
 using PARSE_DURATION = std::chrono::microseconds;
 
+
 /**
  * Parse a PCB or footprint file from the given input stream
  *
  * @param aStream the input stream to read from
  * @return success, duration (in us)
  */
-bool parse(std::istream& aStream, bool aVerbose )
+bool parse( std::istream& aStream, bool aVerbose )
 {
     // Take input from stdin
     STDISTREAM_LINE_READER reader;
@@ -52,7 +61,7 @@ bool parse(std::istream& aStream, bool aVerbose )
 
     BOARD_ITEM* board = nullptr;
 
-    PARSE_DURATION duration {};
+    PARSE_DURATION duration{};
 
     try
     {
@@ -74,40 +83,35 @@ bool parse(std::istream& aStream, bool aVerbose )
 }
 
 
-static const wxCmdLineEntryDesc g_cmdLineDesc [] =
-{
-    { wxCMD_LINE_SWITCH, "h", "help",
-        _( "displays help on the command line parameters" ).mb_str(),
-        wxCMD_LINE_VAL_NONE, wxCMD_LINE_OPTION_HELP },
-    { wxCMD_LINE_SWITCH, "v", "verbose",
-        _( "print parsing information").mb_str() },
-    { wxCMD_LINE_PARAM, nullptr, nullptr,
-        _( "input file" ).mb_str(),
-        wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL | wxCMD_LINE_PARAM_MULTIPLE },
+static const wxCmdLineEntryDesc g_cmdLineDesc[] = {
+    { wxCMD_LINE_SWITCH, "h", "help", _( "displays help on the command line parameters" ).mb_str(),
+            wxCMD_LINE_VAL_NONE, wxCMD_LINE_OPTION_HELP },
+    { wxCMD_LINE_SWITCH, "v", "verbose", _( "print parsing information" ).mb_str() },
+    { wxCMD_LINE_PARAM, nullptr, nullptr, _( "input file" ).mb_str(), wxCMD_LINE_VAL_STRING,
+            wxCMD_LINE_PARAM_OPTIONAL | wxCMD_LINE_PARAM_MULTIPLE },
     { wxCMD_LINE_NONE }
 };
 
 
-enum RET_CODES
+enum PARSER_RET_CODES
 {
-    OK = 0,
-    BAD_CMDLINE = 1,
-    PARSE_FAILED = 2,
+    PARSE_FAILED = RET_CODES::TOOL_SPECIFIC,
 };
 
 
-int main(int argc, char** argv)
+int pcb_parser_main_func( int argc, char** argv )
 {
 #ifdef __AFL_COMPILER
     __AFL_INIT();
 #endif
 
-    wxMessageOutput::Set(new wxMessageOutputStderr);
+    wxMessageOutput::Set( new wxMessageOutputStderr );
     wxCmdLineParser cl_parser( argc, argv );
     cl_parser.SetDesc( g_cmdLineDesc );
-    cl_parser.AddUsageText( _("This program parses PCB files, either from the "
-        "stdin stream or from the given filenames. This can be used either for "
-        "standalone testing of the parser or for fuzz testing." ) );
+    cl_parser.AddUsageText(
+            _( "This program parses PCB files, either from the "
+               "stdin stream or from the given filenames. This can be used either for "
+               "standalone testing of the parser or for fuzz testing." ) );
 
     int cmd_parsed_ok = cl_parser.Parse();
     if( cmd_parsed_ok != 0 )
@@ -119,11 +123,10 @@ int main(int argc, char** argv)
     const bool verbose = cl_parser.Found( "verbose" );
 
     bool ok = true;
-    PARSE_DURATION duration;
 
     const auto file_count = cl_parser.GetParamCount();
 
-    if ( file_count == 0 )
+    if( file_count == 0 )
     {
         // Parse the file provided on stdin - used by AFL to drive the
         // program
@@ -152,7 +155,17 @@ int main(int argc, char** argv)
     }
 
     if( !ok )
-        return RET_CODES::PARSE_FAILED;
+        return PARSER_RET_CODES::PARSE_FAILED;
 
     return RET_CODES::OK;
 }
+
+
+/*
+ * Define the tool interface
+ */
+UTILITY_PROGRAM pcb_parser_tool = {
+    "pcb_parser",
+    "Parse a KiCad PCB file",
+    pcb_parser_main_func,
+};
\ No newline at end of file
diff --git a/qa/pcbnew_tools/tools/pcb_parser/pcb_parser_tool.h b/qa/pcbnew_tools/tools/pcb_parser/pcb_parser_tool.h
new file mode 100644
index 000000000..ddab182d7
--- /dev/null
+++ b/qa/pcbnew_tools/tools/pcb_parser/pcb_parser_tool.h
@@ -0,0 +1,32 @@
+/*
+ * 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
+ */
+
+#ifndef PCBNEW_TOOLS_PCB_PARSER_UTILITY_H
+#define PCBNEW_TOOLS_PCB_PARSER_UTILITY_H
+
+#include <utility_program.h>
+
+/// A tool to parse kicad PCBs from the command line
+extern UTILITY_PROGRAM pcb_parser_tool;
+
+#endif //PCBNEW_TOOLS_PCB_PARSER_UTILITY_H
\ No newline at end of file
-- 
2.20.1

From 172fd1db460385ee69e0fd3876ac6f755af44b16 Mon Sep 17 00:00:00 2001
From: John Beard <john.j.beard@xxxxxxxxx>
Date: Fri, 11 Jan 2019 13:21:38 +0000
Subject: [PATCH 1/4] DRC: Make the marker factory a separate class

This separates the "newMarker" functions from the DRC class. These
are moved to a new class, DRC_MARKER_FACTORY, which is now responsible for
constructing suitable markers from BOARD_ITEMS.

The reasons for this are:

1) Allow DRC related functions to access theese functions without having
   to bake these functions into the main DRC class.
2) Thereby simplify the DRC class interface
3) As DRC_MARKER is now a small class with public interfaces, it's
   possible to put unit tests on these functions.

The motivation here is to allow to start to split out pieces of DRC
into separate classes.
---
 pcbnew/CMakeLists.txt                         |   6 +-
 pcbnew/drc.cpp                                |  80 ++++++++------
 pcbnew/drc.h                                  |  39 +------
 .../drc_marker_factory.cpp}                   |  95 ++++++++--------
 pcbnew/drc/drc_marker_factory.h               | 104 ++++++++++++++++++
 pcbnew/drc_clearance_test_functions.cpp       |  88 +++++++++------
 6 files changed, 269 insertions(+), 143 deletions(-)
 rename pcbnew/{drc_marker_functions.cpp => drc/drc_marker_factory.cpp} (63%)
 create mode 100644 pcbnew/drc/drc_marker_factory.h

diff --git a/pcbnew/CMakeLists.txt b/pcbnew/CMakeLists.txt
index 38754c3f2..f20df851c 100644
--- a/pcbnew/CMakeLists.txt
+++ b/pcbnew/CMakeLists.txt
@@ -211,10 +211,15 @@ set( PCBNEW_MICROWAVE_SRCS
     microwave/microwave_inductor.cpp
     )
 
+set( PCBNEW_DRC_SRCS
+    drc/drc_marker_factory.cpp
+    )
+
 set( PCBNEW_CLASS_SRCS
     ${PCBNEW_DIALOGS}
     ${PCBNEW_EXPORTERS}
     ${PCBNEW_IMPORT_GFX}
+    ${PCBNEW_DRC_SRCS}
 
     autorouter/rect_placement/rect_placement.cpp
     autorouter/spread_footprints.cpp
@@ -238,7 +243,6 @@ set( PCBNEW_CLASS_SRCS
     dragsegm.cpp
     drc.cpp
     drc_clearance_test_functions.cpp
-    drc_marker_functions.cpp
     edgemod.cpp
     edit.cpp
     edit_pcb_text.cpp
diff --git a/pcbnew/drc.cpp b/pcbnew/drc.cpp
index 31c696e7a..5c4a400b4 100644
--- a/pcbnew/drc.cpp
+++ b/pcbnew/drc.cpp
@@ -155,6 +155,8 @@ DRC::DRC( PCB_EDIT_FRAME* aPcbWindow )
     m_ycliplo = 0;
     m_xcliphi = 0;
     m_ycliphi = 0;
+
+    m_markerFactory.SetUnitsProvider( [=]() { return aPcbWindow->GetUserUnits(); } );
 }
 
 
@@ -283,7 +285,8 @@ int DRC::TestZoneToZoneOutline( ZONE_CONTAINER* aZone, bool aCreateMarkers )
                 if( smoothed_polys[ia2].Contains( currentVertex ) )
                 {
                     if( aCreateMarkers )
-                        commit.Add( newMarker( pt, zoneRef, zoneToTest, DRCE_ZONES_INTERSECT ) );
+                        commit.Add( m_markerFactory.NewMarker(
+                                pt, zoneRef, zoneToTest, DRCE_ZONES_INTERSECT ) );
 
                     nerrors++;
                 }
@@ -298,7 +301,8 @@ int DRC::TestZoneToZoneOutline( ZONE_CONTAINER* aZone, bool aCreateMarkers )
                 if( smoothed_polys[ia].Contains( currentVertex ) )
                 {
                     if( aCreateMarkers )
-                        commit.Add( newMarker( pt, zoneToTest, zoneRef, DRCE_ZONES_INTERSECT ) );
+                        commit.Add( m_markerFactory.NewMarker(
+                                pt, zoneToTest, zoneRef, DRCE_ZONES_INTERSECT ) );
 
                     nerrors++;
                 }
@@ -346,7 +350,8 @@ int DRC::TestZoneToZoneOutline( ZONE_CONTAINER* aZone, bool aCreateMarkers )
             for( wxPoint pt : conflictPoints )
             {
                 if( aCreateMarkers )
-                    commit.Add( newMarker( pt, zoneRef, zoneToTest, DRCE_ZONES_TOO_CLOSE ) );
+                    commit.Add( m_markerFactory.NewMarker(
+                            pt, zoneRef, zoneToTest, DRCE_ZONES_TOO_CLOSE ) );
 
                 nerrors++;
             }
@@ -598,7 +603,7 @@ bool DRC::doNetClass( const NETCLASSPTR& nc, wxString& msg )
                     FmtVal( g.m_TrackMinWidth )
                     );
 
-        addMarkerToPcb( newMarker( DRCE_NETCLASS_TRACKWIDTH, msg ) );
+        addMarkerToPcb( m_markerFactory.NewMarker( DRCE_NETCLASS_TRACKWIDTH, msg ) );
         ret = false;
     }
 
@@ -610,7 +615,7 @@ bool DRC::doNetClass( const NETCLASSPTR& nc, wxString& msg )
                     FmtVal( g.m_ViasMinSize )
                     );
 
-        addMarkerToPcb( newMarker( DRCE_NETCLASS_VIASIZE, msg ) );
+        addMarkerToPcb( m_markerFactory.NewMarker( DRCE_NETCLASS_VIASIZE, msg ) );
         ret = false;
     }
 
@@ -622,7 +627,7 @@ bool DRC::doNetClass( const NETCLASSPTR& nc, wxString& msg )
                     FmtVal( g.m_ViasMinDrill )
                     );
 
-        addMarkerToPcb( newMarker( DRCE_NETCLASS_VIADRILLSIZE, msg ) );
+        addMarkerToPcb( m_markerFactory.NewMarker( DRCE_NETCLASS_VIADRILLSIZE, msg ) );
         ret = false;
     }
 
@@ -633,7 +638,7 @@ bool DRC::doNetClass( const NETCLASSPTR& nc, wxString& msg )
                     FmtVal( nc->GetuViaDiameter() ),
                     FmtVal( g.m_MicroViasMinSize ) );
 
-        addMarkerToPcb( newMarker( DRCE_NETCLASS_uVIASIZE, msg ) );
+        addMarkerToPcb( m_markerFactory.NewMarker( DRCE_NETCLASS_uVIASIZE, msg ) );
         ret = false;
     }
 
@@ -644,7 +649,7 @@ bool DRC::doNetClass( const NETCLASSPTR& nc, wxString& msg )
                     FmtVal( nc->GetuViaDrill() ),
                     FmtVal( g.m_MicroViasMinDrill ) );
 
-        addMarkerToPcb( newMarker( DRCE_NETCLASS_uVIADRILLSIZE, msg ) );
+        addMarkerToPcb( m_markerFactory.NewMarker( DRCE_NETCLASS_uVIADRILLSIZE, msg ) );
         ret = false;
     }
 
@@ -905,7 +910,8 @@ void DRC::testZones()
         if( ( netcode < 0 ) || pads_in_net == 0 )
         {
             wxPoint markerPos = zone->GetPosition();
-            addMarkerToPcb( newMarker( markerPos, zone, DRCE_SUSPICIOUS_NET_FOR_ZONE_OUTLINE ) );
+            addMarkerToPcb( m_markerFactory.NewMarker(
+                    markerPos, zone, DRCE_SUSPICIOUS_NET_FOR_ZONE_OUTLINE ) );
         }
     }
 
@@ -940,7 +946,8 @@ void DRC::testKeepoutAreas()
                 SEG trackSeg( segm->GetStart(), segm->GetEnd() );
 
                 if( area->Outline()->Distance( trackSeg, segm->GetWidth() ) == 0 )
-                    addMarkerToPcb( newMarker( segm, area, DRCE_TRACK_INSIDE_KEEPOUT ) );
+                    addMarkerToPcb(
+                            m_markerFactory.NewMarker( segm, area, DRCE_TRACK_INSIDE_KEEPOUT ) );
             }
             else if( segm->Type() == PCB_VIA_T )
             {
@@ -953,7 +960,8 @@ void DRC::testKeepoutAreas()
                     continue;
 
                 if( area->Outline()->Distance( segm->GetPosition() ) < segm->GetWidth()/2 )
-                    addMarkerToPcb( newMarker( segm, area, DRCE_VIA_INSIDE_KEEPOUT ) );
+                    addMarkerToPcb(
+                            m_markerFactory.NewMarker( segm, area, DRCE_VIA_INSIDE_KEEPOUT ) );
             }
         }
         // Test pads: TODO
@@ -1073,9 +1081,11 @@ void DRC::testCopperDrawItem( DRAWSEGMENT* aItem )
             if( trackAsSeg.Distance( itemSeg ) < minDist )
             {
                 if( track->Type() == PCB_VIA_T )
-                    addMarkerToPcb( newMarker( track, aItem, itemSeg, DRCE_VIA_NEAR_COPPER ) );
+                    addMarkerToPcb( m_markerFactory.NewMarker(
+                            track, aItem, itemSeg, DRCE_VIA_NEAR_COPPER ) );
                 else
-                    addMarkerToPcb( newMarker( track, aItem, itemSeg, DRCE_TRACK_NEAR_COPPER ) );
+                    addMarkerToPcb( m_markerFactory.NewMarker(
+                            track, aItem, itemSeg, DRCE_TRACK_NEAR_COPPER ) );
                 break;
             }
         }
@@ -1099,7 +1109,7 @@ void DRC::testCopperDrawItem( DRAWSEGMENT* aItem )
         {
             if( padOutline.Distance( itemSeg, itemWidth ) == 0 )
             {
-                addMarkerToPcb( newMarker( pad, aItem, DRCE_PAD_NEAR_COPPER ) );
+                addMarkerToPcb( m_markerFactory.NewMarker( pad, aItem, DRCE_PAD_NEAR_COPPER ) );
                 break;
             }
         }
@@ -1139,9 +1149,11 @@ void DRC::testCopperTextItem( BOARD_ITEM* aTextItem )
             if( trackAsSeg.Distance( textSeg ) < minDist )
             {
                 if( track->Type() == PCB_VIA_T )
-                    addMarkerToPcb( newMarker( track, aTextItem, textSeg, DRCE_VIA_NEAR_COPPER ) );
+                    addMarkerToPcb( m_markerFactory.NewMarker(
+                            track, aTextItem, textSeg, DRCE_VIA_NEAR_COPPER ) );
                 else
-                    addMarkerToPcb( newMarker( track, aTextItem, textSeg, DRCE_TRACK_NEAR_COPPER ) );
+                    addMarkerToPcb( m_markerFactory.NewMarker(
+                            track, aTextItem, textSeg, DRCE_TRACK_NEAR_COPPER ) );
                 break;
             }
         }
@@ -1167,7 +1179,7 @@ void DRC::testCopperTextItem( BOARD_ITEM* aTextItem )
 
             if( padOutline.Distance( textSeg, textWidth ) == 0 )
             {
-                addMarkerToPcb( newMarker( pad, aTextItem, DRCE_PAD_NEAR_COPPER ) );
+                addMarkerToPcb( m_markerFactory.NewMarker( pad, aTextItem, DRCE_PAD_NEAR_COPPER ) );
                 break;
             }
         }
@@ -1180,7 +1192,7 @@ void DRC::testOutline()
     wxPoint error_loc( m_pcb->GetBoardEdgesBoundingBox().GetPosition() );
     if( !m_pcb->GetBoardPolygonOutlines( m_board_outlines, nullptr, &error_loc ) )
     {
-        addMarkerToPcb( newMarker( error_loc, m_pcb, DRCE_INVALID_OUTLINE ) );
+        addMarkerToPcb( m_markerFactory.NewMarker( error_loc, m_pcb, DRCE_INVALID_OUTLINE ) );
         return;
     }
 }
@@ -1195,9 +1207,9 @@ void DRC::testDisabledLayers()
     // Perform the test only for copper layers
     disabledLayers &= LSET::AllCuMask();
 
-    auto createMarker = [&]( BOARD_ITEM* aItem )
-    {
-        addMarkerToPcb( newMarker( aItem->GetPosition(), aItem, DRCE_DISABLED_LAYER_ITEM ) );
+    auto createMarker = [&]( BOARD_ITEM* aItem ) {
+        addMarkerToPcb( m_markerFactory.NewMarker(
+                aItem->GetPosition(), aItem, DRCE_DISABLED_LAYER_ITEM ) );
     };
 
     for( auto track : board->Tracks() )
@@ -1244,7 +1256,8 @@ bool DRC::doTrackKeepoutDrc( TRACK* aRefSeg )
             if( area->Outline()->Distance( SEG( aRefSeg->GetStart(), aRefSeg->GetEnd() ),
                                            aRefSeg->GetWidth() ) == 0 )
             {
-                m_currentMarker = newMarker( aRefSeg, area, DRCE_TRACK_INSIDE_KEEPOUT );
+                m_currentMarker =
+                        m_markerFactory.NewMarker( aRefSeg, area, DRCE_TRACK_INSIDE_KEEPOUT );
                 return false;
             }
         }
@@ -1260,7 +1273,8 @@ bool DRC::doTrackKeepoutDrc( TRACK* aRefSeg )
 
             if( area->Outline()->Distance( aRefSeg->GetPosition() ) < aRefSeg->GetWidth()/2 )
             {
-                m_currentMarker = newMarker( aRefSeg, area, DRCE_VIA_INSIDE_KEEPOUT );
+                m_currentMarker =
+                        m_markerFactory.NewMarker( aRefSeg, area, DRCE_VIA_INSIDE_KEEPOUT );
                 return false;
             }
         }
@@ -1342,7 +1356,7 @@ bool DRC::doPadToPadsDrc( D_PAD* aRefPad, D_PAD** aStart, D_PAD** aEnd, int x_li
                 if( !checkClearancePadToPad( aRefPad, &dummypad ) )
                 {
                     // here we have a drc error on pad!
-                    m_currentMarker = newMarker( pad, aRefPad, DRCE_HOLE_NEAR_PAD );
+                    m_currentMarker = m_markerFactory.NewMarker( pad, aRefPad, DRCE_HOLE_NEAR_PAD );
                     return false;
                 }
             }
@@ -1358,7 +1372,7 @@ bool DRC::doPadToPadsDrc( D_PAD* aRefPad, D_PAD** aStart, D_PAD** aEnd, int x_li
                 if( !checkClearancePadToPad( pad, &dummypad ) )
                 {
                     // here we have a drc error on aRefPad!
-                    m_currentMarker = newMarker( aRefPad, pad, DRCE_HOLE_NEAR_PAD );
+                    m_currentMarker = m_markerFactory.NewMarker( aRefPad, pad, DRCE_HOLE_NEAR_PAD );
                     return false;
                 }
             }
@@ -1393,7 +1407,7 @@ bool DRC::doPadToPadsDrc( D_PAD* aRefPad, D_PAD** aStart, D_PAD** aEnd, int x_li
         if( !checkClearancePadToPad( aRefPad, pad ) )
         {
             // here we have a drc error!
-            m_currentMarker = newMarker( aRefPad, pad, DRCE_PAD_NEAR_PAD1 );
+            m_currentMarker = m_markerFactory.NewMarker( aRefPad, pad, DRCE_PAD_NEAR_PAD1 );
             return false;
         }
     }
@@ -1417,7 +1431,8 @@ bool DRC::doFootprintOverlappingDrc()
 
         if( !is_ok && m_pcb->GetDesignSettings().m_ProhibitOverlappingCourtyards )
         {
-            addMarkerToPcb( newMarker( pos, footprint, DRCE_MALFORMED_COURTYARD_IN_FOOTPRINT ) );
+            addMarkerToPcb( m_markerFactory.NewMarker(
+                    pos, footprint, DRCE_MALFORMED_COURTYARD_IN_FOOTPRINT ) );
             success = false;
         }
 
@@ -1428,7 +1443,8 @@ bool DRC::doFootprintOverlappingDrc()
             footprint->GetPolyCourtyardBack().OutlineCount() == 0 &&
             is_ok )
         {
-            addMarkerToPcb( newMarker( pos, footprint, DRCE_MISSING_COURTYARD_IN_FOOTPRINT ) );
+            addMarkerToPcb( m_markerFactory.NewMarker(
+                    pos, footprint, DRCE_MISSING_COURTYARD_IN_FOOTPRINT ) );
             success = false;
         }
     }
@@ -1462,8 +1478,8 @@ bool DRC::doFootprintOverlappingDrc()
             {
                 //Overlap between footprint and candidate
                 VECTOR2I& pos = courtyard.Vertex( 0, 0, -1 );
-                addMarkerToPcb( newMarker( wxPoint( pos.x, pos.y ), footprint, candidate,
-                                           DRCE_OVERLAPPING_FOOTPRINTS ) );
+                addMarkerToPcb( m_markerFactory.NewMarker( wxPoint( pos.x, pos.y ), footprint,
+                        candidate, DRCE_OVERLAPPING_FOOTPRINTS ) );
                 success = false;
             }
         }
@@ -1493,8 +1509,8 @@ bool DRC::doFootprintOverlappingDrc()
             {
                 //Overlap between footprint and candidate
                 VECTOR2I& pos = courtyard.Vertex( 0, 0, -1 );
-                addMarkerToPcb( newMarker( wxPoint( pos.x, pos.y ), footprint, candidate,
-                                           DRCE_OVERLAPPING_FOOTPRINTS ) );
+                addMarkerToPcb( m_markerFactory.NewMarker( wxPoint( pos.x, pos.y ), footprint,
+                        candidate, DRCE_OVERLAPPING_FOOTPRINTS ) );
                 success = false;
             }
         }
diff --git a/pcbnew/drc.h b/pcbnew/drc.h
index b20600c9c..012d135e5 100644
--- a/pcbnew/drc.h
+++ b/pcbnew/drc.h
@@ -34,6 +34,8 @@
 #include <geometry/seg.h>
 #include <geometry/shape_poly_set.h>
 
+#include <drc/drc_marker_factory.h>
+
 #define OK_DRC  0
 #define BAD_DRC 1
 
@@ -225,6 +227,7 @@ private:
     BOARD*              m_pcb;
     SHAPE_POLY_SET      m_board_outlines;   ///< The board outline including cutouts
     DIALOG_DRC_CONTROL* m_drcDialog;
+    DRC_MARKER_FACTORY  m_markerFactory; ///< Class that generates markers
 
     DRC_LIST            m_unconnected;      ///< list of unconnected pads, as DRC_ITEMs
 
@@ -234,42 +237,6 @@ private:
      */
     void updatePointers();
 
-
-    /**
-     * Function newMarker
-     * Creates a marker on a track, via or pad.
-     *
-     * @param aTrack/aPad The reference item.
-     * @param aConflitItem  Another item on the board which is in conflict with the
-     *                       reference item.
-     * @param aErrorCode An ID for the particular type of error that is being reported.
-     */
-    MARKER_PCB* newMarker( TRACK* aTrack, BOARD_ITEM* aConflitItem, const SEG& aConflictSeg,
-                           int aErrorCode );
-
-    MARKER_PCB* newMarker( TRACK* aTrack, ZONE_CONTAINER* aConflictZone, int aErrorCode );
-
-    MARKER_PCB* newMarker( D_PAD* aPad, BOARD_ITEM* aConflictItem, int aErrorCode );
-
-    /**
-     * Function newMarker
-     * Creates a marker at a given location.
-     *
-     * @param aItem The reference item.
-     * @param aPos Usually the position of the item, but could be more specific for a zone.
-     * @param aErrorCode An ID for the particular type of error that is being reported.
-     */
-    MARKER_PCB* newMarker( const wxPoint& aPos, BOARD_ITEM* aItem, int aErrorCode );
-
-    MARKER_PCB* newMarker( const wxPoint& aPos, BOARD_ITEM* aItem, BOARD_ITEM* bItem,
-                           int aErrorCode );
-
-    /**
-     * Create a MARKER which will report on a generic problem with the board which is
-     * not geographically locatable.
-     */
-    MARKER_PCB* newMarker( int aErrorCode, const wxString& aMessage );
-
     /**
      * Adds a DRC marker to the PCB through the COMMIT mechanism.
      */
diff --git a/pcbnew/drc_marker_functions.cpp b/pcbnew/drc/drc_marker_factory.cpp
similarity index 63%
rename from pcbnew/drc_marker_functions.cpp
rename to pcbnew/drc/drc_marker_factory.cpp
index 2cb9b2e92..0ffbf2f20 100644
--- a/pcbnew/drc_marker_functions.cpp
+++ b/pcbnew/drc/drc_marker_factory.cpp
@@ -1,7 +1,3 @@
-/**
- * @file drc_marker_functions.cpp
- */
-
 /*
  * This program source code file is part of KiCad, a free EDA CAD application.
  *
@@ -27,32 +23,48 @@
  * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
  */
 
+#include "drc_marker_factory.h"
 
-/* Methods of class DRC to initialize drc markers with messages
- * according to items and error code
-*/
-
-#include <fctsys.h>
-#include <common.h>
-#include <pcbnew.h>
 #include <board_design_settings.h>
-#include <geometry/geometry_utils.h>
-#include <pcb_edit_frame.h>
-#include <drc.h>
-#include <class_pad.h>
-#include <class_track.h>
-#include <class_zone.h>
+#include <class_board_item.h>
+#include <class_edge_mod.h>
 #include <class_marker_pcb.h>
+#include <class_pad.h>
 #include <class_pcb_text.h>
 #include <class_text_mod.h>
-#include <class_edge_mod.h>
-#include <class_board_item.h>
+#include <class_track.h>
+#include <class_zone.h>
+#include <common.h>
+#include <drc.h>
+#include <fctsys.h>
+#include <geometry/geometry_utils.h>
+#include <pcb_edit_frame.h>
+#include <pcbnew.h>
 
 
 const int EPSILON = Mils2iu( 5 );
 
 
-MARKER_PCB* DRC::newMarker( TRACK* aTrack, ZONE_CONTAINER* aConflictZone, int aErrorCode )
+DRC_MARKER_FACTORY::DRC_MARKER_FACTORY()
+{
+    SetUnits( EDA_UNITS_T::MILLIMETRES );
+}
+
+
+void DRC_MARKER_FACTORY::SetUnitsProvider( UNITS_PROVIDER aUnitsProvider )
+{
+    m_units_provider = aUnitsProvider;
+}
+
+
+void DRC_MARKER_FACTORY::SetUnits( EDA_UNITS_T aUnits )
+{
+    m_units_provider = [=]() { return aUnits; };
+}
+
+
+MARKER_PCB* DRC_MARKER_FACTORY::NewMarker(
+        TRACK* aTrack, ZONE_CONTAINER* aConflictZone, int aErrorCode ) const
 {
     SHAPE_POLY_SET* conflictOutline;
 
@@ -84,16 +96,15 @@ MARKER_PCB* DRC::newMarker( TRACK* aTrack, ZONE_CONTAINER* aConflictZone, int aE
         markerPos = pt1;
     }
 
-    return new MARKER_PCB( m_pcbEditorFrame->GetUserUnits(), aErrorCode, markerPos,
-                           aTrack, aTrack->GetPosition(),
-                           aConflictZone, aConflictZone->GetPosition() );
+    return new MARKER_PCB( getCurrentUnits(), aErrorCode, markerPos, aTrack, aTrack->GetPosition(),
+            aConflictZone, aConflictZone->GetPosition() );
 }
 
 
-MARKER_PCB* DRC::newMarker( TRACK* aTrack, BOARD_ITEM* aConflitItem, const SEG& aConflictSeg,
-                            int aErrorCode )
+MARKER_PCB* DRC_MARKER_FACTORY::NewMarker(
+        TRACK* aTrack, BOARD_ITEM* aConflitItem, const SEG& aConflictSeg, int aErrorCode ) const
 {
-    wxPoint  markerPos;
+    wxPoint markerPos;
     wxPoint pt1 = aTrack->GetPosition();
     wxPoint pt2 = aTrack->GetEnd();
 
@@ -109,36 +120,36 @@ MARKER_PCB* DRC::newMarker( TRACK* aTrack, BOARD_ITEM* aConflitItem, const SEG&
     // Once we're within EPSILON pt1 and pt2 are "equivalent"
     markerPos = pt1;
 
-    return new MARKER_PCB( m_pcbEditorFrame->GetUserUnits(), aErrorCode, markerPos,
-                           aTrack, aTrack->GetPosition(),
-                           aConflitItem, aConflitItem->GetPosition() );
+    return new MARKER_PCB( getCurrentUnits(), aErrorCode, markerPos, aTrack, aTrack->GetPosition(),
+            aConflitItem, aConflitItem->GetPosition() );
 }
 
 
-MARKER_PCB* DRC::newMarker( D_PAD* aPad, BOARD_ITEM* aConflictItem, int aErrorCode )
+MARKER_PCB* DRC_MARKER_FACTORY::NewMarker(
+        D_PAD* aPad, BOARD_ITEM* aConflictItem, int aErrorCode ) const
 {
-    return new MARKER_PCB( m_pcbEditorFrame->GetUserUnits(), aErrorCode, aPad->GetPosition(),
-                           aPad, aPad->GetPosition(),
-                           aConflictItem, aConflictItem->GetPosition() );
+    return new MARKER_PCB( getCurrentUnits(), aErrorCode, aPad->GetPosition(), aPad,
+            aPad->GetPosition(), aConflictItem, aConflictItem->GetPosition() );
 }
 
 
-MARKER_PCB* DRC::newMarker(const wxPoint &aPos, BOARD_ITEM *aItem, int aErrorCode )
+MARKER_PCB* DRC_MARKER_FACTORY::NewMarker(
+        const wxPoint& aPos, BOARD_ITEM* aItem, int aErrorCode ) const
 {
-    return new MARKER_PCB( m_pcbEditorFrame->GetUserUnits(), aErrorCode, aPos,
-                           aItem, aItem->GetPosition(), nullptr, wxPoint() );
+    return new MARKER_PCB(
+            getCurrentUnits(), aErrorCode, aPos, aItem, aItem->GetPosition(), nullptr, wxPoint() );
 }
 
 
-MARKER_PCB* DRC::newMarker( const wxPoint &aPos, BOARD_ITEM* aItem, BOARD_ITEM* bItem,
-                            int aErrorCode )
+MARKER_PCB* DRC_MARKER_FACTORY::NewMarker(
+        const wxPoint& aPos, BOARD_ITEM* aItem, BOARD_ITEM* bItem, int aErrorCode ) const
 {
-    return new MARKER_PCB( m_pcbEditorFrame->GetUserUnits(), aErrorCode, aPos,
-                           aItem, aItem->GetPosition(), bItem, bItem->GetPosition() );
+    return new MARKER_PCB( getCurrentUnits(), aErrorCode, aPos, aItem, aItem->GetPosition(), bItem,
+            bItem->GetPosition() );
 }
 
 
-MARKER_PCB* DRC::newMarker( int aErrorCode, const wxString& aMessage )
+MARKER_PCB* DRC_MARKER_FACTORY::NewMarker( int aErrorCode, const wxString& aMessage ) const
 {
     MARKER_PCB* marker = new MARKER_PCB( aErrorCode, wxPoint(), aMessage, wxPoint() );
 
@@ -146,5 +157,3 @@ MARKER_PCB* DRC::newMarker( int aErrorCode, const wxString& aMessage )
 
     return marker;
 }
-
-
diff --git a/pcbnew/drc/drc_marker_factory.h b/pcbnew/drc/drc_marker_factory.h
new file mode 100644
index 000000000..7d18e7c70
--- /dev/null
+++ b/pcbnew/drc/drc_marker_factory.h
@@ -0,0 +1,104 @@
+/*
+ * 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
+ */
+
+
+#ifndef DRC_DRC_MARKER_FACTORY__H
+#define DRC_DRC_MARKER_FACTORY__H
+
+
+#include <class_marker_pcb.h>
+#include <common.h> // for EDA_UNITS_T
+
+
+class ZONE_CONTAINER;
+class TRACK;
+class D_PAD;
+class SEG;
+
+/**
+ * Class that constructs DRC markers of various kinds
+ * with messages according to items and error code
+ */
+class DRC_MARKER_FACTORY
+{
+public:
+    using UNITS_PROVIDER = std::function<EDA_UNITS_T()>;
+
+    DRC_MARKER_FACTORY();
+
+    /**
+     * Set the provider for the units (this will be called for each new
+     * marker constructed)
+     * @param aUnitsProvider a callable that returns the current units to use
+     */
+    void SetUnitsProvider( UNITS_PROVIDER aUnitsProvider );
+
+    /**
+     * Set the units provider to a function returning a constant value.
+     * This is a shorthand for #SetUnitProvider.
+     */
+    void SetUnits( EDA_UNITS_T aUnits );
+
+    /**
+     * Creates a marker on a track, via or pad.
+     *
+     * @param aTrack/aPad The reference item.
+     * @param aConflitItem  Another item on the board which is in conflict with the
+     *                       reference item.
+     * @param aErrorCode An ID for the particular type of error that is being reported.
+     */
+    MARKER_PCB* NewMarker( TRACK* aTrack, BOARD_ITEM* aConflitItem, const SEG& aConflictSeg,
+            int aErrorCode ) const;
+
+    MARKER_PCB* NewMarker( TRACK* aTrack, ZONE_CONTAINER* aConflictZone, int aErrorCode ) const;
+
+    MARKER_PCB* NewMarker( D_PAD* aPad, BOARD_ITEM* aConflictItem, int aErrorCode ) const;
+
+    /**
+     * Creates a marker at a given location.
+     *
+     * @param aItem The reference item.
+     * @param aPos Usually the position of the item, but could be more specific for a zone.
+     * @param aErrorCode An ID for the particular type of error that is being reported.
+     */
+    MARKER_PCB* NewMarker( const wxPoint& aPos, BOARD_ITEM* aItem, int aErrorCode ) const;
+
+    MARKER_PCB* NewMarker(
+            const wxPoint& aPos, BOARD_ITEM* aItem, BOARD_ITEM* bItem, int aErrorCode ) const;
+
+    /**
+     * Create a MARKER which will report on a generic problem with the board which is
+     * not geographically locatable.
+     */
+    MARKER_PCB* NewMarker( int aErrorCode, const wxString& aMessage ) const;
+
+private:
+    EDA_UNITS_T getCurrentUnits() const
+    {
+        return m_units_provider();
+    }
+
+    UNITS_PROVIDER m_units_provider;
+};
+
+#endif // DRC_DRC_MARKER_FACTORY__H
\ No newline at end of file
diff --git a/pcbnew/drc_clearance_test_functions.cpp b/pcbnew/drc_clearance_test_functions.cpp
index a3920204a..d6273ecbd 100644
--- a/pcbnew/drc_clearance_test_functions.cpp
+++ b/pcbnew/drc_clearance_test_functions.cpp
@@ -211,7 +211,8 @@ bool DRC::doTrackDrc( TRACK* aRefSeg, TRACK* aStart, bool testPads )
         {
             if( refvia->GetWidth() < dsnSettings.m_MicroViasMinSize )
             {
-                markers.push_back( newMarker( refviaPos, refvia, DRCE_TOO_SMALL_MICROVIA ) );
+                markers.push_back(
+                        m_markerFactory.NewMarker( refviaPos, refvia, DRCE_TOO_SMALL_MICROVIA ) );
 
                 if( !handleNewMarker() )
                     return false;
@@ -219,7 +220,8 @@ bool DRC::doTrackDrc( TRACK* aRefSeg, TRACK* aStart, bool testPads )
 
             if( refvia->GetDrillValue() < dsnSettings.m_MicroViasMinDrill )
             {
-                markers.push_back( newMarker( refviaPos, refvia, DRCE_TOO_SMALL_MICROVIA_DRILL ) );
+                markers.push_back( m_markerFactory.NewMarker(
+                        refviaPos, refvia, DRCE_TOO_SMALL_MICROVIA_DRILL ) );
 
                 if( !handleNewMarker() )
                     return false;
@@ -229,7 +231,8 @@ bool DRC::doTrackDrc( TRACK* aRefSeg, TRACK* aStart, bool testPads )
         {
             if( refvia->GetWidth() < dsnSettings.m_ViasMinSize )
             {
-                markers.push_back( newMarker( refviaPos, refvia, DRCE_TOO_SMALL_VIA ) );
+                markers.push_back(
+                        m_markerFactory.NewMarker( refviaPos, refvia, DRCE_TOO_SMALL_VIA ) );
 
                 if( !handleNewMarker() )
                     return false;
@@ -237,7 +240,8 @@ bool DRC::doTrackDrc( TRACK* aRefSeg, TRACK* aStart, bool testPads )
 
             if( refvia->GetDrillValue() < dsnSettings.m_ViasMinDrill )
             {
-                markers.push_back( newMarker( refviaPos, refvia, DRCE_TOO_SMALL_VIA_DRILL ) );
+                markers.push_back(
+                        m_markerFactory.NewMarker( refviaPos, refvia, DRCE_TOO_SMALL_VIA_DRILL ) );
 
                 if( !handleNewMarker() )
                     return false;
@@ -249,7 +253,8 @@ bool DRC::doTrackDrc( TRACK* aRefSeg, TRACK* aStart, bool testPads )
         // and a default via hole can be bigger than some vias sizes
         if( refvia->GetDrillValue() > refvia->GetWidth() )
         {
-            markers.push_back( newMarker( refviaPos, refvia, DRCE_VIA_HOLE_BIGGER ) );
+            markers.push_back(
+                    m_markerFactory.NewMarker( refviaPos, refvia, DRCE_VIA_HOLE_BIGGER ) );
 
             if( !handleNewMarker() )
                 return false;
@@ -258,7 +263,8 @@ bool DRC::doTrackDrc( TRACK* aRefSeg, TRACK* aStart, bool testPads )
         // test if the type of via is allowed due to design rules
         if( refvia->GetViaType() == VIA_MICROVIA && !dsnSettings.m_MicroViasAllowed )
         {
-            markers.push_back( newMarker( refviaPos, refvia, DRCE_MICRO_VIA_NOT_ALLOWED ) );
+            markers.push_back(
+                    m_markerFactory.NewMarker( refviaPos, refvia, DRCE_MICRO_VIA_NOT_ALLOWED ) );
             if( !handleNewMarker() )
                 return false;
         }
@@ -266,7 +272,8 @@ bool DRC::doTrackDrc( TRACK* aRefSeg, TRACK* aStart, bool testPads )
         // test if the type of via is allowed due to design rules
         if( refvia->GetViaType() == VIA_BLIND_BURIED && !dsnSettings.m_BlindBuriedViaAllowed )
         {
-            markers.push_back( newMarker( refviaPos, refvia, DRCE_BURIED_VIA_NOT_ALLOWED ) );
+            markers.push_back(
+                    m_markerFactory.NewMarker( refviaPos, refvia, DRCE_BURIED_VIA_NOT_ALLOWED ) );
 
             if( !handleNewMarker() )
                 return false;
@@ -292,7 +299,8 @@ bool DRC::doTrackDrc( TRACK* aRefSeg, TRACK* aStart, bool testPads )
 
             if( err )
             {
-                markers.push_back( newMarker( refviaPos, refvia, DRCE_MICRO_VIA_INCORRECT_LAYER_PAIR ) );
+                markers.push_back( m_markerFactory.NewMarker(
+                        refviaPos, refvia, DRCE_MICRO_VIA_INCORRECT_LAYER_PAIR ) );
 
                 if( !handleNewMarker() )
                     return false;
@@ -306,7 +314,8 @@ bool DRC::doTrackDrc( TRACK* aRefSeg, TRACK* aStart, bool testPads )
         {
             wxPoint refsegMiddle = ( aRefSeg->GetStart() + aRefSeg->GetEnd() ) / 2;
 
-            markers.push_back( newMarker( refsegMiddle, aRefSeg, DRCE_TOO_SMALL_TRACK_WIDTH ) );
+            markers.push_back( m_markerFactory.NewMarker(
+                    refsegMiddle, aRefSeg, DRCE_TOO_SMALL_TRACK_WIDTH ) );
 
             if( !handleNewMarker() )
                 return false;
@@ -381,8 +390,8 @@ bool DRC::doTrackDrc( TRACK* aRefSeg, TRACK* aStart, bool testPads )
                 if( !checkClearanceSegmToPad( &dummypad, aRefSeg->GetWidth(),
                                               netclass->GetClearance() ) )
                 {
-                    markers.push_back( newMarker( aRefSeg, pad, padSeg,
-                                                  DRCE_TRACK_NEAR_THROUGH_HOLE ) );
+                    markers.push_back( m_markerFactory.NewMarker(
+                            aRefSeg, pad, padSeg, DRCE_TRACK_NEAR_THROUGH_HOLE ) );
 
                     if( !handleNewMarker() )
                         return false;
@@ -403,7 +412,8 @@ bool DRC::doTrackDrc( TRACK* aRefSeg, TRACK* aStart, bool testPads )
 
             if( !checkClearanceSegmToPad( pad, aRefSeg->GetWidth(), aRefSeg->GetClearance( pad ) ) )
             {
-                markers.push_back( newMarker( aRefSeg, pad, padSeg, DRCE_TRACK_NEAR_PAD ) );
+                markers.push_back(
+                        m_markerFactory.NewMarker( aRefSeg, pad, padSeg, DRCE_TRACK_NEAR_PAD ) );
 
                 if( !handleNewMarker() )
                     return false;
@@ -455,7 +465,8 @@ bool DRC::doTrackDrc( TRACK* aRefSeg, TRACK* aStart, bool testPads )
                 // Test distance between two vias, i.e. two circles, trivial case
                 if( EuclideanNorm( segStartPoint ) < w_dist )
                 {
-                    markers.push_back( newMarker( pos, aRefSeg, track, DRCE_VIA_NEAR_VIA ) );
+                    markers.push_back(
+                            m_markerFactory.NewMarker( pos, aRefSeg, track, DRCE_VIA_NEAR_VIA ) );
 
                     if( !handleNewMarker() )
                         return false;
@@ -472,7 +483,8 @@ bool DRC::doTrackDrc( TRACK* aRefSeg, TRACK* aStart, bool testPads )
 
                 if( !checkMarginToCircle( segStartPoint, w_dist, delta.x ) )
                 {
-                    markers.push_back( newMarker( pos, aRefSeg, track, DRCE_VIA_NEAR_TRACK ) );
+                    markers.push_back(
+                            m_markerFactory.NewMarker( pos, aRefSeg, track, DRCE_VIA_NEAR_TRACK ) );
 
                     if( !handleNewMarker() )
                         return false;
@@ -498,7 +510,8 @@ bool DRC::doTrackDrc( TRACK* aRefSeg, TRACK* aStart, bool testPads )
             if( checkMarginToCircle( segStartPoint, w_dist, m_segmLength ) )
                 continue;
 
-            markers.push_back( newMarker( aRefSeg, track, seg, DRCE_TRACK_NEAR_VIA ) );
+            markers.push_back(
+                    m_markerFactory.NewMarker( aRefSeg, track, seg, DRCE_TRACK_NEAR_VIA ) );
 
             if( !handleNewMarker() )
                 return false;
@@ -526,7 +539,8 @@ bool DRC::doTrackDrc( TRACK* aRefSeg, TRACK* aStart, bool testPads )
                 // Fine test : we consider the rounded shape of each end of the track segment:
                 if( segStartPoint.x >= 0 && segStartPoint.x <= m_segmLength )
                 {
-                    markers.push_back( newMarker( aRefSeg, track, seg, DRCE_TRACK_ENDS1 ) );
+                    markers.push_back(
+                            m_markerFactory.NewMarker( aRefSeg, track, seg, DRCE_TRACK_ENDS1 ) );
 
                     if( !handleNewMarker() )
                         return false;
@@ -534,7 +548,8 @@ bool DRC::doTrackDrc( TRACK* aRefSeg, TRACK* aStart, bool testPads )
 
                 if( !checkMarginToCircle( segStartPoint, w_dist, m_segmLength ) )
                 {
-                    markers.push_back( newMarker( aRefSeg, track, seg, DRCE_TRACK_ENDS2 ) );
+                    markers.push_back(
+                            m_markerFactory.NewMarker( aRefSeg, track, seg, DRCE_TRACK_ENDS2 ) );
 
                     if( !handleNewMarker() )
                         return false;
@@ -549,7 +564,8 @@ bool DRC::doTrackDrc( TRACK* aRefSeg, TRACK* aStart, bool testPads )
                 // Fine test : we consider the rounded shape of the ends
                 if( segEndPoint.x >= 0 && segEndPoint.x <= m_segmLength )
                 {
-                    markers.push_back( newMarker( aRefSeg, track, seg, DRCE_TRACK_ENDS3 ) );
+                    markers.push_back(
+                            m_markerFactory.NewMarker( aRefSeg, track, seg, DRCE_TRACK_ENDS3 ) );
 
                     if( !handleNewMarker() )
                         return false;
@@ -557,7 +573,8 @@ bool DRC::doTrackDrc( TRACK* aRefSeg, TRACK* aStart, bool testPads )
 
                 if( !checkMarginToCircle( segEndPoint, w_dist, m_segmLength ) )
                 {
-                    markers.push_back( newMarker( aRefSeg, track, seg, DRCE_TRACK_ENDS4 ) );
+                    markers.push_back(
+                            m_markerFactory.NewMarker( aRefSeg, track, seg, DRCE_TRACK_ENDS4 ) );
 
                     if( !handleNewMarker() )
                         return false;
@@ -571,7 +588,8 @@ bool DRC::doTrackDrc( TRACK* aRefSeg, TRACK* aStart, bool testPads )
                 // handled)
                 //  X.............X
                 //    O--REF--+
-                markers.push_back( newMarker( aRefSeg, track, seg, DRCE_TRACK_SEGMENTS_TOO_CLOSE ) );
+                markers.push_back( m_markerFactory.NewMarker(
+                        aRefSeg, track, seg, DRCE_TRACK_SEGMENTS_TOO_CLOSE ) );
 
                 if( !handleNewMarker() )
                     return false;
@@ -588,7 +606,8 @@ bool DRC::doTrackDrc( TRACK* aRefSeg, TRACK* aStart, bool testPads )
 
             if( ( segStartPoint.y < 0 ) && ( segEndPoint.y > 0 ) )
             {
-                markers.push_back( newMarker( aRefSeg, track, seg, DRCE_TRACKS_CROSSING ) );
+                markers.push_back(
+                        m_markerFactory.NewMarker( aRefSeg, track, seg, DRCE_TRACKS_CROSSING ) );
 
                 if( !handleNewMarker() )
                     return false;
@@ -597,14 +616,16 @@ bool DRC::doTrackDrc( TRACK* aRefSeg, TRACK* aStart, bool testPads )
             // At this point the drc error is due to an end near a reference segm end
             if( !checkMarginToCircle( segStartPoint, w_dist, m_segmLength ) )
             {
-                markers.push_back( newMarker( aRefSeg, track, seg, DRCE_ENDS_PROBLEM1 ) );
+                markers.push_back(
+                        m_markerFactory.NewMarker( aRefSeg, track, seg, DRCE_ENDS_PROBLEM1 ) );
 
                 if( !handleNewMarker() )
                     return false;
             }
             if( !checkMarginToCircle( segEndPoint, w_dist, m_segmLength ) )
             {
-                markers.push_back( newMarker( aRefSeg, track, seg, DRCE_ENDS_PROBLEM2 ) );
+                markers.push_back(
+                        m_markerFactory.NewMarker( aRefSeg, track, seg, DRCE_ENDS_PROBLEM2 ) );
 
                 if( !handleNewMarker() )
                     return false;
@@ -633,7 +654,8 @@ bool DRC::doTrackDrc( TRACK* aRefSeg, TRACK* aStart, bool testPads )
 
                 if( !checkLine( segStartPoint, segEndPoint ) )
                 {
-                    markers.push_back( newMarker( aRefSeg, track, seg, DRCE_ENDS_PROBLEM3 ) );
+                    markers.push_back(
+                            m_markerFactory.NewMarker( aRefSeg, track, seg, DRCE_ENDS_PROBLEM3 ) );
 
                     if( !handleNewMarker() )
                         return false;
@@ -662,7 +684,8 @@ bool DRC::doTrackDrc( TRACK* aRefSeg, TRACK* aStart, bool testPads )
 
                     if( !checkMarginToCircle( relStartPos, w_dist, delta.x ) )
                     {
-                        markers.push_back( newMarker( aRefSeg, track, seg, DRCE_ENDS_PROBLEM4 ) );
+                        markers.push_back( m_markerFactory.NewMarker(
+                                aRefSeg, track, seg, DRCE_ENDS_PROBLEM4 ) );
 
                         if( !handleNewMarker() )
                             return false;
@@ -670,7 +693,8 @@ bool DRC::doTrackDrc( TRACK* aRefSeg, TRACK* aStart, bool testPads )
 
                     if( !checkMarginToCircle( relEndPos, w_dist, delta.x ) )
                     {
-                        markers.push_back( newMarker( aRefSeg, track, seg, DRCE_ENDS_PROBLEM5 ) );
+                        markers.push_back( m_markerFactory.NewMarker(
+                                aRefSeg, track, seg, DRCE_ENDS_PROBLEM5 ) );
 
                         if( !handleNewMarker() )
                             return false;
@@ -701,7 +725,7 @@ bool DRC::doTrackDrc( TRACK* aRefSeg, TRACK* aStart, bool testPads )
         SHAPE_POLY_SET* outline = const_cast<SHAPE_POLY_SET*>( &zone->GetFilledPolysList() );
 
         if( outline->Distance( refSeg, aRefSeg->GetWidth() ) < clearance )
-            addMarkerToPcb( newMarker( aRefSeg, zone, DRCE_TRACK_NEAR_ZONE ) );
+            addMarkerToPcb( m_markerFactory.NewMarker( aRefSeg, zone, DRCE_TRACK_NEAR_ZONE ) );
     }
 
     /***********************************************/
@@ -721,7 +745,8 @@ bool DRC::doTrackDrc( TRACK* aRefSeg, TRACK* aStart, bool testPads )
             if( test_seg.SquaredDistance( *it ) < w_dist )
             {
                 auto pt = test_seg.NearestPoint( *it );
-                markers.push_back( newMarker( wxPoint( pt.x, pt.y ), aRefSeg, DRCE_TRACK_NEAR_EDGE ) );
+                markers.push_back( m_markerFactory.NewMarker(
+                        wxPoint( pt.x, pt.y ), aRefSeg, DRCE_TRACK_NEAR_EDGE ) );
 
                 if( !handleNewMarker() )
                     return false;
@@ -793,7 +818,8 @@ bool DRC::doEdgeZoneDrc( ZONE_CONTAINER* aArea, int aCornerIndex )
         if( area_to_test->Outline()->Contains( end ) )
         {
             wxPoint pos( end.x, end.y );
-            m_currentMarker = newMarker( pos, aArea, area_to_test, DRCE_ZONES_INTERSECT );
+            m_currentMarker =
+                    m_markerFactory.NewMarker( pos, aArea, area_to_test, DRCE_ZONES_INTERSECT );
             return false;
         }
 
@@ -825,8 +851,8 @@ bool DRC::doEdgeZoneDrc( ZONE_CONTAINER* aArea, int aCornerIndex )
             if( d < zone_clearance )
             {
                 // COPPERAREA_COPPERAREA error : edge intersect or too close
-                m_currentMarker = newMarker( wxPoint( x, y ), aArea, area_to_test,
-                                             DRCE_ZONES_TOO_CLOSE );
+                m_currentMarker = m_markerFactory.NewMarker(
+                        wxPoint( x, y ), aArea, area_to_test, DRCE_ZONES_TOO_CLOSE );
                 return false;
             }
 
-- 
2.20.1

From 305750348e109b5e4e434431956df758e79dc0f6 Mon Sep 17 00:00:00 2001
From: John Beard <john.j.beard@xxxxxxxxx>
Date: Fri, 11 Jan 2019 22:03:45 +0000
Subject: [PATCH 2/4] 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.
---
 common/CMakeLists.txt                         |   1 +
 pcbnew/CMakeLists.txt                         |   2 +
 pcbnew/drc.cpp                                | 105 +---
 pcbnew/drc.h                                  |   4 +-
 pcbnew/drc/courtyard_overlap.cpp              | 168 ++++++
 pcbnew/drc/courtyard_overlap.h                |  44 ++
 pcbnew/drc/drc_provider.cpp                   |  44 ++
 pcbnew/drc/drc_provider.h                     |  84 +++
 qa/CMakeLists.txt                             |   1 +
 qa/pcbnew/CMakeLists.txt                      |  66 +--
 qa/pcbnew/board_test_utils.cpp                |  53 ++
 qa/pcbnew/board_test_utils.h                  |  63 +++
 qa/pcbnew/drc/drc_test_utils.cpp              |  46 ++
 qa/pcbnew/drc/drc_test_utils.h                |  63 +++
 qa/pcbnew/drc/test_drc_courtyard_invalid.cpp  | 341 ++++++++++++
 qa/pcbnew/drc/test_drc_courtyard_overlap.cpp  | 499 ++++++++++++++++++
 qa/pcbnew_utils/CMakeLists.txt                | 101 ++++
 qa/pcbnew_utils/board_construction_utils.cpp  | 112 ++++
 qa/pcbnew_utils/board_file_utils.cpp          |  53 ++
 .../pcbnew_utils/board_construction_utils.h   |  89 ++++
 .../include/pcbnew_utils/board_file_utils.h   |  67 +++
 qa/unit_test_utils/CMakeLists.txt             |   6 +-
 .../include/unit_test_utils/unit_test_utils.h |  80 +++
 23 files changed, 1949 insertions(+), 143 deletions(-)
 create mode 100644 pcbnew/drc/courtyard_overlap.cpp
 create mode 100644 pcbnew/drc/courtyard_overlap.h
 create mode 100644 pcbnew/drc/drc_provider.cpp
 create mode 100644 pcbnew/drc/drc_provider.h
 create mode 100644 qa/pcbnew/board_test_utils.cpp
 create mode 100644 qa/pcbnew/board_test_utils.h
 create mode 100644 qa/pcbnew/drc/drc_test_utils.cpp
 create mode 100644 qa/pcbnew/drc/drc_test_utils.h
 create mode 100644 qa/pcbnew/drc/test_drc_courtyard_invalid.cpp
 create mode 100644 qa/pcbnew/drc/test_drc_courtyard_overlap.cpp
 create mode 100644 qa/pcbnew_utils/CMakeLists.txt
 create mode 100644 qa/pcbnew_utils/board_construction_utils.cpp
 create mode 100644 qa/pcbnew_utils/board_file_utils.cpp
 create mode 100644 qa/pcbnew_utils/include/pcbnew_utils/board_construction_utils.h
 create mode 100644 qa/pcbnew_utils/include/pcbnew_utils/board_file_utils.h

diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt
index 33c98c74b..5935f720f 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 f20df851c..a64da3bb5 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 5c4a400b4..6808636fc 100644
--- a/pcbnew/drc.cpp
+++ b/pcbnew/drc.cpp
@@ -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;
-
-    // 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;
-            }
-        }
-    }
+    DRC_COURTYARD_OVERLAP drc_overlap(
+            m_markerFactory, [&]( MARKER_PCB* aMarker ) { addMarkerToPcb( aMarker ); } );
 
-    return success;
+    drc_overlap.RunDRC( *m_pcb );
 }
diff --git a/pcbnew/drc.h b/pcbnew/drc.h
index 012d135e5..83c4a810e 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();
 
     //-----<single tests>----------------------------------------------
 
diff --git a/pcbnew/drc/courtyard_overlap.cpp b/pcbnew/drc/courtyard_overlap.cpp
new file mode 100644
index 000000000..4b383cb17
--- /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@xxxxxxxxxxx
+ * 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;
+}
diff --git a/pcbnew/drc/courtyard_overlap.h b/pcbnew/drc/courtyard_overlap.h
new file mode 100644
index 000000000..3e5461856
--- /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 <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
\ 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 000000000..6b7d0c9d6
--- /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/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() );
+}
diff --git a/pcbnew/drc/drc_provider.h b/pcbnew/drc/drc_provider.h
new file mode 100644
index 000000000..65080d044
--- /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 <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
\ No newline at end of file
diff --git a/qa/CMakeLists.txt b/qa/CMakeLists.txt
index 1c774d838..1f22f174a 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 abaaa9f1f..cac74d2ae 100644
--- a/qa/pcbnew/CMakeLists.txt
+++ b/qa/pcbnew/CMakeLists.txt
@@ -24,9 +24,6 @@ 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,56 +31,47 @@ 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
+
+    # Unsure how to make this a proper library - can't make it link
+    # and find Pgm() and friends, so we'll just have to build twice...
+    ${QA_PCBNEW_UTILS_SRCS}
+
     # 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"
+# )
 
-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"
+target_include_directories( qa_pcbnew PRIVATE
+    BEFORE ${INC_BEFORE}
 )
 
-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_include_directories( qa_pcbnew PRIVATE
+    ${QA_PCBNEW_UTILS_INCLUDE_DIRS}
 )
 
 target_link_libraries( qa_pcbnew
-    3d-viewer
-    connectivity
-    pcbcommon
-    pnsrouter
-    pcad2kicadpcb
-    common
-    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
+    # qa_pcbnew_utils
+    ${QA_PCBNEW_UTILS_LIBS}
 )
 
 # we need to pretend to be something to appease the units code
diff --git a/qa/pcbnew/board_test_utils.cpp b/qa/pcbnew/board_test_utils.cpp
new file mode 100644
index 000000000..0aca168e2
--- /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 <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
\ 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 000000000..1cce18371
--- /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 <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
\ 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 000000000..b5455f429
--- /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 000000000..8e1c3428a
--- /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 <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
\ 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 000000000..4f9be1fc6
--- /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 <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()
\ 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 000000000..6b03d3242
--- /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 <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()
\ 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 000000000..5e587df0f
--- /dev/null
+++ b/qa/pcbnew_utils/CMakeLists.txt
@@ -0,0 +1,101 @@
+# 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.
+
+if( BUILD_GITHUB_PLUGIN )
+    set( GITHUB_PLUGIN_LIBRARIES github_plugin )
+endif()
+
+set( QA_PCBNEW_UTILS_SRCS
+
+    # stuff from common due to...units?
+    # ../../common/eda_text.cpp
+
+    # stuff from common which is needed...why?
+    # ../../common/colors.cpp
+    # ../../common/observable.cpp
+
+    ${CMAKE_CURRENT_SOURCE_DIR}/board_construction_utils.cpp
+    ${CMAKE_CURRENT_SOURCE_DIR}/board_file_utils.cpp
+
+    PARENT_SCOPE
+)
+
+# add_library( qa_pcbnew_utils ${QA_PCBNEW_UTILS_SRCS} )
+
+# target_include_directories( qa_pcbnew_utils PUBLIC
+set( QA_PCBNEW_UTILS_INCLUDE_DIRS
+    ${CMAKE_CURRENT_SOURCE_DIR}/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}
+
+    PARENT_SCOPE
+)
+
+# target_link_libraries( qa_pcbnew_utils PUBLIC
+set( QA_PCBNEW_UTILS_LIBS
+    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
+
+    PARENT_SCOPE
+)
+
+# # 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 000000000..46e1e43c9
--- /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 <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
diff --git a/qa/pcbnew_utils/board_file_utils.cpp b/qa/pcbnew_utils/board_file_utils.cpp
new file mode 100644
index 000000000..c278c22ed
--- /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 <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
\ 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 000000000..fa0229e75
--- /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 <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
\ 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 000000000..8543487fd
--- /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 <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
\ No newline at end of file
diff --git a/qa/unit_test_utils/CMakeLists.txt b/qa/unit_test_utils/CMakeLists.txt
index 3fbfa05b8..8ad23bb17 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 160d01d55..2d2467570 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
@@ -77,4 +77,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
\ No newline at end of file
-- 
2.20.1


Follow ups