summaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
author couriersud <couriersud@gmx.org>2020-07-05 11:40:22 +0200
committer couriersud <couriersud@gmx.org>2020-07-05 11:48:07 +0200
commit9e86f5e86612c4f61b27e5ada86ac9bdebcbc272 (patch)
treed40c51579d7575c0e4547dd17321e89450d39236
parentad6505ba636ecf8336eae91dce0b20bd5055b4ad (diff)
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.
-rw-r--r--src/lib/netlist/build/makefile14
-rw-r--r--src/lib/netlist/plib/ptests.h140
-rw-r--r--src/lib/netlist/prg/nltool.cpp13
-rw-r--r--src/lib/netlist/tests/test_pfunction.cpp43
4 files changed, 205 insertions, 5 deletions
diff --git a/src/lib/netlist/build/makefile b/src/lib/netlist/build/makefile
index 1d442953fb6..3591c5317b2 100644
--- a/src/lib/netlist/build/makefile
+++ b/src/lib/netlist/build/makefile
@@ -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
#
diff --git a/src/lib/netlist/plib/ptests.h b/src/lib/netlist/plib/ptests.h
new file mode 100644
index 00000000000..1f4bbd5e866
--- /dev/null
+++ b/src/lib/netlist/plib/ptests.h
@@ -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_
diff --git a/src/lib/netlist/prg/nltool.cpp b/src/lib/netlist/prg/nltool.cpp
index e177b1e0429..d849e15c755 100644
--- a/src/lib/netlist/prg/nltool.cpp
+++ b/src/lib/netlist/prg/nltool.cpp
@@ -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());
diff --git a/src/lib/netlist/tests/test_pfunction.cpp b/src/lib/netlist/tests/test_pfunction.cpp
new file mode 100644
index 00000000000..847865b4a4f
--- /dev/null
+++ b/src/lib/netlist/tests/test_pfunction.cpp
@@ -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}));
+}
+