1 /*
2  * This program source code file is part of KiCad, a free EDA CAD application.
3  *
4  * Copyright (C) 2018 KiCad Developers, see AUTHORS.TXT for contributors.
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version 2
9  * of the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, you may find one here:
18  * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
19  * or you may search the http://www.gnu.org website for the version 2 license,
20  * or you may write to the Free Software Foundation, Inc.,
21  * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
22  */
23 
24 /**
25  * @file test_numeric_evaluator.cpp
26  * Test suite for #NUMERIC_EVALUATOR
27  */
28 
29 #include <qa_utils/wx_utils/unit_test_utils.h>
30 
31 #include <libeval/numeric_evaluator.h>
32 
33 struct NUM_EVAL_FIXTURE
34 {
NUM_EVAL_FIXTURENUM_EVAL_FIXTURE35     NUM_EVAL_FIXTURE() : m_eval( EDA_UNITS::MILLIMETRES )
36     {
37     }
38 
39     NUMERIC_EVALUATOR m_eval;
40 };
41 
42 
43 /**
44  * Declares the struct as the Boost test fixture.
45  */
46 BOOST_FIXTURE_TEST_SUITE( NumericEvaluator, NUM_EVAL_FIXTURE )
47 
48 
49 /**
50  * Struct representing a test case for #NUMERIC_EVALUATOR
51  */
52 struct EVAL_CASE
53 {
54     wxString input;
55     wxString exp_result;
56 };
57 
58 
59 /**
60  * Basic class ops: set one up, trivial input, tear it down
61  */
BOOST_AUTO_TEST_CASE(Basic)62 BOOST_AUTO_TEST_CASE( Basic )
63 {
64     m_eval.Process( "1" );
65     BOOST_CHECK_EQUAL( m_eval.Result(), "1" );
66 }
67 
68 /**
69  * Check that getting/setting vars works
70  */
BOOST_AUTO_TEST_CASE(SetVar)71 BOOST_AUTO_TEST_CASE( SetVar )
72 {
73     m_eval.SetVar( "MoL", 42 );
74 
75     m_eval.Process( "1 + MoL" );
76     BOOST_CHECK_EQUAL( m_eval.GetVar( "MoL" ), 42 );
77     BOOST_CHECK_EQUAL( m_eval.Result(), "43" );
78 
79     m_eval.SetVar( "MoL", 422 );
80 
81     // have to process again to re-evaluate
82     m_eval.Process( "1 + MoL" );
83     BOOST_CHECK_EQUAL( m_eval.Result(), "423" );
84 
85     // Can remove one var
86     m_eval.SetVar( "pi", 3.14 );
87     BOOST_CHECK_EQUAL( m_eval.GetVar( "pi" ), 3.14 );
88     m_eval.RemoveVar( "pi" );
89     BOOST_CHECK_EQUAL( m_eval.GetVar( "pi" ), 0.0 );
90 
91     // Other is still there
92     BOOST_CHECK_EQUAL( m_eval.GetVar( "MoL" ), 422 );
93 
94     // Add another one back
95     m_eval.SetVar( "piish", 3.1 );
96 
97     // String clear doesn't clear vars
98     m_eval.Clear();
99     m_eval.Process( "1 + MoL + piish" );
100     BOOST_CHECK_EQUAL( m_eval.Result(), "426.1" );
101 
102     // Clear both
103     m_eval.ClearVar();
104     BOOST_CHECK_EQUAL( m_eval.GetVar( "MoL" ), 0.0 );
105     BOOST_CHECK_EQUAL( m_eval.GetVar( "piish" ), 0.0 );
106 }
107 
108 /**
109  * A list of valid test strings and the expected results
110  */
111 static const std::vector<EVAL_CASE> eval_cases_valid = {
112     // Empty case
113     { "", "0" },
114     // Trivial eval
115     { "1", "1" },
116     // Decimal separators
117     { "1.5", "1.5" },
118     { "1,5", "1.5" },
119     // Semicolon is valid, but the result is NaN
120     { "1;", "NaN" },
121     // With own unit
122     { "1mm", "1" },
123     // Unit that's not the evaluator's unit
124     { "1in", "25.4" },
125     // Unit with white-space
126     { "1 in", "25.4" },
127     // Unit-less arithmetic
128     { "1+2", "3" },
129     // Multiple units
130     { "1 + 10mm + 1\" + 1.5in + 500mil", "87.2" },
131     // Any White-space is OK
132     { "   1 +     2    ", "3" },
133     // Decimals are OK in expressions
134     { "1.5 + 0.2 + .1", "1.8" },
135     // Negatives are OK
136     { "3 - 10", "-7" },
137     // Lots of operands
138     { "1 + 2 + 10 + 1000.05", "1013.05" },
139     // Operator precedence
140     { "1 + 2 - 4 * 20 / 2", "-37" },
141     // Parens
142     { "(1)", "1" },
143     // Parens affect precedence
144     { "-(1 + (2 - 4)) * 20.8 / 2", "10.4" },
145     // Unary addition is a sign, not a leading operator
146     { "+2 - 1", "1" },
147     // Unknown vars are 0.0
148     { "1 + unknown", "1" },
149     // Set var in-string
150     { "x = 1; 1 + x", "2" },
151     // Multiple set vars
152     { "x = 1; y = 2; 10 + x - y", "9" },
153 };
154 
155 
156 /**
157  * Run through a set of test strings, clearing in between
158  */
BOOST_AUTO_TEST_CASE(Results)159 BOOST_AUTO_TEST_CASE( Results )
160 {
161     for( const auto& c : eval_cases_valid )
162     {
163         BOOST_TEST_CONTEXT( c.input + " -> " + c.exp_result )
164         {
165             // Clear for new string input
166             m_eval.Clear();
167 
168             m_eval.Process( c.input );
169 
170             // These are all valid
171             BOOST_CHECK_EQUAL( m_eval.IsValid(), true );
172             BOOST_CHECK_EQUAL( m_eval.Result(), c.exp_result );
173 
174             // Does original text still match?
175             BOOST_CHECK_EQUAL( m_eval.OriginalText(), c.input );
176         }
177     }
178 }
179 
180 struct EVAL_INVALID_CASE
181 {
182     wxString input;
183 };
184 
185 /**
186  * A list of invalid test strings
187  */
188 static const std::vector<EVAL_INVALID_CASE> eval_cases_invalid = {
189     // Trailing operator
190     { "1+" },
191     // Leading operator
192     { "*2 + 1" },
193     // No operator
194     { "1 2" },
195     { "(1)(2)" },
196     // Unknown operator
197     { "1 $ 2" },
198     // Mismatched parens
199     { "(1 + 2" },
200     { "1 + 2)" },
201     // random text
202     { "sdfsdf sdfsd" },
203     // Div by 0
204     { "1 / 0" },
205     { "1 / unknown" },
206     // Semicolons can't be empty or redundant
207     { ";" },
208     { ";1" },
209     { ";1;" },
210 };
211 
212 /**
213  * Run through a set of invalid test strings, clearing in between
214  */
BOOST_AUTO_TEST_CASE(ResultsInvalid)215 BOOST_AUTO_TEST_CASE( ResultsInvalid )
216 {
217     for( const auto& c : eval_cases_invalid )
218     {
219         BOOST_TEST_CONTEXT( c.input )
220         {
221             // Clear for new string input
222             m_eval.Clear();
223 
224             m_eval.Process( c.input );
225 
226             // These are all valid
227             BOOST_CHECK_EQUAL( m_eval.IsValid(), false );
228 
229             // Does original text still match?
230             BOOST_CHECK_EQUAL( m_eval.OriginalText(), c.input );
231         }
232     }
233 }
234 
235 BOOST_AUTO_TEST_SUITE_END()
236