Merge remote-tracking branch 'origin/master' into blender2.8
[blender.git] / tests / gtests / blenlib / BLI_expr_pylike_eval_test.cc
1 /* Apache License, Version 2.0 */
2
3 #include "testing/testing.h"
4
5 #include <string.h>
6
7 extern "C" {
8 #include "BLI_expr_pylike_eval.h"
9 #include "BLI_math.h"
10 };
11
12 #define TRUE_VAL 1.0
13 #define FALSE_VAL 0.0
14
15 static void expr_pylike_parse_fail_test(const char *str)
16 {
17         ExprPyLike_Parsed *expr = BLI_expr_pylike_parse(str, NULL, 0);
18
19         EXPECT_FALSE(BLI_expr_pylike_is_valid(expr));
20
21         BLI_expr_pylike_free(expr);
22 }
23
24 static void expr_pylike_const_test(const char *str, double value, bool force_const)
25 {
26         ExprPyLike_Parsed *expr = BLI_expr_pylike_parse(str, NULL, 0);
27
28         if (force_const) {
29                 EXPECT_TRUE(BLI_expr_pylike_is_constant(expr));
30         }
31         else {
32                 EXPECT_TRUE(BLI_expr_pylike_is_valid(expr));
33                 EXPECT_FALSE(BLI_expr_pylike_is_constant(expr));
34         }
35
36         double result;
37         eExprPyLike_EvalStatus status = BLI_expr_pylike_eval(expr, NULL, 0, &result);
38
39         EXPECT_EQ(status, EXPR_PYLIKE_SUCCESS);
40         EXPECT_EQ(result, value);
41
42         BLI_expr_pylike_free(expr);
43 }
44
45 static ExprPyLike_Parsed *parse_for_eval(const char *str, bool nonconst)
46 {
47         const char *names[1] = {"x"};
48         ExprPyLike_Parsed *expr = BLI_expr_pylike_parse(str, names, ARRAY_SIZE(names));
49
50         EXPECT_TRUE(BLI_expr_pylike_is_valid(expr));
51
52         if (nonconst) {
53                 EXPECT_FALSE(BLI_expr_pylike_is_constant(expr));
54         }
55
56         return expr;
57 }
58
59 static void verify_eval_result(ExprPyLike_Parsed *expr, double x, double value)
60 {
61         double result;
62         eExprPyLike_EvalStatus status = BLI_expr_pylike_eval(expr, &x, 1, &result);
63
64         EXPECT_EQ(status, EXPR_PYLIKE_SUCCESS);
65         EXPECT_EQ(result, value);
66 }
67
68 static void expr_pylike_eval_test(const char *str, double x, double value)
69 {
70         ExprPyLike_Parsed *expr = parse_for_eval(str, true);
71         verify_eval_result(expr, x, value);
72         BLI_expr_pylike_free(expr);
73 }
74
75 static void expr_pylike_error_test(const char *str, double x, eExprPyLike_EvalStatus error)
76 {
77         ExprPyLike_Parsed *expr = parse_for_eval(str, false);
78
79         double result;
80         eExprPyLike_EvalStatus status = BLI_expr_pylike_eval(expr, &x, 1, &result);
81
82         EXPECT_EQ(status, error);
83
84         BLI_expr_pylike_free(expr);
85 }
86
87 #define TEST_PARSE_FAIL(name, str) \
88         TEST(expr_pylike, ParseFail_##name) { expr_pylike_parse_fail_test(str); }
89
90 TEST_PARSE_FAIL(Empty, "")
91 TEST_PARSE_FAIL(ConstHex, "0x0")
92 TEST_PARSE_FAIL(ConstOctal, "01")
93 TEST_PARSE_FAIL(Tail, "0 0")
94 TEST_PARSE_FAIL(ConstFloatExp, "0.5e+")
95 TEST_PARSE_FAIL(BadId, "Pi")
96 TEST_PARSE_FAIL(BadArgCount0, "sqrt")
97 TEST_PARSE_FAIL(BadArgCount1, "sqrt()")
98 TEST_PARSE_FAIL(BadArgCount2, "sqrt(1,2)")
99 TEST_PARSE_FAIL(BadArgCount3, "pi()")
100 TEST_PARSE_FAIL(BadArgCount4, "max()")
101 TEST_PARSE_FAIL(BadArgCount5, "min()")
102
103 TEST_PARSE_FAIL(Truncated1, "(1+2")
104 TEST_PARSE_FAIL(Truncated2, "1 if 2")
105 TEST_PARSE_FAIL(Truncated3, "1 if 2 else")
106 TEST_PARSE_FAIL(Truncated4, "1 < 2 <")
107 TEST_PARSE_FAIL(Truncated5, "1 +")
108 TEST_PARSE_FAIL(Truncated6, "1 *")
109 TEST_PARSE_FAIL(Truncated7, "1 and")
110 TEST_PARSE_FAIL(Truncated8, "1 or")
111 TEST_PARSE_FAIL(Truncated9, "sqrt(1")
112 TEST_PARSE_FAIL(Truncated10, "fmod(1,")
113
114 /* Constant expression with working constant folding */
115 #define TEST_CONST(name, str, value) \
116         TEST(expr_pylike, Const_##name) { expr_pylike_const_test(str, value, true); }
117
118 /* Constant expression but constant folding is not supported */
119 #define TEST_RESULT(name, str, value) \
120         TEST(expr_pylike, Result_##name) { expr_pylike_const_test(str, value, false); }
121
122 /* Expression with an argument */
123 #define TEST_EVAL(name, str, x, value) \
124         TEST(expr_pylike, Eval_##name) { expr_pylike_eval_test(str, x, value); }
125
126 TEST_CONST(Zero, "0", 0.0)
127 TEST_CONST(Zero2, "00", 0.0)
128 TEST_CONST(One, "1", 1.0)
129 TEST_CONST(OneF, "1.0", 1.0)
130 TEST_CONST(OneF2, "1.", 1.0)
131 TEST_CONST(OneE, "1e0", 1.0)
132 TEST_CONST(TenE, "1.e+1", 10.0)
133 TEST_CONST(Half, ".5", 0.5)
134
135 TEST_CONST(Pi, "pi", M_PI)
136 TEST_CONST(True, "True", TRUE_VAL)
137 TEST_CONST(False, "False", FALSE_VAL)
138
139 TEST_CONST(Sqrt, "sqrt(4)", 2.0)
140 TEST_EVAL(Sqrt, "sqrt(x)", 4.0, 2.0)
141
142 TEST_CONST(FMod, "fmod(3.5, 2)", 1.5)
143 TEST_EVAL(FMod, "fmod(x, 2)", 3.5, 1.5)
144
145 TEST_CONST(Pow, "pow(4, 0.5)", 2.0)
146 TEST_EVAL(Pow, "pow(4, x)", 0.5, 2.0)
147
148 TEST_RESULT(Min1, "min(3,1,2)", 1.0)
149 TEST_RESULT(Max1, "max(3,1,2)", 3.0)
150 TEST_RESULT(Min2, "min(1,2,3)", 1.0)
151 TEST_RESULT(Max2, "max(1,2,3)", 3.0)
152 TEST_RESULT(Min3, "min(2,3,1)", 1.0)
153 TEST_RESULT(Max3, "max(2,3,1)", 3.0)
154
155 TEST_CONST(UnaryPlus, "+1", 1.0)
156
157 TEST_CONST(UnaryMinus, "-1", -1.0)
158 TEST_EVAL(UnaryMinus, "-x", 1.0, -1.0)
159
160 TEST_CONST(BinaryPlus, "1+2", 3.0)
161 TEST_EVAL(BinaryPlus, "x+2", 1, 3.0)
162
163 TEST_CONST(BinaryMinus, "1-2", -1.0)
164 TEST_EVAL(BinaryMinus, "1-x", 2, -1.0)
165
166 TEST_CONST(BinaryMul, "2*3", 6.0)
167 TEST_EVAL(BinaryMul, "x*3", 2, 6.0)
168
169 TEST_CONST(BinaryDiv, "3/2", 1.5)
170 TEST_EVAL(BinaryDiv, "3/x", 2, 1.5)
171
172 TEST_CONST(Arith1, "1 + -2 * 3", -5.0)
173 TEST_CONST(Arith2, "(1 + -2) * 3", -3.0)
174 TEST_CONST(Arith3, "-1 + 2 * 3", 5.0)
175 TEST_CONST(Arith4, "3 * (-2 + 1)", -3.0)
176
177 TEST_EVAL(Arith1, "1 + -x * 3", 2, -5.0)
178
179 TEST_CONST(Eq1, "1 == 1.0", TRUE_VAL)
180 TEST_CONST(Eq2, "1 == 2.0", FALSE_VAL)
181 TEST_CONST(Eq3, "True == 1", TRUE_VAL)
182 TEST_CONST(Eq4, "False == 0", TRUE_VAL)
183
184 TEST_EVAL(Eq1, "1 == x", 1.0, TRUE_VAL)
185 TEST_EVAL(Eq2, "1 == x", 2.0, FALSE_VAL)
186
187 TEST_CONST(NEq1, "1 != 1.0", FALSE_VAL)
188 TEST_CONST(NEq2, "1 != 2.0", TRUE_VAL)
189
190 TEST_EVAL(NEq1, "1 != x", 1.0, FALSE_VAL)
191 TEST_EVAL(NEq2, "1 != x", 2.0, TRUE_VAL)
192
193 TEST_CONST(Lt1, "1 < 1", FALSE_VAL)
194 TEST_CONST(Lt2, "1 < 2", TRUE_VAL)
195 TEST_CONST(Lt3, "2 < 1", FALSE_VAL)
196
197 TEST_CONST(Le1, "1 <= 1", TRUE_VAL)
198 TEST_CONST(Le2, "1 <= 2", TRUE_VAL)
199 TEST_CONST(Le3, "2 <= 1", FALSE_VAL)
200
201 TEST_CONST(Gt1, "1 > 1", FALSE_VAL)
202 TEST_CONST(Gt2, "1 > 2", FALSE_VAL)
203 TEST_CONST(Gt3, "2 > 1", TRUE_VAL)
204
205 TEST_CONST(Ge1, "1 >= 1", TRUE_VAL)
206 TEST_CONST(Ge2, "1 >= 2", FALSE_VAL)
207 TEST_CONST(Ge3, "2 >= 1", TRUE_VAL)
208
209 TEST_CONST(Cmp1, "3 == 1 + 2", TRUE_VAL)
210
211 TEST_EVAL(Cmp1, "3 == x + 2", 1, TRUE_VAL)
212 TEST_EVAL(Cmp1b, "3 == x + 2", 1.5, FALSE_VAL)
213
214 TEST_RESULT(CmpChain1, "1 < 2 < 3", TRUE_VAL)
215 TEST_RESULT(CmpChain2, "1 < 2 == 2", TRUE_VAL)
216 TEST_RESULT(CmpChain3, "1 < 2 > -1", TRUE_VAL)
217 TEST_RESULT(CmpChain4, "1 < 2 < 2 < 3", FALSE_VAL)
218 TEST_RESULT(CmpChain5, "1 < 2 <= 2 < 3", TRUE_VAL)
219
220 TEST_EVAL(CmpChain1a, "1 < x < 3", 2, TRUE_VAL)
221 TEST_EVAL(CmpChain1b, "1 < x < 3", 1, FALSE_VAL)
222 TEST_EVAL(CmpChain1c, "1 < x < 3", 3, FALSE_VAL)
223
224 TEST_CONST(Not1, "not 2", FALSE_VAL)
225 TEST_CONST(Not2, "not 0", TRUE_VAL)
226 TEST_CONST(Not3, "not not 2", TRUE_VAL)
227
228 TEST_EVAL(Not1, "not x", 2, FALSE_VAL)
229 TEST_EVAL(Not2, "not x", 0, TRUE_VAL)
230
231 TEST_RESULT(And1, "2 and 3", 3.0)
232 TEST_RESULT(And2, "0 and 3", 0.0)
233
234 TEST_RESULT(Or1, "2 or 3", 2.0)
235 TEST_RESULT(Or2, "0 or 3", 3.0)
236
237 TEST_RESULT(Bool1, "2 or 3 and 4", 2.0)
238 TEST_RESULT(Bool2, "not 2 or 3 and 4", 4.0)
239
240 TEST(expr_pylike, Eval_Ternary1)
241 {
242         ExprPyLike_Parsed *expr = parse_for_eval("x / 2 if x < 4 else x - 2 if x < 8 else x*2 - 12", true);
243
244         for (int i = 0; i <= 10; i++) {
245                 double x = i;
246                 double v = (x < 4) ? (x / 2) : (x < 8) ? (x - 2) : (x*2 - 12);
247
248                 verify_eval_result(expr, x, v);
249         }
250
251         BLI_expr_pylike_free(expr);
252 }
253
254 TEST(expr_pylike, MultipleArgs)
255 {
256         const char* names[3] = {"x", "y", "x"};
257         double values[3] = {1.0, 2.0, 3.0};
258
259         ExprPyLike_Parsed *expr = BLI_expr_pylike_parse("x*10 + y", names, ARRAY_SIZE(names));
260
261         EXPECT_TRUE(BLI_expr_pylike_is_valid(expr));
262
263         double result;
264         eExprPyLike_EvalStatus status = BLI_expr_pylike_eval(expr, values, 3, &result);
265
266         EXPECT_EQ(status, EXPR_PYLIKE_SUCCESS);
267         EXPECT_EQ(result, 32.0);
268
269         BLI_expr_pylike_free(expr);
270 }
271
272 #define TEST_ERROR(name, str, x, code) \
273         TEST(expr_pylike, Error_##name) { expr_pylike_error_test(str, x, code); }
274
275 TEST_ERROR(DivZero1, "0 / 0", 0.0, EXPR_PYLIKE_MATH_ERROR)
276 TEST_ERROR(DivZero2, "1 / 0", 0.0, EXPR_PYLIKE_DIV_BY_ZERO)
277 TEST_ERROR(DivZero3, "1 / x", 0.0, EXPR_PYLIKE_DIV_BY_ZERO)
278 TEST_ERROR(DivZero4, "1 / x", 1.0, EXPR_PYLIKE_SUCCESS)
279
280 TEST_ERROR(SqrtDomain1, "sqrt(-1)", 0.0, EXPR_PYLIKE_MATH_ERROR)
281 TEST_ERROR(SqrtDomain2, "sqrt(x)", -1.0, EXPR_PYLIKE_MATH_ERROR)
282 TEST_ERROR(SqrtDomain3, "sqrt(x)", 0.0, EXPR_PYLIKE_SUCCESS)
283
284 TEST_ERROR(PowDomain1, "pow(-1, 0.5)", 0.0, EXPR_PYLIKE_MATH_ERROR)
285 TEST_ERROR(PowDomain2, "pow(-1, x)", 0.5, EXPR_PYLIKE_MATH_ERROR)
286 TEST_ERROR(PowDomain3, "pow(-1, x)", 2.0, EXPR_PYLIKE_SUCCESS)
287
288 TEST_ERROR(Mixed1, "sqrt(x) + 1 / max(0, x)", -1.0, EXPR_PYLIKE_MATH_ERROR)
289 TEST_ERROR(Mixed2, "sqrt(x) + 1 / max(0, x)", 0.0, EXPR_PYLIKE_DIV_BY_ZERO)
290 TEST_ERROR(Mixed3, "sqrt(x) + 1 / max(0, x)", 1.0, EXPR_PYLIKE_SUCCESS)
291
292 TEST(expr_pylike, Error_Invalid)
293 {
294         ExprPyLike_Parsed *expr = BLI_expr_pylike_parse("", NULL, 0);
295         double result;
296
297         EXPECT_EQ(BLI_expr_pylike_eval(expr, NULL, 0, &result), EXPR_PYLIKE_INVALID);
298
299         BLI_expr_pylike_free(expr);
300 }
301
302 TEST(expr_pylike, Error_ArgumentCount)
303 {
304         ExprPyLike_Parsed *expr = parse_for_eval("x", false);
305         double result;
306
307         EXPECT_EQ(BLI_expr_pylike_eval(expr, NULL, 0, &result), EXPR_PYLIKE_FATAL_ERROR);
308
309         BLI_expr_pylike_free(expr);
310 }