From d6e2ac8328b9d501a6ff15a79fb117fac6b10f28 Mon Sep 17 00:00:00 2001 From: whitequark Date: Tue, 13 Dec 2016 11:06:29 +0000 Subject: [PATCH] test: add tests for ExprParser, and fix two crashes. --- src/expr.cpp | 6 +++ src/expr.h | 1 + test/CMakeLists.txt | 1 + test/core/expr/test.cpp | 106 ++++++++++++++++++++++++++++++++++++++++ test/harness.cpp | 12 +++++ test/harness.h | 5 ++ 6 files changed, 131 insertions(+) create mode 100644 test/core/expr/test.cpp diff --git a/src/expr.cpp b/src/expr.cpp index 745a739a..e00a1143 100644 --- a/src/expr.cpp +++ b/src/expr.cpp @@ -682,6 +682,7 @@ ExprParser::Token ExprParser::Lex(std::string *error) { Token t = Token::From(); char c = PeekChar(); if(isupper(c)) { + std::string n = ReadWord(); t = Token::From(TokenType::OPERAND, Expr::Op::VARIABLE); } else if(isalpha(c)) { std::string s = ReadWord(); @@ -796,6 +797,7 @@ bool ExprParser::Reduce(std::string *error) { switch(op.expr->op) { case Expr::Op::NEGATE: e = e->Negate(); break; case Expr::Op::SQRT: e = e->Sqrt(); break; + case Expr::Op::SQUARE: e = e->Times(e); break; case Expr::Op::SIN: e = e->Times(Expr::From(PI/180))->Sin(); break; case Expr::Op::COS: e = e->Times(Expr::From(PI/180))->Cos(); break; case Expr::Op::ASIN: e = e->ASin()->Times(Expr::From(180/PI)); break; @@ -883,6 +885,10 @@ Expr *ExprParser::Parse(const char *input, std::string *error) { return r.expr; } +Expr *Expr::Parse(const char *input, std::string *error) { + return ExprParser::Parse(input, error); +} + Expr *Expr::From(const char *input, bool popUpError) { std::string error; Expr *e = ExprParser::Parse(input, &error); diff --git a/src/expr.h b/src/expr.h index b02d903a..ea113105 100644 --- a/src/expr.h +++ b/src/expr.h @@ -96,6 +96,7 @@ public: Expr *DeepCopyWithParamsAsPointers(IdList *firstTry, IdList *thenTry) const; + static Expr *Parse(const char *input, std::string *error); static Expr *From(const char *in, bool popUpError); }; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index ca91188c..271751d9 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -11,6 +11,7 @@ endforeach() set(testsuite_SOURCES harness.cpp + core/expr/test.cpp constraint/points_coincident/test.cpp constraint/pt_pt_distance/test.cpp constraint/pt_plane_distance/test.cpp diff --git a/test/core/expr/test.cpp b/test/core/expr/test.cpp new file mode 100644 index 00000000..ab3829b5 --- /dev/null +++ b/test/core/expr/test.cpp @@ -0,0 +1,106 @@ +#include "harness.h" + +#define CHECK_PARSE(var, expr) \ + do { \ + var = Expr::From(expr, false); \ + CHECK_TRUE(var != NULL); \ + } while(0) + +#define CHECK_PARSE_ERR(expr, msg) \ + do { \ + std::string err; \ + Expr *e = Expr::Parse(expr, &err); \ + CHECK_TRUE(e == NULL); \ + CHECK_TRUE(err.find(msg) != std::string::npos); \ + } while(0) + +TEST_CASE(constant) { + Expr *e; + CHECK_PARSE(e, "pi"); + CHECK_EQ_EPS(e->Eval(), 3.1415926); +} + +TEST_CASE(literal) { + Expr *e; + CHECK_PARSE(e, "42"); + CHECK_TRUE(e->Eval() == 42); + CHECK_PARSE(e, "42.5"); + CHECK_TRUE(e->Eval() == 42.5); +} + +TEST_CASE(unary_ops) { + Expr *e; + CHECK_PARSE(e, "-10"); + CHECK_TRUE(e->Eval() == -10); +} + +TEST_CASE(binary_ops) { + Expr *e; + CHECK_PARSE(e, "1 + 2"); + CHECK_TRUE(e->Eval() == 3); + CHECK_PARSE(e, "1 - 2"); + CHECK_TRUE(e->Eval() == -1); + CHECK_PARSE(e, "3 * 4"); + CHECK_TRUE(e->Eval() == 12); + CHECK_PARSE(e, "3 / 4"); + CHECK_TRUE(e->Eval() == 0.75); +} + +TEST_CASE(parentheses) { + Expr *e; + CHECK_PARSE(e, "(1 + 2) * 3"); + CHECK_TRUE(e->Eval() == 9); + CHECK_PARSE(e, "1 + (2 * 3)"); + CHECK_TRUE(e->Eval() == 7); +} + +TEST_CASE(functions) { + Expr *e; + CHECK_PARSE(e, "sqrt(2)"); + CHECK_EQ_EPS(e->Eval(), 1.414213); + CHECK_PARSE(e, "square(3)"); + CHECK_EQ_EPS(e->Eval(), 9); + CHECK_PARSE(e, "sin(180)"); + CHECK_EQ_EPS(e->Eval(), 0); + CHECK_PARSE(e, "sin(90)"); + CHECK_EQ_EPS(e->Eval(), 1); + CHECK_PARSE(e, "cos(180)"); + CHECK_EQ_EPS(e->Eval(), -1); + CHECK_PARSE(e, "asin(1)"); + CHECK_EQ_EPS(e->Eval(), 90); + CHECK_PARSE(e, "acos(0)"); + CHECK_EQ_EPS(e->Eval(), 90); +} + +TEST_CASE(variable) { + Expr *e; + CHECK_PARSE(e, "Var"); + CHECK_TRUE(e->op == Expr::Op::VARIABLE); +} + +TEST_CASE(precedence) { + Expr *e; + CHECK_PARSE(e, "2 + 3 * 4"); + CHECK_TRUE(e->Eval() == 14); + CHECK_PARSE(e, "2 - 3 / 4"); + CHECK_TRUE(e->Eval() == 1.25); + CHECK_PARSE(e, "-3 + 2"); + CHECK_TRUE(e->Eval() == -1); + CHECK_PARSE(e, "2 + 3 - 4"); + CHECK_TRUE(e->Eval() == 1); +} + +TEST_CASE(errors) { + CHECK_PARSE_ERR("\x01", + "Unexpected character"); + CHECK_PARSE_ERR("notavar", + "'notavar' is not a valid variable, function or constant"); + CHECK_PARSE_ERR("_", + "'_' is not a valid operator"); + CHECK_PARSE_ERR("2 2", + "Expected an operator"); + CHECK_PARSE_ERR("2 + +", + "Expected an operand"); + CHECK_PARSE_ERR("( 2 + 2", + "Expected ')'"); +} diff --git a/test/harness.cpp b/test/harness.cpp index 7f3a8f04..213768cc 100644 --- a/test/harness.cpp +++ b/test/harness.cpp @@ -178,6 +178,18 @@ bool Test::Helper::CheckTrue(const char *file, int line, const char *expr, bool } } +bool Test::Helper::CheckEqualEpsilon(const char *file, int line, const char *valueExpr, + double value, double reference) { + bool result = fabs(value - reference) < LENGTH_EPS; + if(!RecordCheck(result)) { + std::string msg = ssprintf("(%s) = %.4g ≉ %.4g", valueExpr, value, reference); + PrintFailure(file, line, msg); + return false; + } else { + return true; + } +} + bool Test::Helper::CheckLoad(const char *file, int line, const char *fixture) { std::string fixturePath = GetAssetPath(file, fixture); diff --git a/test/harness.h b/test/harness.h index 74798e01..9e434a21 100644 --- a/test/harness.h +++ b/test/harness.h @@ -22,6 +22,8 @@ public: std::string mangle = ""); bool CheckTrue(const char *file, int line, const char *expr, bool result); + bool CheckEqualEpsilon(const char *file, int line, const char *valueExpr, + double value, double reference); bool CheckLoad(const char *file, int line, const char *fixture); bool CheckSave(const char *file, int line, const char *reference); bool CheckRender(const char *file, int line, const char *fixture); @@ -51,6 +53,9 @@ using namespace SolveSpace; #define CHECK_TRUE(cond) \ do { if(!helper->CheckTrue(__FILE__, __LINE__, #cond, cond)) return; } while(0) +#define CHECK_EQ_EPS(value, reference) \ + do { if(!helper->CheckEqualEpsilon(__FILE__, __LINE__, \ + #value, value, reference)) return; } while(0) #define CHECK_LOAD(fixture) \ do { if(!helper->CheckLoad(__FILE__, __LINE__, fixture)) return; } while(0) #define CHECK_SAVE(fixture) \