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