tcuTestCase.js (17153B)
1 /*------------------------------------------------------------------------- 2 * drawElements Quality Program OpenGL ES Utilities 3 * ------------------------------------------------ 4 * 5 * Copyright 2014 The Android Open Source Project 6 * 7 * Licensed under the Apache License, Version 2.0 (the "License"); 8 * you may not use this file except in compliance with the License. 9 * You may obtain a copy of the License at 10 * 11 * http://www.apache.org/licenses/LICENSE-2.0 12 * 13 * Unless required by applicable law or agreed to in writing, software 14 * distributed under the License is distributed on an "AS IS" BASIS, 15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 * See the License for the specific language governing permissions and 17 * limitations under the License. 18 * 19 */ 20 21 /** 22 * This class allows one to create a hierarchy of tests and iterate over them. 23 * It replaces TestCase and TestCaseGroup classes. 24 */ 25 'use strict'; 26 goog.provide('framework.common.tcuTestCase'); 27 goog.require('framework.common.tcuSkipList'); 28 29 goog.scope(function() { 30 31 var tcuTestCase = framework.common.tcuTestCase; 32 var tcuSkipList = framework.common.tcuSkipList; 33 34 tcuTestCase.getQueryVal = function(key) { 35 const queryVars = window.location.search.substring(1).split('&'); 36 for (let kv of queryVars) { 37 kv = kv.split('='); 38 if (decodeURIComponent(kv[0]) === key) 39 return decodeURIComponent(kv[1]); 40 } 41 return null; 42 }; 43 44 tcuTestCase.isQuickMode = () => tcuTestCase.getQueryVal('quick') === '1'; 45 tcuTestCase.isQuietMode = () => tcuTestCase.getQueryVal('quiet') === '1'; 46 47 /** 48 * Reads the filter parameter from the URL to filter tests. 49 * @return {?string } 50 */ 51 tcuTestCase.getFilter = () => tcuTestCase.getQueryVal('filter'); 52 53 /** 54 * Indicates the state of an iteration operation. 55 * @enum {number} 56 */ 57 tcuTestCase.IterateResult = { 58 STOP: 0, 59 CONTINUE: 1 60 }; 61 62 /**************************************** 63 * Runner 64 ***************************************/ 65 66 /** 67 * A simple state machine. 68 * The purpose of this this object is to break 69 * long tests into small chunks that won't cause a timeout 70 * @constructor 71 */ 72 tcuTestCase.Runner = function() { 73 /** @type {tcuTestCase.DeqpTest} */ this.currentTest = null; 74 /** @type {tcuTestCase.DeqpTest} */ this.nextTest = null; 75 /** @type {tcuTestCase.DeqpTest} */ this.testCases = null; 76 /** @type {?string } */ this.filter = tcuTestCase.getFilter(); 77 }; 78 79 /** 80 * @param {tcuTestCase.DeqpTest} root The root test of the test tree. 81 */ 82 tcuTestCase.Runner.prototype.setRoot = function(root) { 83 this.currentTest = null; 84 this.testCases = root; 85 }; 86 87 tcuTestCase.Runner.prototype.setRange = function(range) { 88 this.range = range; 89 }; 90 91 /** 92 * Searches the test tree for the next executable test 93 * @return {?tcuTestCase.DeqpTest } 94 */ 95 tcuTestCase.Runner.prototype.next = function() { 96 97 // First time? Use root test 98 if (!this.currentTest) { 99 this.currentTest = this.testCases; 100 101 // Root is executable? Use it 102 if (this.currentTest.isExecutable()) 103 return this.currentTest; 104 } 105 106 // Should we proceed with the next test? 107 if (tcuTestCase.lastResult == tcuTestCase.IterateResult.STOP) { 108 // Look for next executable test 109 do { 110 if (this.range) 111 this.currentTest = this.currentTest.nextInRange(this.filter, this.range); 112 else 113 this.currentTest = this.currentTest.next(this.filter); 114 } while (this.currentTest && !this.currentTest.isExecutable()); 115 } 116 117 return this.currentTest; 118 }; 119 120 /** 121 * Schedule the callback to be run ASAP 122 * @param {function()} callback Callback to schedule 123 */ 124 tcuTestCase.Runner.prototype.runCallback = function(callback) { 125 setTimeout(function() { 126 callback(); 127 }.bind(this), 0); 128 }; 129 130 /** 131 * Call this function at the end of the test 132 */ 133 tcuTestCase.Runner.prototype.terminate = function() { 134 finishTest(); 135 if (!tcuTestCase.isQuietMode()) { 136 console.log('finishTest() after (in ms):', performance.now()); 137 } 138 }; 139 140 tcuTestCase.runner = new tcuTestCase.Runner(); 141 142 /** @type {tcuTestCase.IterateResult} */ tcuTestCase.lastResult = tcuTestCase.IterateResult.STOP; 143 144 /*************************************** 145 * DeqpTest 146 ***************************************/ 147 148 /** 149 * Assigns name, description and specification to test 150 * @constructor 151 * @param {?string} name 152 * @param {?string} description 153 * @param {Object=} spec 154 */ 155 tcuTestCase.DeqpTest = function(name, description, spec) { 156 this.name = name || ''; 157 this.description = description || ''; 158 this.spec = spec; 159 this.currentTestNdx = 0; 160 this.parentTest = null; 161 this.childrenTests = []; 162 this.executeAlways = false; 163 }; 164 165 /** 166 * Abstract init function(each particular test will implement it, or not) 167 */ 168 tcuTestCase.DeqpTest.prototype.init = function() {}; 169 170 /** 171 * Abstract deinit function(each particular test will implement it, or not) 172 */ 173 tcuTestCase.DeqpTest.prototype.deinit = function() {}; 174 175 /** 176 * Abstract iterate function(each particular test will implement it, or not) 177 * @return {tcuTestCase.IterateResult} 178 */ 179 tcuTestCase.DeqpTest.prototype.iterate = function() { return tcuTestCase.IterateResult.STOP; }; 180 181 /** 182 * Checks if the test is executable 183 * @return {boolean} 184 */ 185 tcuTestCase.DeqpTest.prototype.isExecutable = function() { 186 return this.childrenTests.length == 0 || this.executeAlways; 187 }; 188 189 /** 190 * Checks if the test is a leaf 191 */ 192 tcuTestCase.DeqpTest.prototype.isLeaf = function() { 193 return this.childrenTests.length == 0; 194 }; 195 196 /** 197 * Marks the test as always executable 198 */ 199 tcuTestCase.DeqpTest.prototype.makeExecutable = function() { 200 this.executeAlways = true; 201 }; 202 203 /** 204 * Adds a child test to the test's children 205 * @param {tcuTestCase.DeqpTest} test 206 */ 207 tcuTestCase.DeqpTest.prototype.addChild = function(test) { 208 test.parentTest = this; 209 this.childrenTests.push(test); 210 }; 211 212 /** 213 * Sets the whole children tests array 214 * @param {Array<tcuTestCase.DeqpTest>} tests 215 */ 216 tcuTestCase.DeqpTest.prototype.setChildren = function(tests) { 217 for (var test in tests) 218 tests[test].parentTest = this; 219 this.childrenTests = tests; 220 }; 221 222 /** 223 * Returns the next test in the hierarchy of tests 224 * 225 * @param {?string } pattern Optional pattern to search for 226 * @return {tcuTestCase.DeqpTest} 227 */ 228 tcuTestCase.DeqpTest.prototype.next = function(pattern) { 229 return this._nextHonoringSkipList(pattern); 230 }; 231 232 /** 233 * Returns the next test in the hierarchy of tests, honoring the 234 * skip list, and reporting skipped tests. 235 * 236 * @param {?string } pattern Optional pattern to search for 237 * @return {tcuTestCase.DeqpTest} 238 */ 239 tcuTestCase.DeqpTest.prototype._nextHonoringSkipList = function(pattern) { 240 var tryAgain = false; 241 var test = null; 242 do { 243 tryAgain = false; 244 test = this._nextIgnoringSkipList(pattern); 245 if (test != null) { 246 // See whether the skip list vetoes the execution of 247 // this test. 248 var fullTestName = test.fullName(); 249 var skipDisposition = tcuSkipList.getSkipStatus(fullTestName); 250 if (skipDisposition.skip) { 251 tryAgain = true; 252 setCurrentTestName(fullTestName); 253 checkMessage(false, 'Skipping test due to tcuSkipList: ' + fullTestName); 254 } 255 } 256 } while (tryAgain); 257 return test; 258 }; 259 260 261 /** 262 * Returns the next test in the hierarchy of tests, ignoring the 263 * skip list. 264 * 265 * @param {?string } pattern Optional pattern to search for 266 * @return {tcuTestCase.DeqpTest} 267 */ 268 tcuTestCase.DeqpTest.prototype._nextIgnoringSkipList = function(pattern) { 269 if (pattern) 270 return this._findIgnoringSkipList(pattern); 271 272 var test = null; 273 274 //Look for the next child 275 if (this.currentTestNdx < this.childrenTests.length) { 276 test = this.childrenTests[this.currentTestNdx]; 277 this.currentTestNdx++; 278 } 279 280 // If no more children, get the next brother 281 if (test == null && this.parentTest != null) { 282 test = this.parentTest._nextIgnoringSkipList(null); 283 } 284 285 return test; 286 }; 287 288 /** 289 * Returns the next test in the hierarchy of tests 290 * whose 1st level is in the given range 291 * 292 * @param {?string } pattern Optional pattern to search for 293 * @param {Array<number>} range 294 * @return {tcuTestCase.DeqpTest} 295 */ 296 tcuTestCase.DeqpTest.prototype.nextInRange = function(pattern, range) { 297 while (true) { 298 var test = this._nextHonoringSkipList(pattern); 299 if (!test) 300 return null; 301 var topLevelId = tcuTestCase.runner.testCases.currentTestNdx - 1; 302 if (topLevelId >= range[0] && topLevelId < range[1]) 303 return test; 304 } 305 }; 306 307 /** 308 * Returns the full name of the test 309 * 310 * @return {string} Full test name. 311 */ 312 tcuTestCase.DeqpTest.prototype.fullName = function() { 313 if (this.parentTest) { 314 var parentName = this.parentTest.fullName(); 315 if (parentName) 316 return parentName + '.' + this.name; 317 } 318 return this.name; 319 }; 320 321 /** 322 * Returns the description of the test 323 * 324 * @return {string} Test description. 325 */ 326 tcuTestCase.DeqpTest.prototype.getDescription = function() { 327 return this.description; 328 }; 329 330 /** 331 * Find a test with a matching name. Fast-forwards to a test whose 332 * full name matches the given pattern. 333 * 334 * @param {string} pattern Regular expression to search for 335 * @return {?tcuTestCase.DeqpTest } Found test or null. 336 */ 337 tcuTestCase.DeqpTest.prototype.find = function(pattern) { 338 return this._findHonoringSkipList(pattern); 339 }; 340 341 /** 342 * Find a test with a matching name. Fast-forwards to a test whose 343 * full name matches the given pattern, honoring the skip list, and 344 * reporting skipped tests. 345 * 346 * @param {string} pattern Regular expression to search for 347 * @return {?tcuTestCase.DeqpTest } Found test or null. 348 */ 349 tcuTestCase.DeqpTest.prototype._findHonoringSkipList = function(pattern) { 350 var tryAgain = false; 351 var test = null; 352 do { 353 tryAgain = false; 354 test = this._findIgnoringSkipList(pattern); 355 if (test != null) { 356 // See whether the skip list vetoes the execution of 357 // this test. 358 var fullTestName = test.fullName(); 359 var skipDisposition = tcuSkipList.getSkipStatus(fullTestName); 360 if (skipDisposition.skip) { 361 tryAgain = true; 362 checkMessage(false, 'Skipping test due to tcuSkipList: ' + fullTestName); 363 } 364 } 365 } while (tryAgain); 366 return test; 367 }; 368 369 /** 370 * Find a test with a matching name. Fast-forwards to a test whose 371 * full name matches the given pattern. 372 * 373 * @param {string} pattern Regular expression to search for 374 * @return {?tcuTestCase.DeqpTest } Found test or null. 375 */ 376 tcuTestCase.DeqpTest.prototype._findIgnoringSkipList = function(pattern) { 377 var test = this; 378 while (true) { 379 test = test._nextIgnoringSkipList(null); 380 if (!test) 381 break; 382 if (test.fullName().match(pattern) || test.executeAlways) 383 break; 384 } 385 return test; 386 }; 387 388 /** 389 * Reset the iterator. 390 */ 391 tcuTestCase.DeqpTest.prototype.reset = function() { 392 this.currentTestNdx = 0; 393 394 for (var i = 0; i < this.childrenTests.length; i++) 395 this.childrenTests[i].reset(); 396 }; 397 398 /** 399 * Defines a new test 400 * 401 * @param {?string} name Short test name 402 * @param {?string} description Description of the test 403 * @param {Object=} spec Test specification 404 * 405 * @return {tcuTestCase.DeqpTest} The new test 406 */ 407 tcuTestCase.newTest = function(name, description, spec) { 408 var test = new tcuTestCase.DeqpTest(name, description, spec); 409 410 return test; 411 }; 412 413 /** 414 * Defines a new executable test so it gets run even if it's not a leaf 415 * 416 * @param {string} name Short test name 417 * @param {string} description Description of the test 418 * @param {Object=} spec Test specification 419 * 420 * @return {tcuTestCase.DeqpTest} The new test 421 */ 422 tcuTestCase.newExecutableTest = function(name, description, spec) { 423 var test = tcuTestCase.newTest(name, description, spec); 424 test.makeExecutable(); 425 426 return test; 427 }; 428 429 /** 430 * Run through the test cases giving time to system operation. 431 */ 432 tcuTestCase.runTestCases = function() { 433 var state = tcuTestCase.runner; 434 435 if (state.next()) { 436 try { 437 // If proceeding with the next test, prepare it. 438 var fullTestName = state.currentTest.fullName(); 439 var inited = true; 440 if (tcuTestCase.lastResult == tcuTestCase.IterateResult.STOP) { 441 // Update current test name 442 setCurrentTestName(fullTestName); 443 bufferedLogToConsole('Init testcase: ' + fullTestName); //Show also in console so we can see which test crashed the browser's tab 444 445 // Initialize particular test 446 inited = state.currentTest.init(); 447 inited = inited === undefined ? true : inited; 448 449 //If it's a leaf test, notify of it's execution. 450 if (state.currentTest.isLeaf() && inited) 451 debug('<hr/><br/>Start testcase: ' + fullTestName); 452 } 453 454 if (inited) { 455 // Run the test, save the result. 456 457 const debug = tcuTestCase._debug = tcuTestCase._debug || (() => { 458 function LapStopwatch() { 459 this.lap = function() { 460 const now = performance.now(); 461 const ret = now - this.last; 462 this.last = now; 463 return ret; 464 }; 465 this.lap(); 466 } 467 return { 468 stopwatch: new LapStopwatch(), 469 testDoneCount: 0, 470 }; 471 })(); 472 const overheadDur = debug.stopwatch.lap(); 473 474 tcuTestCase.lastResult = state.currentTest.iterate(); 475 476 const testDur = debug.stopwatch.lap(); 477 debug.testDoneCount += 1; 478 console.log( 479 `[test ${debug.testDoneCount}] Ran in ${testDur}ms`, 480 `(+ ${overheadDur}ms overhead)`, 481 ); 482 } else { 483 // Skip uninitialized test. 484 tcuTestCase.lastResult = tcuTestCase.IterateResult.STOP; 485 } 486 487 // Cleanup 488 if (tcuTestCase.lastResult == tcuTestCase.IterateResult.STOP) 489 state.currentTest.deinit(); 490 } 491 catch (err) { 492 // If the exception was not thrown by a test check, log it, but don't throw it again 493 if (!(err instanceof TestFailedException)) { 494 //Stop execution of current test. 495 tcuTestCase.lastResult = tcuTestCase.IterateResult.STOP; 496 try { 497 // Cleanup 498 if (tcuTestCase.lastResult == tcuTestCase.IterateResult.STOP) 499 state.currentTest.deinit(); 500 } catch (cerr) { 501 bufferedLogToConsole('Error while cleaning up test: ' + cerr); 502 } 503 var msg = err; 504 if (err.message) 505 msg = err.message; 506 testFailedOptions(msg, false); 507 } 508 bufferedLogToConsole(err); 509 } 510 511 tcuTestCase.runner.runCallback(tcuTestCase.runTestCases); 512 } else { 513 tcuTestCase.runner.terminate(); 514 } 515 }; 516 });