← Back to team overview

kicad-developers team mailing list archive

[PATCH] Add some testing utils and COLOR4D tests

 

Hi,

Here are some more patches for unit tests.

The first introduces some generic utils for unit tests as a library,
which simplifies the making of a unit test executable slightly and
makes a place for handy generic unit test constructs and compiler
options.

One of these is a way to define "expected failures", which should be
done inside a protective #ifdef for Boosts < 1.59. This allows to flag
up a bug (and demonstrate that the test actually covers that bug)
before fixing, but does *not* break `make test`. Without this, the
only way to avoid a commit that fails `make test` is squashing the
flag-up and the fix together, which deletes this information from the
Git history (all you see is the fixed code and a new, passing, test).

On old Boosts, these tests are simply skipped. I think this is an
acceptable trade-off against a more complex method of supporting two
Boost APIs for expected failures. Expected failures should be fixed
ASAP, so there should be very few, if any, legacy Boost builds of
commits with "missing" tests like this.

Then, some tests are added for COLOR4D. One of these tests exposes a
bug in COLOR4D::ToColour(), which is then marked with expected failure
and fixed in a follow-up commit.

Fun fact: making the implicit double->unsigned char casts explicit in
this same function saves around 800kB of compiler spew on MSVC -
around 10% of the total output.

Both patches Jenkins'd on MSVC and Msys2.

Cheers,

John
From 6c0cf6634c78ec55291844ba480c552201fac9b0 Mon Sep 17 00:00:00 2001
From: John Beard <john.j.beard@xxxxxxxxx>
Date: Wed, 7 Nov 2018 13:55:20 +0000
Subject: [PATCH 1/2] QA: Make a separate unit test utils library, COLOR4D
 tests

This includes:

* Linkage against the Boost unit test libs
* Configuration of the Boost libs
* A place for common generics "extras" for unit test harnesses
  including
    * A simple way to allow "expected-failure" tests (without
      breaking Boost < 1.58, e.g. Ubuntu LTS)
    * Moving some simple numeric predicates from the geom tests
      to the utils library.

Expand unit test docs to describe the expected failures macro.

Add a few COLOR4D tests, including one with expected failures due
to a pre-existing bug. This will be fixed in a follow-up commit.
---
 Documentation/development/testing.md          |  44 +++
 qa/CMakeLists.txt                             |   1 +
 qa/common/CMakeLists.txt                      |   9 +-
 qa/common/geometry/geom_test_utils.h          |  51 +---
 qa/common/geometry/test_fillet.cpp            |  20 +-
 qa/common/test_color4d.cpp                    | 289 ++++++++++++++++++
 qa/unit_test_utils/CMakeLists.txt             |  47 +++
 .../include/unit_test_utils/numeric.h         |  74 +++++
 .../include/unit_test_utils/unit_test_utils.h |  53 ++++
 qa/unit_test_utils/unit_test_utils.cpp        |  26 ++
 10 files changed, 554 insertions(+), 60 deletions(-)
 create mode 100644 qa/common/test_color4d.cpp
 create mode 100644 qa/unit_test_utils/CMakeLists.txt
 create mode 100644 qa/unit_test_utils/include/unit_test_utils/numeric.h
 create mode 100644 qa/unit_test_utils/include/unit_test_utils/unit_test_utils.h
 create mode 100644 qa/unit_test_utils/unit_test_utils.cpp

diff --git a/Documentation/development/testing.md b/Documentation/development/testing.md
index 0b47d13a1..81fd22701 100644
--- a/Documentation/development/testing.md
+++ b/Documentation/development/testing.md
@@ -70,6 +70,50 @@ messages inside tested functions (i.e. where you don't have access to the Boost
 unit test headers). These will always be printed, so take care
 to remove them before committing, or they'll show up when KiCad runs normally!
 
+### Expected failures {#expected-failures}
+
+Sometimes, it is helpful to check in tests that do not pass. However, it is bad
+practise to intentionally check in commits that break builds (which is what
+happens if you cause `make test` to fail).
+
+Boost provides a method of declaring that some specific tests are allowed to fail.
+This syntax is not consistently available in all supported Boost versions, so you
+should use the following construct:
+
+```
+#include <unit_test_utils/unit_test_utils.h>
+
+// On platforms with older boosts, the test will be excluded entirely
+#ifdef HAVE_EXPECTED_FAILURES
+
+// Declare a test case with 1 "allowed" failure (out of 2, in this case)
+BOOST_AUTO_TEST_CASE( SomeTest, *boost::unit_test::expected_failures( 1 ) )
+{
+    BOOST_CHECK_EQUAL( 1, 1 );
+
+    // This check fails, but does not cause a test suite failure
+    BOOST_CHECK_EQUAL( 1, 2 );
+
+    // Further failures *would* be a test suit failure
+}
+
+#endif
+```
+
+When run, this produces output somewhat like this:
+
+```
+qa/common/test_mytest.cpp(123): error: in "MyTests/SomeTest": check 1 == 2 has failed [1 != 2
+*** No errors detected
+```
+
+And the unit test executable returns `0` (success).
+
+Checking in a failing test is a strictly temporary situation, used to illustrate
+the triggering of a bug prior to fixing it. This is advantageous, not only from
+a "project history" perspective, but also to ensure that the test you write to
+catch the bug in question does, in fact, catch the bug in the first place.
+
 ## Python modules {#python-tests}
 
 The Pcbnew Python modules have some test programs in the `qa` directory.
diff --git a/qa/CMakeLists.txt b/qa/CMakeLists.txt
index ae30e1a38..086bf144d 100644
--- a/qa/CMakeLists.txt
+++ b/qa/CMakeLists.txt
@@ -14,6 +14,7 @@ endif()
 
 # common QA helpers
 add_subdirectory( qa_utils )
+add_subdirectory( unit_test_utils )
 
 add_subdirectory( common )
 add_subdirectory( shape_poly_set_refactor )
diff --git a/qa/common/CMakeLists.txt b/qa/common/CMakeLists.txt
index ad17eeaab..f6a7298cc 100644
--- a/qa/common/CMakeLists.txt
+++ b/qa/common/CMakeLists.txt
@@ -19,12 +19,8 @@
 # or you may write to the Free Software Foundation, Inc.,
 # 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 
-find_package(Boost COMPONENTS unit_test_framework REQUIRED)
 find_package( wxWidgets 3.0.0 COMPONENTS gl aui adv html core net base xml stc REQUIRED )
 
-
-add_definitions(-DBOOST_TEST_DYN_LINK)
-
 add_executable( qa_common
     # This is needed for the global mock objects
     common_mocks.cpp
@@ -39,6 +35,7 @@ add_executable( qa_common
     ../../common/colors.cpp
     ../../common/observable.cpp
 
+    test_color4d.cpp
     test_hotkey_store.cpp
     test_title_block.cpp
     test_utf8.cpp
@@ -50,7 +47,6 @@ include_directories(
     ${CMAKE_SOURCE_DIR}
     ${CMAKE_SOURCE_DIR}/include
     ${CMAKE_SOURCE_DIR}/polygon
-    ${Boost_INCLUDE_DIR}
     ${INC_AFTER}
 )
 
@@ -60,7 +56,8 @@ target_link_libraries( qa_common
     polygon
     bitmaps
     gal
-    ${Boost_UNIT_TEST_FRAMEWORK_LIBRARY}
+    qa_utils
+    unit_test_utils
     ${wxWidgets_LIBRARIES}
 )
 
diff --git a/qa/common/geometry/geom_test_utils.h b/qa/common/geometry/geom_test_utils.h
index d598a24df..5f79edc68 100644
--- a/qa/common/geometry/geom_test_utils.h
+++ b/qa/common/geometry/geom_test_utils.h
@@ -26,54 +26,17 @@
 
 #include <math.h>
 
-/**
- * @brief Utility functions for testing geometry functions.
- */
-namespace GEOM_TEST
-{
-
-/**
- * @brief Check if a value is within a tolerance of a nominal value
- *
-* @return value is in [aNominal - aError, aNominal + aError]
- */
-template<typename T>
-bool IsWithin( T aValue, T aNominal, T aError )
-{
-    return ( aValue >= aNominal - aError )
-            && ( aValue <= aNominal + aError );
-}
-
-/**
- * @brief Check if a value is within a tolerance of a nominal value,
- * with different allowances for errors above and below.
- *
- * @return value is in [aNominal - aErrorBelow, aNominal + aErrorAbove]
- */
-template<typename T>
-bool IsWithin( T aValue, T aNominal, T aErrorAbove, T aErrorBelow )
-{
-    return ( aValue >= aNominal - aErrorBelow )
-            && ( aValue <= aNominal + aErrorAbove );
-}
+#include <geometry/seg.h>
+#include <geometry/shape_line_chain.h>
+#include <geometry/shape_poly_set.h>
 
-/**
- * @brief value is in range [aNominal - aErrorBelow, aNominal]
- */
-template<typename T>
-bool IsWithinAndBelow( T aValue, T aNominal, T aErrorBelow )
-{
-    return IsWithin( aValue, aNominal, 0, aErrorBelow );
-}
+#include <unit_test_utils/numeric.h>
 
 /**
- * @brief value is in range [aNominal, aNominal + aErrorAbove]
+ * @brief Utility functions for testing geometry functions.
  */
-template<typename T>
-bool IsWithinAndAbove( T aValue, T aNominal, T aErrorAbove )
+namespace GEOM_TEST
 {
-    return IsWithin( aValue, aNominal, aErrorAbove, 0 );
-}
 
 /**
  * @brief Geometric quadrants, from top-right, anti-clockwise
@@ -163,7 +126,7 @@ bool ArePerpendicular( const VECTOR2<T>& a, const VECTOR2<T>& b, double aToleran
         angle -= M_PI;
     }
 
-    return IsWithin( angle, M_PI / 2.0, aTolerance );
+    return KI_TEST::IsWithin( angle, M_PI / 2.0, aTolerance );
 }
 
 /**
diff --git a/qa/common/geometry/test_fillet.cpp b/qa/common/geometry/test_fillet.cpp
index 119db6b19..a3521d09e 100644
--- a/qa/common/geometry/test_fillet.cpp
+++ b/qa/common/geometry/test_fillet.cpp
@@ -24,6 +24,8 @@
 #include <boost/test/unit_test.hpp>
 #include <boost/test/test_case_template.hpp>
 
+#include <unit_test_utils/unit_test_utils.h>
+
 #include <geometry/shape_poly_set.h>
 #include <geometry/shape_line_chain.h>
 
@@ -51,26 +53,24 @@ BOOST_FIXTURE_TEST_SUITE( Fillet, FilletFixture )
 void TestFilletSegmentConstraints( const SEG& aSeg, VECTOR2I aRadCentre,
     int aRadius, int aError )
 {
-    using namespace GEOM_TEST;
-
     const auto diffA = aRadCentre - aSeg.A;
     const auto diffB = aRadCentre - aSeg.B;
     const auto diffC = aRadCentre - aSeg.Center();
 
     // Check 1: radii (error of 1 for rounding)
-    BOOST_CHECK_PREDICATE( IsWithinAndBelow<int>,
-        ( diffA.EuclideanNorm() )( aRadius )( 1 ) );
-    BOOST_CHECK_PREDICATE( IsWithinAndBelow<int>,
-        ( diffB.EuclideanNorm() )( aRadius )( 1 ) );
+    BOOST_CHECK_PREDICATE(
+            KI_TEST::IsWithinAndBelow<int>, ( diffA.EuclideanNorm() )( aRadius )( 1 ) );
+    BOOST_CHECK_PREDICATE(
+            KI_TEST::IsWithinAndBelow<int>, ( diffB.EuclideanNorm() )( aRadius )( 1 ) );
 
     // Check 2: Mid-point error
-    BOOST_CHECK_PREDICATE( IsWithinAndBelow<int>,
-        ( diffC.EuclideanNorm() )( aRadius )( aError + 1 ) );
+    BOOST_CHECK_PREDICATE(
+            KI_TEST::IsWithinAndBelow<int>, ( diffC.EuclideanNorm() )( aRadius )( aError + 1 ) );
 
     // Check 3: Mid-point -> radius centre perpendicular
     const auto perpendularityMaxError = ( M_PI / 2 ) / 10;
-    BOOST_CHECK_PREDICATE( ArePerpendicular<int>,
-        ( diffC )( aSeg.A - aSeg.B )( perpendularityMaxError ) );
+    BOOST_CHECK_PREDICATE( GEOM_TEST::ArePerpendicular<int>,
+            ( diffC )( aSeg.A - aSeg.B )( perpendularityMaxError ) );
 }
 
 
diff --git a/qa/common/test_color4d.cpp b/qa/common/test_color4d.cpp
new file mode 100644
index 000000000..940fb0ad1
--- /dev/null
+++ b/qa/common/test_color4d.cpp
@@ -0,0 +1,289 @@
+/*
+ * 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 <boost/test/test_case_template.hpp>
+#include <boost/test/unit_test.hpp>
+
+#include <unit_test_utils/numeric.h>
+#include <unit_test_utils/unit_test_utils.h>
+
+#include <gal/color4d.h>
+
+#ifdef WX_COMPATIBILITY
+#include <wx/colour.h>
+#endif
+
+// All these tests are of a class in KIGFX
+using namespace KIGFX;
+
+
+/**
+ * Checks if a COLOR4D is close enough to another
+ */
+bool pred_colour_is_near( const COLOR4D& aCol, const COLOR4D aOther, double aTol )
+{
+    return KI_TEST::IsWithin<double>( aCol.r, aOther.r, aTol )
+           && KI_TEST::IsWithin<double>( aCol.g, aOther.g, aTol )
+           && KI_TEST::IsWithin<double>( aCol.b, aOther.b, aTol )
+           && KI_TEST::IsWithin<double>( aCol.a, aOther.a, aTol );
+}
+
+/**
+ * Checks if a COLOR4D is close enough to a given RGB char value
+ */
+bool pred_colour_is_near_hex(
+        const COLOR4D& aCol, unsigned char r, unsigned char g, unsigned char b, unsigned char a )
+{
+    const double tol = 0.5 / 255.0; // One bit of quantised error
+
+    return KI_TEST::IsWithin<double>( aCol.r, r / 255.0, tol )
+           && KI_TEST::IsWithin<double>( aCol.g, g / 255.0, tol )
+           && KI_TEST::IsWithin<double>( aCol.b, b / 255.0, tol )
+           && KI_TEST::IsWithin<double>( aCol.a, a / 255.0, tol );
+}
+
+
+/**
+ * Declares a struct as the Boost test fixture.
+ */
+BOOST_AUTO_TEST_SUITE( Color4D )
+
+
+/**
+ * Check basic setting and getting of values
+ */
+BOOST_AUTO_TEST_CASE( BasicOps )
+{
+    const auto c = COLOR4D{ 0.4, 0.5, 0.6, 0.7 };
+
+    BOOST_CHECK_EQUAL( c.r, 0.4 );
+    BOOST_CHECK_EQUAL( c.g, 0.5 );
+    BOOST_CHECK_EQUAL( c.b, 0.6 );
+    BOOST_CHECK_EQUAL( c.a, 0.7 );
+
+    const auto copied = c;
+
+    // Test equality
+    BOOST_CHECK_EQUAL( c, copied );
+
+    const auto c2 = COLOR4D{ 0.1, 0.2, 0.3, 0.4 };
+
+    // Test inequality
+    BOOST_CHECK_NE( c, c2 );
+}
+
+
+/**
+ * Test case data for a test that takes a colour and a scalar factor
+ * and returns a result.
+ */
+struct COLOR_SCALAR_CASE
+{
+    COLOR4D start;
+    double  factor;
+    COLOR4D expected;
+};
+
+
+/**
+ * Check inversion
+ */
+BOOST_AUTO_TEST_CASE( Invert )
+{
+    // Inverts RGB, A is the same
+    static const std::vector<COLOR_SCALAR_CASE> cases = {
+        { { 0.0, 0.25, 1.0, 1.0 }, 0.0, { 1.0, 0.75, 0.0, 1.0 } },
+    };
+
+    for( const auto& c : cases )
+    {
+        auto col = c.start;
+
+        const auto inverted = col.Inverted();
+        BOOST_CHECK_EQUAL( inverted, c.expected );
+
+        // Test in-place function
+        col.Invert();
+        BOOST_CHECK_EQUAL( col, c.expected );
+    }
+}
+
+
+/**
+ * Check inversion
+ */
+BOOST_AUTO_TEST_CASE( Brighten )
+{
+    static const std::vector<COLOR_SCALAR_CASE> cases = {
+        { { 0.0, 0.0, 0.0, 1.0 }, 0.5, { 0.5, 0.5, 0.5, 1.0 } },
+        { { 0.0, 0.5, 1.0, 1.0 }, 0.5, { 0.5, 0.75, 1.0, 1.0 } },
+    };
+
+    for( const auto& c : cases )
+    {
+        auto col = c.start;
+
+        const auto brightened = col.Brightened( c.factor );
+        BOOST_CHECK_EQUAL( brightened, c.expected );
+
+        // Test in-place function
+        col.Brighten( c.factor );
+        BOOST_CHECK_EQUAL( col, c.expected );
+    }
+}
+
+
+/**
+ * Check darken
+ */
+BOOST_AUTO_TEST_CASE( Darken )
+{
+    static const std::vector<COLOR_SCALAR_CASE> cases = {
+        { { 0.0, 0.0, 0.0, 1.0 }, 0.5, { 0.0, 0.0, 0.0, 1.0 } },
+        { { 1.0, 1.0, 1.0, 1.0 }, 0.5, { 0.5, 0.5, 0.5, 1.0 } },
+    };
+
+    for( const auto& c : cases )
+    {
+        auto col = c.start;
+
+        const auto brightened = col.Darkened( c.factor );
+        BOOST_CHECK_EQUAL( brightened, c.expected );
+
+        // Test in-place function
+        col.Darken( c.factor );
+        BOOST_CHECK_EQUAL( col, c.expected );
+    }
+}
+
+
+/**
+ * Check alpha setting
+ */
+BOOST_AUTO_TEST_CASE( WithAlpha )
+{
+    static const std::vector<COLOR_SCALAR_CASE> cases = {
+        { { 0.0, 0.0, 0.0, 1.0 }, 0.5, { 0.0, 0.0, 0.0, 0.5 } },
+        { { 0.0, 0.5, 1.0, 1.0 }, 0.5, { 0.0, 0.5, 1.0, 0.5 } },
+    };
+
+    for( const auto& c : cases )
+    {
+        auto col = c.start;
+
+        const auto with_alpha = col.WithAlpha( c.factor );
+        BOOST_CHECK_EQUAL( with_alpha, c.expected );
+    }
+
+    // Note: If COLOR4D::WithAlpha raised an exception, we could check
+    // the bounds-checking with BOOST_REQUIRE_THROW,
+    // but it assert()s, so we can't.
+}
+
+struct FROM_HSV_TO_HEX_CASE
+{
+    double        h;
+    double        s;
+    double        v;
+    unsigned char r;
+    unsigned char g;
+    unsigned char b;
+};
+
+
+/**
+ * Check FromHSV
+ */
+BOOST_AUTO_TEST_CASE( FromHsv )
+{
+    static const std::vector<FROM_HSV_TO_HEX_CASE> cases = {
+        { 90.0, 0.5, 0.5, 96, 128, 64 },
+    };
+
+    for( const auto& c : cases )
+    {
+        auto col = COLOR4D{};
+        col.FromHSV( c.h, c.s, c.v );
+        const unsigned char alpha = 0xFF;
+
+        BOOST_CHECK_PREDICATE( pred_colour_is_near_hex, ( col )( c.r )( c.g )( c.b )( alpha ) );
+    }
+}
+
+
+#ifdef WX_COMPATIBILITY
+
+struct WX_CONV_CASE
+{
+    wxColour wx;
+    COLOR4D  c4d;
+};
+
+
+static std::vector<WX_CONV_CASE> wx_conv_cases = {
+    { { 0x00, 0x00, 0x00, 0x00 }, { 0.0, 0.0, 0.0, 0.0 } },
+    { { 0x66, 0x80, 0x99, 0xB3 }, { 0.4, 0.5, 0.6, 0.7 } },
+    { { 0xFF, 0xFF, 0xFF, 0xFF }, { 1.0, 1.0, 1.0, 1.0 } },
+    { { 0xFF, 0x00, 0x00, 0xFF }, { 0.999, 0.001, 0.0, 1.0 } },
+};
+
+#ifdef HAVE_EXPECTED_FAILURES
+
+/**
+ * Check conversion to WxColour
+ */
+BOOST_AUTO_TEST_CASE( ToWx, *boost::unit_test::expected_failures( 3 ) )
+{
+    for( const auto& c : wx_conv_cases )
+    {
+        wxColour wx_col = c.c4d.ToColour();
+
+        // A hack, but avoids having to define a custom operator<<
+        BOOST_CHECK_EQUAL( wx_col.Red(), c.wx.Red() );
+        BOOST_CHECK_EQUAL( wx_col.Green(), c.wx.Green() );
+        BOOST_CHECK_EQUAL( wx_col.Blue(), c.wx.Blue() );
+        BOOST_CHECK_EQUAL( wx_col.Alpha(), c.wx.Alpha() );
+    }
+}
+
+#endif // HAVE_EXPECTED_FAILURES
+
+
+/**
+ * Check conversion from WxColour
+ */
+BOOST_AUTO_TEST_CASE( FromWx )
+{
+    const double tol = 0.5 / 255.0; // One bit of quantised error
+
+    for( const auto& c : wx_conv_cases )
+    {
+        const auto col = COLOR4D{ c.wx };
+
+        BOOST_CHECK_PREDICATE( pred_colour_is_near, ( col )( c.c4d )( tol ) );
+    }
+}
+
+#endif // WX_COMPATIBILITY
+
+BOOST_AUTO_TEST_SUITE_END()
diff --git a/qa/unit_test_utils/CMakeLists.txt b/qa/unit_test_utils/CMakeLists.txt
new file mode 100644
index 000000000..3fbfa05b8
--- /dev/null
+++ b/qa/unit_test_utils/CMakeLists.txt
@@ -0,0 +1,47 @@
+# 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
+
+# The unit_test_utils library is a simple helper library to collate
+# utilities that are generically useful for unit test executables.
+#
+# 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)
+
+set(SRCS
+    unit_test_utils.cpp
+)
+
+add_library( unit_test_utils STATIC ${SRCS} )
+
+target_link_libraries( unit_test_utils PUBLIC
+    ${Boost_UNIT_TEST_FRAMEWORK_LIBRARY}
+)
+
+target_include_directories( unit_test_utils PUBLIC
+    include
+    ${Boost_INCLUDE_DIR}
+)
+
+target_compile_definitions( unit_test_utils PUBLIC
+    BOOST_TEST_DYN_LINK
+)
\ No newline at end of file
diff --git a/qa/unit_test_utils/include/unit_test_utils/numeric.h b/qa/unit_test_utils/include/unit_test_utils/numeric.h
new file mode 100644
index 000000000..cfda976f1
--- /dev/null
+++ b/qa/unit_test_utils/include/unit_test_utils/numeric.h
@@ -0,0 +1,74 @@
+/*
+ * 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
+ */
+
+/**
+ * @file numeric.h
+ * Numerical test predicates.
+ */
+
+#ifndef NUMERIC__H
+#define NUMERIC__H
+
+namespace KI_TEST
+{
+
+/**
+ * Check if a value is within a tolerance of a nominal value
+ *
+ * @return value is in [aNominal - aError, aNominal + aError]
+ */
+template <typename T> bool IsWithin( T aValue, T aNominal, T aError )
+{
+    return ( aValue >= aNominal - aError ) && ( aValue <= aNominal + aError );
+}
+
+/**
+ * Check if a value is within a tolerance of a nominal value,
+ * with different allowances for errors above and below.
+ *
+ * @return value is in [aNominal - aErrorBelow, aNominal + aErrorAbove]
+ */
+template <typename T> bool IsWithinBounds( T aValue, T aNominal, T aErrorAbove, T aErrorBelow )
+{
+    return ( aValue >= aNominal - aErrorBelow ) && ( aValue <= aNominal + aErrorAbove );
+}
+
+/**
+ * value is in range [aNominal - aErrorBelow, aNominal]
+ */
+template <typename T> bool IsWithinAndBelow( T aValue, T aNominal, T aErrorBelow )
+{
+    return IsWithinBounds( aValue, aNominal, 0, aErrorBelow );
+}
+
+/**
+ * value is in range [aNominal, aNominal + aErrorAbove]
+ */
+template <typename T> bool IsWithinAndAbove( T aValue, T aNominal, T aErrorAbove )
+{
+    return IsWithinBounds( aValue, aNominal, aErrorAbove, 0 );
+}
+
+} // namespace KI_TEST
+
+#endif // NUMERIC__H
\ No newline at end of file
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
new file mode 100644
index 000000000..8efcd46f2
--- /dev/null
+++ b/qa/unit_test_utils/include/unit_test_utils/unit_test_utils.h
@@ -0,0 +1,53 @@
+/*
+ * 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 UNIT_TEST_UTILS__H
+#define UNIT_TEST_UTILS__H
+
+#include <functional>
+
+/**
+ * If HAVE_EXPECTED_FAILURES is defined, this means that
+ * boost::unit_test::expected_failures is available.
+ *
+ * Wrap expected-to-fail tests with this to prevent them being compiled
+ * on platforms with older (<1.59) Boost versions.
+ *
+ * This can be removed when our minimum boost version is 1.59 or higher.
+ */
+#if BOOST_VERSION >= 105900
+#define HAVE_EXPECTED_FAILURES
+#endif
+
+/**
+ * BOOST_TEST, while extremely handy, is not available in Boost < 1.59.
+ * Undef it here to prevent use. Using it can cause older packaging like
+ * Ubuntu LTS (which is on Boost 1.58) to fail.
+ *
+ * Use BOOST_CHECK_{EQUAL,NE,etc} instead.
+ *
+ * This can be removed when our minimum boost version is 1.59 or higher.
+ */
+#undef BOOST_TEST
+
+#endif // UNIT_TEST_UTILS__H
\ No newline at end of file
diff --git a/qa/unit_test_utils/unit_test_utils.cpp b/qa/unit_test_utils/unit_test_utils.cpp
new file mode 100644
index 000000000..e96ba6c09
--- /dev/null
+++ b/qa/unit_test_utils/unit_test_utils.cpp
@@ -0,0 +1,26 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 1992-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
+ */
+
+/*
+ * Nothing here yet, but CMake requires *something*.
+ */
\ No newline at end of file
-- 
2.19.1

From d9f5ca908c740a843ea4db6aed7237931b4cf146 Mon Sep 17 00:00:00 2001
From: John Beard <john.j.beard@xxxxxxxxx>
Date: Thu, 8 Nov 2018 13:28:42 +0000
Subject: [PATCH 2/2] Fix rounding in COLOR4D::ToColour

Due to the implicit floor of the cast from double to unsigned char,
there was a small rounding error in COLOUR4D's WX conversion function.

This fixes the failing tests.

Also make the cast to unsigned char explicit.
---
 common/gal/color4d.cpp     | 14 ++++++++++++++
 include/gal/color4d.h      |  6 +-----
 qa/common/test_color4d.cpp |  5 +----
 3 files changed, 16 insertions(+), 9 deletions(-)

diff --git a/common/gal/color4d.cpp b/common/gal/color4d.cpp
index b7653f3cc..d4abcf5c0 100644
--- a/common/gal/color4d.cpp
+++ b/common/gal/color4d.cpp
@@ -79,6 +79,20 @@ COLOR4D::COLOR4D( EDA_COLOR_T aColor )
     }
 
 
+    wxColour COLOR4D::ToColour() const
+    {
+        using CHAN_T = wxColourBase::ChannelType;
+
+        const wxColour colour(
+            static_cast<CHAN_T>( r * 255 + 0.5 ),
+            static_cast<CHAN_T>( g * 255 + 0.5 ),
+            static_cast<CHAN_T>( b * 255 + 0.5 ),
+            static_cast<CHAN_T>( a * 255 + 0.5 )
+        );
+        return colour;
+    }
+
+
     COLOR4D COLOR4D::LegacyMix( COLOR4D aColor ) const
     {
         COLOR4D candidate;
diff --git a/include/gal/color4d.h b/include/gal/color4d.h
index c5ff3ccc5..a5bf00c86 100644
--- a/include/gal/color4d.h
+++ b/include/gal/color4d.h
@@ -89,11 +89,7 @@ public:
 
     wxString ToWxString( long flags ) const;
 
-    wxColour ToColour() const
-    {
-        wxColour colour( r * 255, g * 255, b * 255, a * 255 );
-        return colour;
-    }
+    wxColour ToColour() const;
 
     /**
      * Function LegacyMix()
diff --git a/qa/common/test_color4d.cpp b/qa/common/test_color4d.cpp
index 940fb0ad1..9d9354c4a 100644
--- a/qa/common/test_color4d.cpp
+++ b/qa/common/test_color4d.cpp
@@ -247,12 +247,11 @@ static std::vector<WX_CONV_CASE> wx_conv_cases = {
     { { 0xFF, 0x00, 0x00, 0xFF }, { 0.999, 0.001, 0.0, 1.0 } },
 };
 
-#ifdef HAVE_EXPECTED_FAILURES
 
 /**
  * Check conversion to WxColour
  */
-BOOST_AUTO_TEST_CASE( ToWx, *boost::unit_test::expected_failures( 3 ) )
+BOOST_AUTO_TEST_CASE( ToWx )
 {
     for( const auto& c : wx_conv_cases )
     {
@@ -266,8 +265,6 @@ BOOST_AUTO_TEST_CASE( ToWx, *boost::unit_test::expected_failures( 3 ) )
     }
 }
 
-#endif // HAVE_EXPECTED_FAILURES
-
 
 /**
  * Check conversion from WxColour
-- 
2.19.1


Follow ups