test: add tests for ExprParser, and fix two crashes.

pull/3/merge
whitequark 2016-12-13 11:06:29 +00:00
parent 11adaf20b7
commit d6e2ac8328
6 changed files with 131 additions and 0 deletions

View File

@ -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);

View File

@ -96,6 +96,7 @@ public:
Expr *DeepCopyWithParamsAsPointers(IdList<Param,hParam> *firstTry,
IdList<Param,hParam> *thenTry) const;
static Expr *Parse(const char *input, std::string *error);
static Expr *From(const char *in, bool popUpError);
};

View File

@ -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

106
test/core/expr/test.cpp Normal file
View File

@ -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 ')'");
}

View File

@ -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);

View File

@ -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) \