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 +#include +#include +#include + +#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 ## _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(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 + struct reg_entry : public reg_entry_base + { + using reg_entry_base::reg_entry_base; + + virtual Test *create() override { return new T(); } + }; + + template + 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 \ + 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 // scanf #include // scanf #include @@ -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({"run","validate","convert","listdevices","static","header","docheader"}), "run|validate|convert|listdevices|static|header|docheader"), + opt_cmd (*this, "c", "cmd", 0, std::vector({"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(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("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("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("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("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("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("0!=a", {"a"})({1.0})); +} +