Line data Source code
1 : /****************************************************************************/
2 : // Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.dev/sumo
3 : // Copyright (C) 2001-2026 German Aerospace Center (DLR) and others.
4 : // This program and the accompanying materials are made available under the
5 : // terms of the Eclipse Public License 2.0 which is available at
6 : // https://www.eclipse.org/legal/epl-2.0/
7 : // This Source Code may also be made available under the following Secondary
8 : // Licenses when the conditions for such availability set forth in the Eclipse
9 : // Public License 2.0 are satisfied: GNU General Public License, version 2
10 : // or later which is available at
11 : // https://www.gnu.org/licenses/old-licenses/gpl-2.0-standalone.html
12 : // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-or-later
13 : /****************************************************************************/
14 : /// @file InternalTest.cpp
15 : /// @author Pablo Alvarez Lopez
16 : /// @date Mar 2025
17 : ///
18 : // Class used for internal tests
19 : /****************************************************************************/
20 : #include <config.h>
21 :
22 : #include <fstream>
23 : #include <utils/common/MsgHandler.h>
24 :
25 : #include "InternalTest.h"
26 : #include "InternalTestStep.h"
27 :
28 : #ifdef _MSC_VER
29 : // disable using unsecure functions (getenv)
30 : #pragma warning(disable:4996)
31 : #endif
32 :
33 : // define number of points to interpolate
34 : #define numPointsInterpolation 100
35 :
36 : // ===========================================================================
37 : // member method definitions
38 : // ===========================================================================
39 :
40 : // ---------------------------------------------------------------------------
41 : // InternalTest::ViewPosition - public methods
42 : // ---------------------------------------------------------------------------
43 :
44 0 : InternalTest::ViewPosition::ViewPosition() {}
45 :
46 :
47 0 : InternalTest::ViewPosition::ViewPosition(const int x, const int y) :
48 0 : myX(x),
49 0 : myY(y) {
50 0 : }
51 :
52 :
53 0 : InternalTest::ViewPosition::ViewPosition(const std::string& x, const std::string& y) :
54 0 : myX(StringUtils::toInt(x)),
55 0 : myY(StringUtils::toInt(y)) {
56 0 : }
57 :
58 :
59 : int
60 0 : InternalTest::ViewPosition::getX() const {
61 0 : return myX;
62 : }
63 :
64 :
65 : int
66 0 : InternalTest::ViewPosition::getY() const {
67 0 : return myY;
68 : }
69 :
70 : // ---------------------------------------------------------------------------
71 : // InternalTest::ContextualMenu - public methods
72 : // ---------------------------------------------------------------------------
73 :
74 0 : InternalTest::ContextualMenu::ContextualMenu() {}
75 :
76 :
77 0 : InternalTest::ContextualMenu::ContextualMenu(const std::string& mainMenuValue,
78 0 : const std::string& subMenuAValue, const std::string& subMenuBValue) :
79 0 : myMainMenu(StringUtils::toInt(mainMenuValue)),
80 0 : mySubMenuA(StringUtils::toInt(subMenuAValue)),
81 0 : mySubMenuB(StringUtils::toInt(subMenuBValue)) {
82 0 : }
83 :
84 :
85 : int
86 0 : InternalTest::ContextualMenu::getMainMenuPosition() const {
87 0 : return myMainMenu;
88 : }
89 :
90 :
91 : int
92 0 : InternalTest::ContextualMenu::getSubMenuAPosition() const {
93 0 : return mySubMenuA;
94 : }
95 :
96 :
97 : int
98 0 : InternalTest::ContextualMenu::getSubMenuBPosition() const {
99 0 : return mySubMenuB;
100 : }
101 :
102 : // ---------------------------------------------------------------------------
103 : // InternalTest::Movement - public methods
104 : // ---------------------------------------------------------------------------
105 :
106 0 : InternalTest::Movement::Movement() {}
107 :
108 :
109 0 : InternalTest::Movement::Movement(const std::string& up, const std::string& down,
110 0 : const std::string& left, const std::string& right) :
111 0 : myUp(StringUtils::toInt(up)),
112 0 : myDown(StringUtils::toInt(down)),
113 0 : myLeft(StringUtils::toInt(left)),
114 0 : myRight(StringUtils::toInt(right)) {
115 0 : }
116 :
117 :
118 : int
119 0 : InternalTest::Movement::getUp() const {
120 0 : return myUp;
121 : }
122 :
123 :
124 : int
125 0 : InternalTest::Movement::getDown() const {
126 0 : return myDown;
127 : }
128 :
129 :
130 : int
131 0 : InternalTest::Movement::getLeft() const {
132 0 : return myLeft;
133 : }
134 :
135 :
136 : int
137 0 : InternalTest::Movement::getRight() const {
138 0 : return myRight;
139 : }
140 :
141 : // ---------------------------------------------------------------------------
142 : // InternalTest - public methods
143 : // ---------------------------------------------------------------------------
144 :
145 0 : InternalTest::InternalTest(const std::string& testFile) {
146 : // locate sumo home directory
147 0 : const auto sumoHome = std::string(getenv("SUMO_HOME"));
148 : // load data files
149 0 : myAttributesEnum = parseAttributesEnumFile(sumoHome + "/data/tests/attributesEnum.txt");
150 0 : myContextualMenuOperations = parseContextualMenuOperationsFile(sumoHome + "/data/tests/contextualMenuOperations.txt");
151 0 : myViewPositions = parseViewPositionsFile(sumoHome + "/data/tests/viewPositions.txt");
152 0 : myMovements = parseMovementsFile(sumoHome + "/data/tests/movements.txt");
153 : // open file
154 0 : std::ifstream strm(testFile);
155 : // check if file can be opened
156 0 : if (!strm.good()) {
157 0 : std::cout << "Could not open test file '" + testFile + "'." << std::endl;
158 0 : throw ProcessError();
159 0 : } else if (myAttributesEnum.empty() || myContextualMenuOperations.empty() || myViewPositions.empty() || myMovements.empty()) {
160 : std::cout << "Error loading test data files" << std::endl;
161 0 : throw ProcessError();
162 : } else {
163 : std::string line;
164 : std::vector<std::pair<bool, std::string> > linesRaw;
165 : // read full lines until end of file
166 0 : while (std::getline(strm, line)) {
167 : // filter lines
168 0 : if (!line.empty() && // emty lines
169 0 : !(line[0] == '#') && // comments
170 0 : !startWith(line, "import") && // imports
171 0 : !startWith(line, "time.") && // time calls
172 0 : !startWith(line, "sys.")) { // sys calls
173 0 : linesRaw.push_back(std::make_pair(startWith(line, "netedit."), line));
174 : }
175 : }
176 : // clean lines
177 0 : const auto lines = cleanLines(linesRaw);
178 : // create steps
179 0 : new InternalTestStep(this, "netedit.setupAndStart");
180 0 : for (const auto& clearLine : lines) {
181 0 : new InternalTestStep(this, clearLine);
182 : }
183 0 : new InternalTestStep(this, "netedit.finish");
184 0 : }
185 0 : }
186 :
187 :
188 0 : InternalTest::~InternalTest() {
189 : // delete all test steps
190 0 : while (myInitialTestStep != nullptr) {
191 : // store next step
192 0 : auto nextStep = myInitialTestStep->getNextStep();
193 : // delete current step
194 0 : delete myInitialTestStep;
195 : // set next step as initial step
196 0 : myInitialTestStep = nextStep;
197 : }
198 0 : }
199 :
200 :
201 : void
202 0 : InternalTest::addTestSteps(InternalTestStep* internalTestStep) {
203 0 : if (myLastTestStep == nullptr) {
204 : // set initial step
205 0 : myInitialTestStep = internalTestStep;
206 0 : myLastTestStep = internalTestStep;
207 0 : myCurrentTestStep = internalTestStep;
208 : } else {
209 : // set next step
210 0 : myLastTestStep->setNextStep(internalTestStep);
211 0 : myLastTestStep = internalTestStep;
212 : }
213 0 : }
214 :
215 :
216 : InternalTestStep*
217 0 : InternalTest::getCurrentStep() const {
218 0 : return myCurrentTestStep;
219 : }
220 :
221 :
222 : InternalTestStep*
223 0 : InternalTest::setNextStep() {
224 0 : const auto currentStep = myCurrentTestStep;
225 0 : myCurrentTestStep = myCurrentTestStep->getNextStep();
226 0 : return currentStep;
227 : }
228 :
229 :
230 : bool
231 0 : InternalTest::isRunning() const {
232 0 : return myRunning;
233 : }
234 :
235 :
236 : void
237 0 : InternalTest::stopTests() {
238 0 : myRunning = false;
239 0 : }
240 :
241 :
242 : FXint
243 0 : InternalTest::getTime() const {
244 : return static_cast<FXuint>(
245 : std::chrono::duration_cast<std::chrono::milliseconds>(
246 0 : std::chrono::steady_clock::now().time_since_epoch()
247 0 : ).count());
248 : }
249 :
250 :
251 : const std::map<std::string, int>&
252 0 : InternalTest::getAttributesEnum() const {
253 0 : return myAttributesEnum;
254 : }
255 :
256 :
257 : const std::map<std::string, InternalTest::ContextualMenu>&
258 0 : InternalTest::getContextualMenuOperations() const {
259 0 : return myContextualMenuOperations;
260 : }
261 :
262 :
263 : const std::map<std::string, InternalTest::ViewPosition>&
264 0 : InternalTest::getViewPositions() const {
265 0 : return myViewPositions;
266 : }
267 :
268 :
269 : const std::map<std::string, InternalTest::Movement>&
270 0 : InternalTest::getMovements() const {
271 0 : return myMovements;
272 : }
273 :
274 :
275 : const InternalTest::ViewPosition&
276 0 : InternalTest::getLastMovedPosition() const {
277 0 : return myLastMovedPosition;
278 : }
279 :
280 :
281 : void
282 0 : InternalTest::updateLastMovedPosition(const int x, const int y) {
283 0 : myLastMovedPosition = InternalTest::ViewPosition(x, y);
284 0 : }
285 :
286 :
287 : std::vector<InternalTest::ViewPosition>
288 0 : InternalTest::interpolateViewPositions(const InternalTest::ViewPosition& viewStartPosition,
289 : const int offsetStartX, const int offsetStartY,
290 : const InternalTest::ViewPosition& viewEndPosition,
291 : const int offsetEndX, const int offsetEndY) const {
292 : // declare trajectory vector
293 : std::vector<InternalTest::ViewPosition> trajectory;
294 0 : trajectory.reserve(numPointsInterpolation);
295 : // calulate from using offsets
296 0 : const auto from = InternalTest::ViewPosition(viewStartPosition.getX() + offsetStartX, viewStartPosition.getY() + offsetStartY);
297 0 : const auto to = InternalTest::ViewPosition(viewEndPosition.getX() + offsetEndX, viewEndPosition.getY() + offsetEndY);
298 : // itearte over the number of points to interpolate
299 0 : for (int i = 0; i < numPointsInterpolation; i++) {
300 0 : const double t = static_cast<double>(i) / (numPointsInterpolation - 1); // t in [0, 1]
301 : // calculate interpolated position
302 0 : const int interpolatedX = int(from.getX() + t * (to.getX() - from.getX()));
303 0 : const int interpolatedY = int(from.getY() + t * (to.getY() - from.getY()));
304 : // add interpolated position
305 0 : trajectory.push_back(ViewPosition(interpolatedX, interpolatedY));
306 : }
307 0 : return trajectory;
308 0 : }
309 :
310 :
311 : std::map<std::string, int>
312 0 : InternalTest::parseAttributesEnumFile(const std::string filePath) const {
313 : std::map<std::string, int> solution;
314 : // open file
315 0 : std::ifstream strm(filePath);
316 : // check if file can be opened
317 0 : if (!strm.good()) {
318 0 : WRITE_ERRORF(TL("Could not open attributes enum file '%'."), filePath);
319 : } else {
320 : std::string line;
321 : // read full lines until end of file
322 0 : while (std::getline(strm, line)) {
323 : // use stringstream for
324 0 : std::stringstream ss(line);
325 : // read key and value
326 : std::string key;
327 : std::string value;
328 0 : std::getline(ss, key, ' ');
329 0 : std::getline(ss, value, '\n');
330 : // check that int can be parsed
331 0 : if (!StringUtils::isInt(value)) {
332 0 : WRITE_ERRORF(TL("In internal test file, value '%' cannot be parsed to int."), value);
333 : } else {
334 0 : solution[key] = StringUtils::toInt(value);
335 : }
336 0 : }
337 : }
338 0 : return solution;
339 0 : }
340 :
341 :
342 : std::map<std::string, InternalTest::ContextualMenu>
343 0 : InternalTest::parseContextualMenuOperationsFile(const std::string filePath) const {
344 : std::map<std::string, InternalTest::ContextualMenu> solution;
345 : // open file
346 0 : std::ifstream strm(filePath);
347 : // check if file can be opened
348 0 : if (!strm.good()) {
349 0 : WRITE_ERRORF(TL("Could not open view positions file '%'."), filePath);
350 : } else {
351 : std::string line;
352 : // read full lines until end of file
353 0 : while (std::getline(strm, line)) {
354 : // read key and value
355 : std::string mainMenuKey;
356 : std::string mainMenuValue;
357 : std::string subMenuAKey;
358 : std::string subMenuAValue;
359 : std::string subMenuBKey;
360 : std::string subMenuBValue;
361 : // parse first line
362 0 : std::stringstream mainMenuSS(line);
363 0 : std::getline(mainMenuSS, mainMenuKey, ' ');
364 0 : std::getline(mainMenuSS, mainMenuValue, '\n');
365 : // parse second line
366 0 : std::getline(strm, line);
367 0 : std::stringstream subMenuASS(line);
368 0 : std::getline(subMenuASS, subMenuAKey, ' ');
369 0 : std::getline(subMenuASS, subMenuAValue, '\n');
370 : // parse third line
371 0 : std::getline(strm, line);
372 0 : std::stringstream subMenuBSS(line);
373 0 : std::getline(subMenuBSS, subMenuBKey, ' ');
374 0 : std::getline(subMenuBSS, subMenuBValue, '\n');
375 : // check that int can be parsed
376 0 : if (!StringUtils::isInt(mainMenuValue)) {
377 0 : WRITE_ERRORF(TL("In internal test file, mainMenu value '%' cannot be parsed to int."), mainMenuValue);
378 0 : } else if (!StringUtils::isInt(subMenuAValue)) {
379 0 : WRITE_ERRORF(TL("In internal test file, subMenuA value '%' cannot be parsed to int."), subMenuAValue);
380 0 : } else if (!StringUtils::isInt(subMenuBValue)) {
381 0 : WRITE_ERRORF(TL("In internal test file, subMenuB value '%' cannot be parsed to int."), subMenuBValue);
382 : } else {
383 : // remove '.mainMenuPosition' from mainMenuKey
384 0 : solution[mainMenuKey.erase(mainMenuKey.size() - 17)] = InternalTest::ContextualMenu(mainMenuValue, subMenuAValue, subMenuBValue);
385 : }
386 0 : }
387 : }
388 0 : return solution;
389 0 : }
390 :
391 :
392 : std::map<std::string, InternalTest::ViewPosition>
393 0 : InternalTest::parseViewPositionsFile(const std::string filePath) const {
394 : std::map<std::string, InternalTest::ViewPosition> solution;
395 : // open file
396 0 : std::ifstream strm(filePath);
397 : // check if file can be opened
398 0 : if (!strm.good()) {
399 0 : WRITE_ERRORF(TL("Could not open view positions file '%'."), filePath);
400 : } else {
401 : std::string line;
402 : // read full lines until end of file
403 0 : while (std::getline(strm, line)) {
404 : // use stringstream for
405 0 : std::stringstream ss(line);
406 : // read key and value
407 : std::string key;
408 : std::string xValue;
409 : std::string yValue;
410 0 : std::getline(ss, key, ' ');
411 0 : std::getline(ss, xValue, ' ');
412 0 : std::getline(ss, yValue, '\n');
413 : // check that int can be parsed
414 0 : if (!StringUtils::isInt(xValue)) {
415 0 : WRITE_ERRORF(TL("In internal test file, x value '%' cannot be parsed to int."), xValue);
416 0 : } else if (!StringUtils::isInt(yValue)) {
417 0 : WRITE_ERRORF(TL("In internal test file, y value '%' cannot be parsed to int."), yValue);
418 : } else {
419 0 : solution[key] = InternalTest::ViewPosition(xValue, yValue);
420 : }
421 0 : }
422 : }
423 0 : return solution;
424 0 : }
425 :
426 :
427 : std::map<std::string, InternalTest::Movement>
428 0 : InternalTest::parseMovementsFile(const std::string filePath) const {
429 : std::map<std::string, InternalTest::Movement> solution;
430 : // open file
431 0 : std::ifstream strm(filePath);
432 : // check if file can be opened
433 0 : if (!strm.good()) {
434 0 : WRITE_ERRORF(TL("Could not open view positions file '%'."), filePath);
435 : } else {
436 : std::string line;
437 : // read full lines until end of file
438 0 : while (std::getline(strm, line)) {
439 : // use stringstream for
440 0 : std::stringstream ss(line);
441 : // read key and value
442 : std::string key;
443 : std::string upValue;
444 : std::string downValue;
445 : std::string leftValue;
446 : std::string rightValue;
447 0 : std::getline(ss, key, ' ');
448 0 : std::getline(ss, upValue, ' ');
449 0 : std::getline(ss, downValue, ' ');
450 0 : std::getline(ss, leftValue, ' ');
451 0 : std::getline(ss, rightValue, '\n');
452 : // check that int can be parsed
453 0 : if (!StringUtils::isInt(upValue)) {
454 0 : WRITE_ERRORF(TL("In internal test file, x value '%' cannot be parsed to int."), upValue);
455 0 : } else if (!StringUtils::isInt(downValue)) {
456 0 : WRITE_ERRORF(TL("In internal test file, y value '%' cannot be parsed to int."), downValue);
457 0 : } else if (!StringUtils::isInt(leftValue)) {
458 0 : WRITE_ERRORF(TL("In internal test file, y value '%' cannot be parsed to int."), leftValue);
459 0 : } else if (!StringUtils::isInt(rightValue)) {
460 0 : WRITE_ERRORF(TL("In internal test file, y value '%' cannot be parsed to int."), rightValue);
461 : } else {
462 0 : solution[key] = InternalTest::Movement(upValue, downValue, leftValue, rightValue);
463 : }
464 0 : }
465 : }
466 0 : return solution;
467 0 : }
468 :
469 :
470 : std::vector<std::string>
471 0 : InternalTest::cleanLines(const std::vector<std::pair<bool, std::string> >& linesRaw) const {
472 : std::vector<std::string> results;
473 0 : for (const auto& lineRaw : linesRaw) {
474 0 : if (lineRaw.first) {
475 0 : results.push_back(lineRaw.second);
476 0 : } else if (results.size() > 0) {
477 : results.back().append(lineRaw.second);
478 : }
479 : }
480 0 : return results;
481 0 : }
482 :
483 :
484 : bool
485 0 : InternalTest::startWith(const std::string& str, const std::string& prefix) const {
486 0 : if (prefix.size() > str.size()) {
487 : return false;
488 : } else {
489 0 : for (int i = 0; i < (int)prefix.size(); i++) {
490 0 : if (str[i] != prefix[i]) {
491 : return false;
492 : }
493 : }
494 : return true;
495 : }
496 : }
497 :
498 : /****************************************************************************/
|