netlist: Add basic unit testing support.

* Add google test syntax compatible unit testing support. This is a very
limited subset of the google test framework and not intended ever to be
a replacement. Adding a dependency to google test for the functionality
required was considered to be an overkill.
* nltool -c tests runs unit tests if linked in. This is *not* the case
for the version of nltool compiled with TOOLS=1.
* Added unit tests for plib::pfunction.
This commit is contained in:
couriersud 2020-07-05 11:40:22 +02:00
parent ad6505ba63
commit 9e86f5e866
4 changed files with 205 additions and 5 deletions

View File

@ -103,6 +103,7 @@ TARGETS = nltool$(EXESUFFIX) nlwav$(EXESUFFIX)
NLOBJ = $(OBJ)
POBJ = $(OBJ)/plib
TESTOBJ = $(OBJ)/tests
DEPEND = $(OBJ)/.depend
@ -113,12 +114,13 @@ OBJDIRS = $(OBJ) \
$(OBJ)/plib \
$(OBJ)/devices \
$(OBJ)/macro \
$(OBJ)/tests \
$(OBJ)/tools \
$(OBJ)/prg \
$(OBJ)/generated \
OBJS = $(POBJS) $(NLOBJS)
OBJS = $(POBJS) $(NLOBJS) $(TESTOBJS)
POBJS := \
$(POBJ)/pstring.o \
@ -212,6 +214,9 @@ NLOBJS := \
$(NLOBJ)/macro/nlm_roms.o \
$(NLOBJ)/macro/nlm_ttl74xx.o \
TESTOBJS := \
$(TESTOBJ)/test_pfunction.o \
VSBUILDS = \
$(VSBUILD/netlistlib.vcxproj) \
$(VSBUILD/netlistlib.vcxproj.user \
@ -234,7 +239,7 @@ ALL_TIDY_FILES = $(ALL_OBJS:.o=.json)
SOURCES = $(patsubst $(OBJ)%, $(SRC)%, $(ALL_OBJS:.o=.cpp))
ALLFILES = $(SOURCES) $(VSBUILDS) $(DOCS)
MAKEFILE_TARGETS_WITHOUT_INCLUDE := gcc9 clang clang-5 mingw doc native maketree tidy
MAKEFILE_TARGETS_WITHOUT_INCLUDE := gcc9 clang clang-5 mingw doc native maketree tidy runtests
# git archive HEAD --prefix=project-name-version/ \
@ -244,7 +249,7 @@ MAKEFILE_TARGETS_WITHOUT_INCLUDE := gcc9 clang clang-5 mingw doc native maketree
# PHONY
#-------------------------------------------------
.PHONY: all gcc9 clang clang-5 mingw doc native maketree $(DEPEND) depend
.PHONY: all gcc9 clang clang-5 mingw doc native maketree $(DEPEND) depend runtests
#-------------------------------------------------
# all
@ -328,6 +333,9 @@ nvcc:
-Xcompiler -O6 -Xcompiler -march=native -ccbin g++-8 " \
DEPENDCC=g++
runtests:
./nltool$(EXESUFFIX) -c tests
tidy_db: compile_commands_prefix $(ALL_TIDY_FILES) compile_commands_postfix
#

View File

@ -0,0 +1,140 @@
// license:GPL-2.0+
// copyright-holders:Couriersud
#ifndef PTESTS_H_
#define PTESTS_H_
///
/// \file ptests.h
///
/// google tests compatible (hopefully) test macros. This is work in progress!
///
#include <string>
#include <vector>
#include <iostream>
#include <functional>
#define EXPECT_EQ(exp1, exp2) PINT_EXPECT(eq, exp1, exp2)
#define EXPECT_NE(exp1, exp2) PINT_EXPECT(ne, exp1, exp2)
#define EXPECT_GT(exp1, exp2) PINT_EXPECT(gt, exp1, exp2)
#define EXPECT_LT(exp1, exp2) PINT_EXPECT(lt, exp1, exp2)
#define EXPECT_GE(exp1, exp2) PINT_EXPECT(ge, exp1, exp2)
#define EXPECT_LE(exp1, exp2) PINT_EXPECT(le, exp1, exp2)
#define EXPECT_TRUE(exp1) PINT_EXPECT(eq, exp1, true)
#define EXPECT_FALSE(exp1) PINT_EXPECT(eq, exp1, false)
#define TEST(name, desc) PINT_TEST(name, desc)
#define TEST_F(name, desc) PINT_TEST_F(name, desc, name)
#define RUN_ALL_TESTS() plib::testing::run_all_tests()
#define PINT_TEST(name, desc) PINT_TEST_F(name, desc, plib::testing::Test)
#define PINT_TEST_F(name, desc, base) \
class name ## _ ## desc : public base \
{ public:\
void desc (); \
void run() override { desc (); } \
}; \
plib::testing::reg_entry<name ## _ ## desc> name ## _ ## desc ## _reg(#name, #desc); \
void name ## _ ## desc :: desc ()
#define PINT_EXPECT(comp, exp1, exp2) \
if (!plib::testing::internal_assert(plib::testing::comp_ ## comp (), # exp1, # exp2, exp1, exp2)) \
std::cout << __FILE__ << ":" << __LINE__ << ":1: error: test failed\n";
namespace plib
{
namespace testing
{
class Test
{
public:
virtual ~Test() {}
virtual void run() {}
virtual void SetUp() {}
virtual void TearDown() {}
};
struct reg_entry_base
{
using list_t = std::vector<reg_entry_base *>;
reg_entry_base(const std::string &n, const std::string &d)
: name(n), desc(d)
{
registry().push_back(this);
}
virtual ~reg_entry_base() = default;
virtual Test *create() { return nullptr; }
std::string name;
std::string desc;
public:
static list_t & registry()
{
static list_t prlist;
return prlist;
}
};
template <typename T>
struct reg_entry : public reg_entry_base
{
using reg_entry_base::reg_entry_base;
virtual Test *create() override { return new T(); }
};
template <typename C, typename T1, typename T2>
bool internal_assert(C comp,
const char* exp1, const char* exp2,
const T1& val1, const T2& val2)
{
if (comp(val1, val2))
{
std::cout << "\tOK: " << exp1 << " " << C::opstr() << " " << exp2 << "\n";
return true;
}
std::cout << "\tFAIL: " << exp1 << " " << C::opstr() << " " << exp2
<< " <" << val1 << ">,<" << val2 << ">\n";
return false;
}
static inline int run_all_tests()
{
for (auto &e : reg_entry_base::registry())
{
std::cout << e->name << "::" << e->desc << ":\n";
Test *t = e->create();
t->SetUp();
t->run();
t->TearDown();
delete t;
}
return 0;
}
#define DEF_COMP(name, op) \
struct comp_ ## name \
{ \
static const char * opstr() { return #op ; } \
template <typename T1, typename T2> \
bool operator()(const T1 &v1, const T2 &v2) { return v1 op v2; } \
}; \
DEF_COMP(eq, ==)
DEF_COMP(ne, !=)
DEF_COMP(gt, >)
DEF_COMP(lt, <)
DEF_COMP(ge, >=)
DEF_COMP(le, <=)
#undef DEF_COMP
} // namespace testing
} // namespace plib
#endif // PTESTS_H_

View File

@ -20,6 +20,8 @@
#include "netlist/solver/nld_solver.h"
#include "netlist/tools/nl_convert.h"
#include "plib/ptests.h"
#include <cstdio> // scanf
#include <iomanip> // scanf
#include <ios>
@ -44,7 +46,7 @@ public:
m_errors(0),
opt_grp1(*this, "General options", "The following options apply to all commands."),
opt_cmd (*this, "c", "cmd", 0, std::vector<pstring>({"run","validate","convert","listdevices","static","header","docheader"}), "run|validate|convert|listdevices|static|header|docheader"),
opt_cmd (*this, "c", "cmd", 0, std::vector<pstring>({"run","validate","convert","listdevices","static","header","docheader","tests"}), "run|validate|convert|listdevices|static|header|docheader|tests"),
opt_includes(*this, "I", "include", "Add the directory to the list of directories to be searched for header files. This option may be specified repeatedly."),
opt_defines(*this, "D", "define", "predefine value as macro, e.g. -Dname=value. If '=value' is omitted predefine it as 1. This option may be specified repeatedly."),
opt_rfolders(*this, "r", "rom", "where to look for data files"),
@ -94,7 +96,9 @@ public:
opt_ex3(*this, "nltool --cmd=header --tab-width=8 --line-width=80",
"Create the header file needed for including netlists as code."),
opt_ex4(*this, "nltool --cmd static --output src/lib/netlist/generated/static_solvers.cpp src/mame/audio/nl_*.cpp src/mame/machine/nl_*.cpp",
"Create static solvers for the MAME project.")
"Create static solvers for the MAME project."),
opt_ex5(*this, "nltool --cmd tests",
"Run unit tests. In case the unit tests are not linked in, this will do nothing.")
{}
int execute() override;
@ -158,6 +162,7 @@ private:
plib::option_example opt_ex2;
plib::option_example opt_ex3;
plib::option_example opt_ex4;
plib::option_example opt_ex5;
struct compile_map_entry
{
@ -1267,6 +1272,10 @@ int tool_app_t::execute()
create_docheader();
else if (cmd == "convert")
convert();
else if (cmd == "tests")
{
return RUN_ALL_TESTS();
}
else
{
perr("Unknown command {}\n", cmd.c_str());

View File

@ -0,0 +1,43 @@
// license:GPL-2.0+
// copyright-holders:Couriersud
///
/// \file pfunction_test.cpp
///
/// tests for pfunction
///
#include "plib/ptests.h"
#include "plib/pfunction.h"
#define PFUNCEXPECT(formula, val) \
EXPECT_EQ(val, plib::pfunction<double>(formula)());
TEST(pfunction, operators)
{
PFUNCEXPECT("1==1", 1.0)
PFUNCEXPECT("1 *0 == 2-1-1", 1.0)
PFUNCEXPECT("0!=1", 1.0)
PFUNCEXPECT("0<1", 1.0)
PFUNCEXPECT("1>0", 1.0)
PFUNCEXPECT("0<=1", 1.0)
PFUNCEXPECT("1>=0", 1.0)
PFUNCEXPECT("1<=1", 1.0)
PFUNCEXPECT("1>=1", 1.0)
EXPECT_EQ(1.0, plib::pfunction<double>("0!=a", {"a"})({1.0}));
}
TEST(pfunction, func_if)
{
PFUNCEXPECT("if(1>0, 2, 0)", 2.0)
PFUNCEXPECT("if(0>1, 2, 3)", 3.0)
PFUNCEXPECT("if(sin(1)>0, 2, 3)", 3.0) // fail
EXPECT_EQ( 1.0, plib::pfunction<double>("if(A2>2.5, 0-A1, (0.07-(0.005*A1))*if(A0>2.5,1,0-1))", {"A0","A1","A2"})({1.0,-1.0,3.0}));
EXPECT_EQ(-0.065, plib::pfunction<double>("if(A2>2.5, 0-A1, (0.07-(0.005*A1))*if(A0>2.5,1,0-1))", {"A0","A1","A2"})({1.0,1.0,1.0}));
EXPECT_EQ( 0.065, plib::pfunction<double>("if(A2>2.5, 0-A1, (0.07-(0.005*A1))*if(A0>2.5,1,0-1))", {"A0","A1","A2"})({3.0,1.0,1.0}));
//EXPECT(plib::pfunction<double>("if(A2>2.5, A1, if(A0>2.5,1,(0-1)))", {"A0","A1","A2"})({1.0,1.0,1.0}), -1.0)
//PFUNCEXPECT("-1>-2", 1.0)
EXPECT_TRUE(1.0 == plib::pfunction<double>("0!=a", {"a"})({1.0}));
}