1/* 2 * Copyright (c) 2021-2024 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16import SysTestKit from './module/kit/SysTestKit'; 17import { TAG } from './Constant'; 18import LogExpectError from './module/report/LogExpectError'; 19import { NestFilter } from './module/config/Filter'; 20 21class AssertException extends Error { 22 constructor(message) { 23 super(); 24 this.name = 'AssertException'; 25 this.message = message; 26 } 27} 28 29function getFuncWithArgsZero(func, timeout, isStressTest) { 30 return new Promise(async (resolve, reject) => { 31 let timer = null; 32 if (!isStressTest) { 33 timer = setTimeout(() => { 34 reject(new Error('execute timeout ' + timeout + 'ms')); 35 }, timeout); 36 } 37 try { 38 await func(); 39 } catch (err) { 40 reject(err); 41 } 42 timer !== null ? clearTimeout(timer) : null; 43 resolve(); 44 }); 45} 46 47function getFuncWithArgsOne(func, timeout, isStressTest) { 48 return new Promise(async (resolve, reject) => { 49 let timer = null; 50 if (!isStressTest) { 51 timer = setTimeout(() => { 52 reject(new Error('execute timeout ' + timeout + 'ms')); 53 }, timeout); 54 } 55 56 function done() { 57 timer !== null ? clearTimeout(timer) : null; 58 resolve(); 59 } 60 61 try { 62 await func(done); 63 } catch (err) { 64 timer !== null ? clearTimeout(timer) : null; 65 reject(err); 66 } 67 }); 68} 69 70function getFuncWithArgsTwo(func, timeout, paramItem, isStressTest) { 71 return new Promise(async (resolve, reject) => { 72 let timer = null; 73 if (!isStressTest) { 74 timer = setTimeout(() => { 75 reject(new Error('execute timeout ' + timeout + 'ms')); 76 }, timeout); 77 } 78 79 function done() { 80 timer !== null ? clearTimeout(timer) : null; 81 resolve(); 82 } 83 84 try { 85 await func(done, paramItem); 86 } catch (err) { 87 timer !== null ? clearTimeout(timer) : null; 88 reject(err); 89 } 90 }); 91} 92 93function processFunc(coreContext, func) { 94 let argNames = ((func || '').toString() 95 .replace(/((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg, '') 96 .match(/^(function)?\s*[^\(]*\(\s*([^\)]*)\)/m) || ['', '', ''])[2] 97 .split(',') // split parameters 98 .map(item => item.replace(/^\s*(_?)(.+?)\1\s*$/, name => name.split('=')[0].trim())) 99 .filter(String); 100 let funcLen = func.length; 101 let processedFunc; 102 const config = coreContext.getDefaultService('config'); 103 config.setSupportAsync(true); 104 const timeout = + (config.timeout === undefined ? 5000 : config.timeout); 105 const isStressTest = (coreContext.getServices('dataDriver') !== undefined || config.getStress() > 1); 106 switch (funcLen) { 107 case 0: { 108 processedFunc = function () { 109 return getFuncWithArgsZero(func, timeout, isStressTest); 110 }; 111 break; 112 } 113 case 1: { 114 if (argNames[0] === 'data') { 115 processedFunc = function (paramItem) { 116 func(paramItem); 117 }; 118 } else { 119 processedFunc = function () { 120 return getFuncWithArgsOne(func, timeout, isStressTest); 121 }; 122 } 123 break; 124 } 125 default: { 126 processedFunc = function (paramItem) { 127 return getFuncWithArgsTwo(func, timeout, paramItem, isStressTest); 128 }; 129 break; 130 } 131 } 132 return processedFunc; 133} 134 135function secureRandomNumber() { 136 return crypto.randomBytes(8).readUInt32LE() / 0xffffffff; 137} 138 139class SuiteService { 140 constructor(attr) { 141 this.id = attr.id; 142 this.rootSuite = new SuiteService.Suite({}); 143 this.currentRunningSuite = this.rootSuite; 144 this.suitesStack = [this.rootSuite]; 145 this.targetSuiteArray = []; 146 this.targetSpecArray = []; 147 this.currentRunningSuiteDesc = null; 148 this.fullRun = false; 149 this.isSkipSuite = false; 150 this.suiteSkipReason = null; 151 } 152 153 describe(desc, func) { 154 const configService = this.coreContext.getDefaultService('config'); 155 if (this.suitesStack.some(suite => { return suite.description === desc })) { 156 console.error(`${TAG} Loop nesting occurs : ${desc}`); 157 this.suiteSkipReason = ''; 158 this.isSkipSuite = false; 159 return; 160 } 161 let isFilter = this.analyzeConfigServiceClass(configService.class, desc); 162 if (configService.filterSuite(desc) && isFilter) { 163 if (this.currentRunningSuite.description === '' || this.currentRunningSuite.description == null) { 164 console.info(`${TAG}filter suite : ${desc}`); 165 this.suiteSkipReason = ''; 166 this.isSkipSuite = false; 167 return; 168 } 169 } 170 const suite = new SuiteService.Suite({ description: desc }); 171 if (this.isSkipSuite) { 172 suite.isSkip = true; 173 suite.skipReason = this.suiteSkipReason; 174 } 175 this.suiteSkipReason = ''; 176 this.isSkipSuite = false; 177 if (typeof this.coreContext.getServices('dataDriver') !== 'undefined' && configService['dryRun'] !== 'true') { 178 let suiteStress = this.coreContext.getServices('dataDriver').dataDriver.getSuiteStress(desc); 179 for (let i = 1; i < suiteStress; i++) { 180 this.currentRunningSuite.childSuites.push(suite); 181 } 182 } 183 this.currentRunningSuite.childSuites.push(suite); 184 this.currentRunningSuite = suite; 185 this.suitesStack.push(suite); 186 func.call(); 187 this.suitesStack.pop(); 188 this.currentRunningSuite = this.suitesStack.pop(); 189 this.suitesStack.push(this.currentRunningSuite); 190 } 191 xdescribe(desc, func, reason) { 192 const configService = this.coreContext.getDefaultService('config'); 193 if (!configService.skipMessage && configService.runSkipped !== 'all') { 194 if (configService.runSkipped != null && configService.runSkipped !== '') { 195 let finalDesc = ''; 196 this.suitesStack.map(suite => { 197 finalDesc = finalDesc + '.' + suite.description; 198 }); 199 finalDesc = (finalDesc + '.' + desc).substring(2); 200 console.info(`${TAG} finalDesc ${finalDesc}`); 201 if (configService.checkIfSuiteInSkipRun(finalDesc)) { 202 console.info(`${TAG} runSkipped suite: ${desc}`); 203 } else { 204 console.info(reason == null ? `${TAG} skip suite: ${desc}` : `${TAG} skip suite: ${desc}, and the reason is ${reason}`); 205 return; 206 } 207 } else { 208 console.info(reason == null ? `${TAG} skip suite: ${desc}` : `${TAG} skip suite: ${desc}, and the reason is ${reason}`); 209 return; 210 } 211 } 212 this.isSkipSuite = true; 213 this.suiteSkipReason = reason; 214 this.describe(desc, func); 215 } 216 217 beforeAll(func) { 218 this.currentRunningSuite.beforeAll.push(processFunc(this.coreContext, func)); 219 } 220 221 beforeEach(func) { 222 this.currentRunningSuite.beforeEach.push(processFunc(this.coreContext, func)); 223 } 224 225 beforeItSpecified(itDescs, func) { 226 this.currentRunningSuite.beforeItSpecified.set(itDescs, processFunc(this.coreContext, func)); 227 } 228 229 afterItSpecified(itDescs, func) { 230 this.currentRunningSuite.afterItSpecified.set(itDescs, processFunc(this.coreContext, func)); 231 } 232 233 afterAll(func) { 234 this.currentRunningSuite.afterAll.push(processFunc(this.coreContext, func)); 235 } 236 237 afterEach(func) { 238 this.currentRunningSuite.afterEach.push(processFunc(this.coreContext, func)); 239 } 240 241 getCurrentRunningSuite() { 242 return this.currentRunningSuite; 243 } 244 245 setCurrentRunningSuite(suite) { 246 this.currentRunningSuite = suite; 247 } 248 249 getRootSuite() { 250 return this.rootSuite; 251 } 252 253 getCurrentRunningSuiteDesc() { 254 return this.currentRunningSuiteDesc; 255 } 256 257 258 setCurrentRunningSuiteDesc(suite, currentSuite, prefix) { 259 if (suite != null && suite === currentSuite) { 260 this.currentRunningSuiteDesc = prefix; 261 } else if (suite != null && suite !== currentSuite) { 262 suite.childSuites.forEach(it => { 263 let temp = prefix; 264 if (it.description != null || it.description !== '') { 265 temp = prefix === '' ? it.description : prefix + '.' + it.description; 266 } 267 this.setCurrentRunningSuiteDesc(it, currentSuite, temp); 268 } 269 ); 270 } 271 } 272 analyzeConfigServiceClass(configServiceClass, desc) { 273 if (configServiceClass == null || configServiceClass === '') { 274 this.fullRun = true 275 return false; 276 } 277 if (this.targetSuiteArray.length === 0) { 278 const targetArray = configServiceClass.split(','); 279 for (let index in targetArray) { 280 if (targetArray[index].includes('#')) { 281 this.targetSpecArray.push(targetArray[index]); 282 } else { 283 this.targetSuiteArray.push(targetArray[index]); 284 } 285 } 286 287 } 288 return !configServiceClass.includes(desc); 289 290 } 291 traversalResults(suite, obj, breakOnError) { 292 if (suite.childSuites.length === 0 && suite.specs.length === 0) { 293 return obj; 294 } 295 if (suite.specs.length > 0) { 296 for (const itItem of suite.specs) { 297 obj.total++; 298 let itInfo = { 299 currentThreadName: 'mainThread', 300 description: suite.description + '#' + itItem.description, 301 result: -3 302 }; 303 if (SysTestKit.workerPort !== null) { 304 itInfo.currentThreadName = SysTestKit.workerPort.name; 305 } 306 obj.itItemList.push(itInfo); 307 if (breakOnError && (obj.error > 0 || obj.failure > 0)) { // breakOnError模式 308 continue; 309 } 310 if (itItem.error) { 311 obj.error++; 312 itInfo.result = -1; 313 } else if (itItem.fail) { 314 obj.failure++; 315 itInfo.result = -2; 316 } else if (itItem.pass === true) { 317 obj.pass++; 318 itInfo.result = 0; 319 } 320 } 321 } 322 323 obj.duration += suite.duration; 324 325 if (suite.childSuites.length > 0) { 326 for (const suiteItem of suite.childSuites) { 327 this.traversalResults(suiteItem, obj, breakOnError); 328 } 329 } 330 } 331 332 async setSuiteResults(suite, error, coreContext) { 333 if (suite.childSuites.length === 0 && suite.specs.length === 0) { 334 return obj; 335 } 336 if (suite.specs.length > 0) { 337 const specService = coreContext.getDefaultService('spec'); 338 for (const specItem of suite.specs) { 339 specService.setCurrentRunningSpec(specItem); 340 if (error instanceof AssertException) { 341 specItem.fail = error; 342 } else { 343 specItem.error = error; 344 } 345 await coreContext.fireEvents('spec', 'specStart', specItem); 346 await coreContext.fireEvents('spec', 'specDone', specItem); 347 } 348 } 349 if (suite.childSuites.length > 0) { 350 for (const suiteItem of suite.childSuites) { 351 await this.setSuiteResults(suiteItem, error, coreContext); 352 } 353 } 354 } 355 356 getSummary() { 357 let suiteService = this.coreContext.getDefaultService('suite'); 358 let rootSuite = suiteService.rootSuite; 359 const specService = this.coreContext.getDefaultService('spec'); 360 const configService = this.coreContext.getDefaultService('config'); 361 let breakOnError = configService.isBreakOnError(); 362 let isError = specService.getStatus(); 363 let isBreaKOnError = breakOnError && isError; 364 // itItemList 保存当前用例执行情况, 发送到主线程用例计算最终结果 365 let obj = { total: 0, failure: 0, error: 0, pass: 0, ignore: 0, duration: 0, itItemList: []}; 366 for (const suiteItem of rootSuite.childSuites) { 367 this.traversalResults(suiteItem, obj, isBreaKOnError); 368 } 369 obj.ignore = obj.total - obj.pass - obj.failure - obj.error; 370 return obj; 371 } 372 373 init(coreContext) { 374 this.coreContext = coreContext; 375 } 376 377 traversalSuites(suite, obj, configService) { 378 if (suite.childSuites.length === 0 && suite.specs.length === 0) { 379 return []; 380 } 381 if (suite.specs.length > 0) { 382 let itArray = []; 383 for (const itItem of suite['specs']) { 384 if (!configService.filterDesc(suite.description, itItem.description, itItem.fi, null)) { 385 itArray.push({ 'itName': itItem.description }); 386 } 387 } 388 obj[suite.description] = itArray; 389 } 390 if (suite.childSuites.length > 0) { 391 let suiteArray = []; 392 for (const suiteItem of suite.childSuites) { 393 let suiteObj = {}; 394 this.traversalSuites(suiteItem, suiteObj, configService); 395 if (!configService.filterSuite(suiteItem.description)) { 396 suiteArray.push(suiteObj); 397 } 398 } 399 obj.suites = suiteArray; 400 } 401 } 402 403 async dryRun(abilityDelegator) { 404 console.info(`${TAG} rootSuite : ` + JSON.stringify(this.rootSuite)); 405 let obj = this.rootSuite; 406 let prefixStack = []; 407 let suiteArray = []; 408 let skipSuiteArray = []; 409 this.analyzeSuitesArray(prefixStack, suiteArray, skipSuiteArray, obj); 410 const configService = this.coreContext.getDefaultService('config'); 411 let result; 412 if (configService.skipMessage) { 413 result = { 'suites': suiteArray, 'skipSuites': skipSuiteArray }; 414 } else { 415 result = { 'suites': suiteArray }; 416 } 417 let strJson = JSON.stringify(result); 418 let strLen = strJson.length; 419 let maxLen = 500; 420 let maxCount = Math.floor(strLen / maxLen); 421 for (let count = 0; count <= maxCount; count++) { 422 await SysTestKit.print(strJson.substring(count * maxLen, (count + 1) * maxLen)); 423 } 424 console.info(`${TAG}dryRun print success`); 425 abilityDelegator.finishTest('dry run finished!!!', 0, () => { }); 426 } 427 428 //将suitesArray的嵌套结构展开成三层结构 429 analyzeSuitesArray(prefixStack, suiteArray, skipSuiteArray, obj) { 430 obj.childSuites.map(suite => { 431 if (suite.description != null && suite.description !== '') { 432 let prefix = ''; 433 if (prefixStack.length > 0) { 434 prefix = prefixStack.join('.') + '.' + suite.description; 435 } else { 436 prefix = suite.description; 437 } 438 prefixStack.push(suite.description); 439 let temp = {}; 440 temp[prefix] = []; 441 let skipTemp = {}; 442 skipTemp[prefix] = []; 443 suite.specs.map(spec => { 444 let it = { 'itName': spec.description }; 445 spec.isSkip ? skipTemp[prefix].push(it) : temp[prefix].push(it); 446 }); 447 suiteArray.push(temp); 448 skipSuiteArray.push(skipTemp); 449 } 450 this.analyzeSuitesArray(prefixStack, suiteArray, skipSuiteArray, suite); 451 prefixStack.pop(); 452 }); 453 } 454 //获取当前测试套下的所有测试用例数量 455 getAllChildSuiteNum(suite, specArray) { 456 if (suite.specs != null) { 457 suite.specs.forEach(spec => specArray.push(spec)); 458 } 459 if (suite.childSuites != null) { 460 suite.childSuites.forEach(it => this.getAllChildSuiteNum(it, specArray)); 461 } 462 } 463 464 execute() { 465 const configService = this.coreContext.getDefaultService('config'); 466 if (configService.filterValid.length !== 0) { 467 this.coreContext.fireEvents('task', 'incorrectFormat'); 468 return; 469 } 470 if (configService.filterXdescribe.length !== 0) { 471 this.coreContext.fireEvents('task', 'incorrectTestSuiteFormat'); 472 return; 473 } 474 if (configService.isRandom() && this.rootSuite.childSuites.length > 0) { 475 this.rootSuite.childSuites.sort(function () { 476 return Math.random().toFixed(1) > 0.5 ? -1 : 1; 477 }); 478 this.currentRunningSuite = this.rootSuite.childSuites[0]; 479 } 480 if (configService.isSupportAsync()) { 481 console.info(`${TAG} rootSuite:` + JSON.stringify(this.rootSuite)); 482 let asyncExecute = async () => { 483 await this.coreContext.fireEvents('task', 'taskStart'); 484 await this.rootSuite.asyncRun(this.coreContext); 485 }; 486 asyncExecute().then(async () => { 487 await this.coreContext.fireEvents('task', 'taskDone'); 488 }); 489 } else { 490 console.info('${TAG} rootSuite:' + JSON.stringify(this.rootSuite)); 491 this.coreContext.fireEvents('task', 'taskStart'); 492 this.rootSuite.run(this.coreContext); 493 this.coreContext.fireEvents('task', 'taskDone'); 494 } 495 } 496 497 apis() { 498 const _this = this; 499 return { 500 describe: function (desc, func) { 501 return _this.describe(desc, func); 502 }, 503 xdescribe: function (desc, func, reason) { 504 return _this.xdescribe(desc, func, reason); 505 }, 506 beforeItSpecified: function (itDescs, func) { 507 return _this.beforeItSpecified(itDescs, func); 508 }, 509 afterItSpecified: function (itDescs, func) { 510 return _this.afterItSpecified(itDescs, func); 511 }, 512 beforeAll: function (func) { 513 return _this.beforeAll(func); 514 }, 515 beforeEach: function (func) { 516 return _this.beforeEach(func); 517 }, 518 afterAll: function (func) { 519 return _this.afterAll(func); 520 }, 521 afterEach: function (func) { 522 return _this.afterEach(func); 523 } 524 }; 525 } 526} 527 528SuiteService.Suite = class { 529 constructor(attrs) { 530 this.description = attrs.description || ''; 531 this.childSuites = []; 532 this.specs = []; 533 this.beforeAll = []; 534 this.afterAll = []; 535 this.beforeItSpecified = new Map(); 536 this.afterItSpecified = new Map(); 537 this.beforeEach = []; 538 this.afterEach = []; 539 this.duration = 0; 540 this.hookError = null; 541 this.isSkip = false; 542 this.skipReason = ''; 543 } 544 545 pushSpec(spec) { 546 this.specs.push(spec); 547 } 548 549 removeSpec(desc) { 550 this.specs = this.specs.filter((item, index) => { 551 return item.description !== desc; 552 }); 553 } 554 555 getSpecsNum() { 556 return this.specs.length; 557 } 558 559 isRun(coreContext) { 560 const configService = coreContext.getDefaultService('config'); 561 const suiteService = coreContext.getDefaultService('suite'); 562 const specService = coreContext.getDefaultService('spec'); 563 let breakOnError = configService.isBreakOnError(); 564 let isError = specService.getStatus(); 565 return breakOnError && isError; 566 } 567 568 run(coreContext) { 569 const suiteService = coreContext.getDefaultService('suite'); 570 suiteService.setCurrentRunningSuite(this); 571 if (this.description !== '') { 572 coreContext.fireEvents('suite', 'suiteStart', this); 573 } 574 this.runHookFunc('beforeAll'); 575 if (this.specs.length > 0) { 576 const configService = coreContext.getDefaultService('config'); 577 if (configService.isRandom()) { 578 this.specs.sort(function () { 579 return Math.random().toFixed(1) > 0.5 ? -1 : 1; 580 }); 581 } 582 for (let spec in this.specs) { 583 let isBreakOnError = this.isRun(coreContext); 584 if (isBreakOnError) { 585 break; 586 } 587 this.runHookFunc('beforeEach'); 588 spec.run(coreContext); 589 this.runHookFunc('afterEach'); 590 } 591 } 592 if (this.childSuites.length > 0) { 593 for (let suite in this.childSuites) { 594 let isBreakOnError = this.isRun(coreContext); 595 if (isBreakOnError) { 596 break; 597 } 598 suite.run(coreContext); 599 suiteService.setCurrentRunningSuite(suite); 600 } 601 } 602 this.runHookFunc('afterAll'); 603 if (this.description !== '') { 604 coreContext.fireEvents('suite', 'suiteDone'); 605 } 606 } 607 608 async asyncRunSpecs(coreContext) { 609 const configService = coreContext.getDefaultService('config'); 610 if (configService.isRandom()) { 611 this.specs.sort(function () { 612 return Math.random().toFixed(1) > 0.5 ? -1 : 1; 613 }); 614 } 615 const specService = coreContext.getDefaultService('spec'); 616 for (let specItem of this.specs) { 617 specService.setCurrentRunningSpec(specItem); 618 // 遇错即停模式,发现用例有问题,直接返回,不在执行后面的it 619 let isBreakOnError = this.isRun(coreContext); 620 if (isBreakOnError) { 621 console.info('break description :' + this.description); 622 break; 623 } 624 await coreContext.fireEvents('spec', 'specStart', specItem); 625 try { 626 for (const [itNames, hookFunc] of this.beforeItSpecified) { 627 if ((Object.prototype.toString.call(itNames) === '[object Array]' && itNames.includes(specItem.description)) || 628 (Object.prototype.toString.call(itNames) === '[object String]' && itNames === specItem.description)) { 629 await Reflect.apply(hookFunc, null, []); 630 } 631 break; 632 } 633 await this.runAsyncHookFunc('beforeEach'); 634 await specItem.asyncRun(coreContext); 635 for (const [itNames, hookFunc] of this.afterItSpecified) { 636 if ((Object.prototype.toString.call(itNames) === '[object Array]' && itNames.includes(specItem.description)) || 637 (Object.prototype.toString.call(itNames) === '[object String]' && itNames === specItem.description)) { 638 await Reflect.apply(hookFunc, null, []); 639 } 640 break; 641 } 642 await this.runAsyncHookFunc('afterEach'); 643 } catch (e) { 644 console.error(`${TAG}stack:${e?.stack}`); 645 console.error(`${TAG}stack end`); 646 if (e instanceof AssertException) { 647 specItem.fail = e; 648 } else { 649 specItem.error = e; 650 } 651 specService.setStatus(true); 652 } 653 specItem.setResult(); 654 await coreContext.fireEvents('spec', 'specDone', specItem); 655 specService.setCurrentRunningSpec(null); 656 } 657 } 658 659 async asyncRunChildSuites(coreContext) { 660 for (let i = 0; i < this.childSuites.length; i++) { 661 // 遇错即停模式, 发现用例有问题,直接返回,不在执行后面的description 662 let isBreakOnError = this.isRun(coreContext); 663 if (isBreakOnError) { 664 console.info(`${TAG}break description : ${this.description}`); 665 break; 666 } 667 await this.childSuites[i].asyncRun(coreContext); 668 } 669 } 670 671 async asyncRun(coreContext) { 672 const suiteService = coreContext.getDefaultService('suite'); 673 const specService = coreContext.getDefaultService('spec'); 674 675 suiteService.setCurrentRunningSuite(this); 676 suiteService.suitesStack.push(this); 677 if (this.description !== '') { 678 await coreContext.fireEvents('suite', 'suiteStart', this); 679 } 680 681 try { 682 await this.runAsyncHookFunc('beforeAll'); 683 } catch (error) { 684 console.error(`${TAG}${error?.stack}`); 685 this.hookError = error; 686 } 687 688 if (this.hookError !== null) { 689 specService.setStatus(true); 690 await suiteService.setSuiteResults(this, this.hookError, coreContext); 691 } 692 693 if (this.specs.length > 0 && this.hookError === null) { 694 await this.asyncRunSpecs(coreContext); 695 } 696 697 if (this.childSuites.length > 0 && this.hookError === null) { 698 await this.asyncRunChildSuites(coreContext); 699 } 700 701 try { 702 await this.runAsyncHookFunc('afterAll'); 703 } catch (error) { 704 console.error(`${TAG}${error?.stack}`); 705 this.hookError = error; 706 specService.setStatus(true); 707 } 708 709 if (this.description !== '') { 710 await coreContext.fireEvents('suite', 'suiteDone'); 711 let childSuite = suiteService.suitesStack.pop(); 712 let currentRunningSuite = suiteService.suitesStack.pop(); 713 suiteService.setCurrentRunningSuite(currentRunningSuite); 714 suiteService.suitesStack.push(currentRunningSuite); 715 } 716 } 717 718 runHookFunc(hookName) { 719 if (this[hookName] && this[hookName].length > 0) { 720 this[hookName].forEach(func => { 721 try { 722 func(); 723 } catch (e) { 724 console.error(`${TAG}${e.stack}`); 725 } 726 }); 727 } 728 } 729 730 async runAsyncHookFunc(hookName) { 731 for (const hookItem of this[hookName]) { 732 try { 733 await hookItem(); 734 } catch (error) { 735 error['message'] += `, error in ${hookName} function`; 736 throw error; 737 } 738 739 } 740 } 741}; 742 743class SpecService { 744 constructor(attr) { 745 this.id = attr.id; 746 this.totalTest = 0; 747 this.hasError = false; 748 this.skipSpecNum = 0; 749 this.isSkipSpec = false; 750 this.specSkipReason = ''; 751 } 752 753 init(coreContext) { 754 this.coreContext = coreContext; 755 } 756 757 setCurrentRunningSpec(spec) { 758 this.currentRunningSpec = spec; 759 } 760 761 setStatus(obj) { 762 this.hasError = obj; 763 } 764 765 getStatus() { 766 return this.hasError; 767 } 768 769 getTestTotal() { 770 return this.totalTest; 771 } 772 773 getCurrentRunningSpec() { 774 return this.currentRunningSpec; 775 } 776 777 778 getSkipSpecNum() { 779 return this.skipSpecNum; 780 } 781 782 initSpecService() { 783 this.isSkipSpec = false; 784 this.specSkipReason = ''; 785 } 786 787 it(desc, filter, func) { 788 const suiteService = this.coreContext.getDefaultService('suite'); 789 const configService = this.coreContext.getDefaultService('config'); 790 let isFilter = new NestFilter().filterNestName(suiteService.targetSuiteArray, suiteService.targetSpecArray, suiteService.suitesStack, desc); 791 if (configService.filterWithNest(desc, filter)) { 792 console.info(`${TAG}filter it :${desc}`); 793 this.initSpecService(); 794 return; 795 } 796 if (configService.filterDesc(suiteService.currentRunningSuite.description, desc, filter, this.coreContext) && isFilter && !suiteService.fullRun) { 797 console.info(`${TAG}filter it :${desc}`); 798 this.initSpecService(); 799 } else { 800 let processedFunc = processFunc(this.coreContext, func); 801 const spec = new SpecService.Spec({ description: desc, fi: filter, fn: processedFunc }); 802 if (this.isSkipSpec) { 803 spec.isSkip = true; 804 spec.skipReason = this.specSkipReason; 805 } 806 this.initSpecService(); 807 if (configService.runSkipped === 'skipped' && !spec.isSkip) { 808 console.info(`${TAG} runSkipped is skipped , just run xit, don't run it: ${spec.description}`); 809 return; 810 } 811 if (suiteService.getCurrentRunningSuite().isSkip && !spec.isSkip) { 812 configService.filterXdescribe.push(suiteService.getCurrentRunningSuite().description); 813 } 814 if (typeof this.coreContext.getServices('dataDriver') !== 'undefined' && configService['dryRun'] !== 'true') { 815 let specStress = this.coreContext.getServices('dataDriver').dataDriver.getSpecStress(desc); 816 for (let i = 1; i < specStress; i++) { 817 this.totalTest++; 818 suiteService.getCurrentRunningSuite().pushSpec(spec); 819 } 820 } 821 // dryRun 状态下不统计压力测试重复数据 822 if (configService['dryRun'] !== 'true') { 823 let stress = configService.getStress(); // 命令配置压力测试 824 console.info(`${TAG}stress length : ${stress}`); 825 for (let i = 1; i < stress; i++) { 826 this.totalTest++; 827 suiteService.getCurrentRunningSuite().pushSpec(spec); 828 } 829 } 830 this.totalTest++; 831 suiteService.getCurrentRunningSuite().pushSpec(spec); 832 } 833 } 834 835 xit(desc, filter, func, reason) { 836 const configService = this.coreContext.getDefaultService('config'); 837 const suiteService = this.coreContext.getDefaultService('suite'); 838 if (!configService.skipMessage && configService.runSkipped !== 'all') { 839 if (configService.runSkipped != null && configService.runSkipped !== '') { 840 let finalDesc = ''; 841 suiteService.suitesStack.map(suite => { 842 finalDesc = finalDesc + '.' + suite.description; 843 }); 844 finalDesc = (finalDesc + '#' + desc).substring(2); 845 if (configService.checkIfSpecInSkipRun(finalDesc)) { 846 console.info(`${TAG} runSkipped spec: ${desc}`); 847 } else { 848 console.info(reason == null ? `${TAG} skip spec: ${desc}` : `${TAG} skip spec: ${desc}, and the reason is ${reason}`); 849 return; 850 } 851 } else { 852 console.info(reason == null ? `${TAG} skip spec: ${desc}` : `${TAG} skip spec: ${desc}, and the reason is ${reason}`); 853 return; 854 } 855 } 856 this.skipSpecNum++; 857 this.isSkipSpec = true; 858 this.specSkipReason = reason; 859 this.it(desc, filter, func); 860 } 861 862 apis() { 863 const _this = this; 864 return { 865 it: function (desc, filter, func) { 866 return _this.it(desc, filter, func); 867 }, 868 xit: function (desc, filter, func, reason) { 869 return _this.xit(desc, filter, func, reason); 870 } 871 }; 872 } 873} 874 875SpecService.Spec = class { 876 constructor(attrs) { 877 this.description = attrs.description || ''; 878 this.fi = attrs.fi; 879 this.fn = attrs.fn || function () { 880 }; 881 this.fail = undefined; 882 this.error = undefined; 883 this.duration = 0; 884 this.startTime = 0; 885 this.isExecuted = false; // 当前用例是否执行 886 this.isSkip = false; 887 this.skipReason = ''; 888 this.expectMsg = ''; 889 } 890 891 setResult() { 892 if (this.fail) { 893 this.pass = false; 894 } else { 895 this.pass = true; 896 } 897 } 898 899 run(coreContext) { 900 const specService = coreContext.getDefaultService('spec'); 901 specService.setCurrentRunningSpec(this); 902 coreContext.fireEvents('spec', 'specStart', this); 903 this.isExecuted = true; 904 try { 905 let dataDriver = coreContext.getServices('dataDriver'); 906 if (typeof dataDriver === 'undefined') { 907 this.fn(); 908 } else { 909 let suiteParams = dataDriver.dataDriver.getSuiteParams(); 910 let specParams = dataDriver.dataDriver.getSpecParams(); 911 console.info(`${TAG}[suite params] ${JSON.stringify(suiteParams)}`); 912 console.info(`${TAG}[spec params] ${JSON.stringify(specParams)}`); 913 if (this.fn.length === 0) { 914 this.fn(); 915 } else if (specParams.length === 0) { 916 this.fn(suiteParams); 917 } else { 918 specParams.forEach(paramItem => this.fn(Object.assign({}, paramItem, suiteParams))); 919 } 920 } 921 this.setResult(); 922 } catch (e) { 923 this.error = e; 924 specService.setStatus(true); 925 } 926 coreContext.fireEvents('spec', 'specDone', this); 927 } 928 929 async asyncRun(coreContext) { 930 const dataDriver = coreContext.getServices('dataDriver'); 931 if (typeof dataDriver === 'undefined') { 932 await this.fn(); 933 } else { 934 const suiteParams = dataDriver.dataDriver.getSuiteParams(); 935 const specParams = dataDriver.dataDriver.getSpecParams(); 936 console.info(`[suite params] ${JSON.stringify(suiteParams)}`); 937 console.info(`[spec params] ${JSON.stringify(specParams)}`); 938 if (this.fn.length === 0) { 939 await this.fn(); 940 } else if (specParams.length === 0) { 941 await this.fn(suiteParams); 942 } else { 943 for (const paramItem of specParams) { 944 await this.fn(Object.assign({}, paramItem, suiteParams)); 945 } 946 } 947 } 948 949 this.isExecuted = true; 950 } 951 952 filterCheck(coreContext) { 953 const specService = coreContext.getDefaultService('spec'); 954 specService.setCurrentRunningSpec(this); 955 return true; 956 } 957}; 958 959class ExpectService { 960 constructor(attr) { 961 this.id = attr.id; 962 this.matchers = {}; 963 this.customMatchers = []; 964 } 965 966 expect(actualValue) { 967 return this.wrapMatchers(actualValue); 968 } 969 970 init(coreContext) { 971 this.coreContext = coreContext; 972 this.addMatchers(this.basicMatchers()); 973 } 974 975 addMatchers(matchers) { 976 for (const matcherName in matchers) { 977 if (Object.prototype.hasOwnProperty.call(matchers, matcherName)) { 978 this.matchers[matcherName] = matchers[matcherName]; 979 } 980 } 981 } 982 983 removeMatchers(customAssertionName) { 984 if (customAssertionName === 'all') { 985 for (const matcherName in this.matchers) { 986 this.matchers[matcherName] = this.customMatchers.includes(matcherName) ? (() => {throw new Error(`${matcherName} is unregistered`)}) : undefined; 987 } 988 }else { 989 this.matchers[customAssertionName] = () => { 990 throw new Error(`${customAssertionName} is unregistered`) 991 }; 992 } 993 } 994 995 basicMatchers() { 996 return { 997 assertTrue: function (actualValue) { 998 return { 999 pass: (actualValue) === true, 1000 message: 'expect true, actualValue is ' + actualValue 1001 }; 1002 }, 1003 assertEqual: function (actualValue, args) { 1004 let msg = 'expect ' + actualValue + ' equals ' + args[0]; 1005 if (actualValue == args[0]) { // 数值相同,提示数据类型 1006 const aClassName = Object.prototype.toString.call(actualValue); 1007 const bClassName = Object.prototype.toString.call(args[0]); 1008 msg = 'expect ' + actualValue + aClassName + ' equals ' + args[0] + bClassName + 'strict mode inspect type'; 1009 } 1010 return { 1011 pass: (actualValue) === args[0], 1012 expectValue: args[0], 1013 message: msg 1014 }; 1015 }, 1016 assertThrow: function (actual, args) { 1017 const result = { 1018 pass: false 1019 }; 1020 if (typeof actual !== 'function') { 1021 result.message = 'toThrow\'s Actual should be a Function'; 1022 } else { 1023 let hasThrow = false; 1024 let throwError; 1025 try { 1026 actual(); 1027 } catch (e) { 1028 hasThrow = true; 1029 throwError = e; 1030 } 1031 if (!hasThrow) { 1032 result.message = 'function did not throw an exception'; 1033 } else if (throwError && throwError.message === args[0]) { 1034 result.pass = true; 1035 } else { 1036 result.message = `expect to throw ${args[0]} , actual throw ${throwError.message}`; 1037 } 1038 } 1039 return result; 1040 } 1041 }; 1042 } 1043 1044 initWrapMatchers(currentRunningSpec) { 1045 return { 1046 // 翻转标识 1047 isNot: false, 1048 // 翻转方法 1049 not: function () { 1050 this.isNot = true; 1051 return this; 1052 }, 1053 message: function (msg) { 1054 currentRunningSpec.expectMsg = msg; 1055 console.info(`${TAG} msg: ${msg}`); 1056 return this; 1057 } 1058 }; 1059 1060 } 1061 wrapMatchers(actualValue) { 1062 const _this = this; 1063 const specService = _this.coreContext.getDefaultService('spec'); 1064 const currentRunningSpec = specService.getCurrentRunningSpec(); 1065 const wrappedMatchers = this.initWrapMatchers(currentRunningSpec); 1066 const currentRunningSuite = _this.coreContext.getDefaultService('suite').getCurrentRunningSuite(); 1067 for (const matcherName in this.matchers) { 1068 let result = Object.prototype.hasOwnProperty.call(this.matchers, matcherName); 1069 if (!result) { 1070 continue; 1071 } 1072 if (matcherName.search('assertPromise') == 0) { 1073 wrappedMatchers[matcherName] = async function () { 1074 await _this.matchers[matcherName](actualValue, arguments).then(function (result) { 1075 if (wrappedMatchers.isNot) { 1076 result.pass = !result.pass; 1077 } 1078 result.actualValue = actualValue; 1079 result.checkFunc = matcherName; 1080 if (!result.pass) { 1081 const assertError = new AssertException(result.message); 1082 currentRunningSpec ? currentRunningSpec.fail = assertError : currentRunningSuite.hookError = assertError; 1083 throw assertError; 1084 } 1085 }); 1086 }; 1087 } else { 1088 wrappedMatchers[matcherName] = function () { 1089 const result = _this.customMatchers.includes(matcherName) ? _this.matchers[matcherName](actualValue, arguments[0]) : _this.matchers[matcherName](actualValue, arguments); 1090 if (wrappedMatchers.isNot) { 1091 result.pass = !result.pass; 1092 result.message = LogExpectError.getErrorMsg(matcherName, actualValue, arguments[0], result.message); 1093 } 1094 result.actualValue = actualValue; 1095 result.checkFunc = matcherName; 1096 if (!result.pass) { 1097 const assertError = new AssertException(result.message); 1098 currentRunningSpec ? currentRunningSpec.fail = assertError : currentRunningSuite.hookError = assertError; 1099 throw assertError; 1100 } 1101 }; 1102 } 1103 } 1104 return wrappedMatchers; 1105 } 1106 1107 apis() { 1108 const _this = this; 1109 return { 1110 expect: function (actualValue) { 1111 return _this.expect(actualValue); 1112 } 1113 }; 1114 } 1115} 1116 1117class ReportService { 1118 constructor(attr) { 1119 this.id = attr.id; 1120 } 1121 1122 init(coreContext) { 1123 this.coreContext = coreContext; 1124 this.specService = this.coreContext.getDefaultService('spec'); 1125 this.suiteService = this.coreContext.getDefaultService('suite'); 1126 this.duration = 0; 1127 } 1128 1129 taskStart() { 1130 console.info(`${TAG}[start] start run suites`); 1131 } 1132 1133 async suiteStart() { 1134 console.info(`${TAG}[suite start]${this.suiteService.getCurrentRunningSuite().description}`); 1135 } 1136 1137 async specStart() { 1138 console.info(`${TAG}start running case '${this.specService.currentRunningSpec.description}'`); 1139 this.index = this.index + 1; 1140 let spec = this.specService.currentRunningSpec; 1141 spec.startTime = await SysTestKit.getRealTime(); 1142 } 1143 1144 async specDone() { 1145 let msg = ''; 1146 let spec = this.specService.currentRunningSpec; 1147 let suite = this.suiteService.currentRunningSuite; 1148 spec.duration = await SysTestKit.getRealTime() - spec.startTime; 1149 suite.duration += spec.duration; 1150 if (spec.error) { 1151 this.formatPrint('error', spec.description + ' ; consuming ' + spec.duration + 'ms'); 1152 this.formatPrint('errorDetail', spec.error); 1153 } else if (spec.fail) { 1154 this.formatPrint('fail', spec.description + ' ; consuming ' + spec.duration + 'ms'); 1155 this.formatPrint('failDetail', spec.fail?.message); 1156 } else { 1157 this.formatPrint('pass', spec.description + ' ; consuming ' + spec.duration + 'ms'); 1158 } 1159 this.formatPrint(this.specService.currentRunningSpec.error, msg); 1160 } 1161 1162 suiteDone() { 1163 let suite = this.suiteService.currentRunningSuite; 1164 let message = suite.hookError ? `, ${suite.hookError?.message}` : ''; 1165 console.info(`[suite end] ${suite.description} consuming ${suite.duration} ms${message}`); 1166 } 1167 1168 taskDone() { 1169 let msg = ''; 1170 let summary = this.suiteService.getSummary(); 1171 msg = 'total cases:' + summary.total + ';failure ' + summary.failure + ',' + 'error ' + summary.error; 1172 msg += ',pass ' + summary.pass + '; consuming ' + summary.duration + 'ms'; 1173 console.info(`${TAG}${msg}`); 1174 console.info(`${TAG}[end] run suites end`); 1175 } 1176 1177 incorrectFormat() { 1178 if (this.coreContext.getDefaultService('config').filterValid.length !== 0) { 1179 this.coreContext.getDefaultService('config').filterValid.forEach(function (item) { 1180 console.info(`${TAG}this param ${item} is invalid`); 1181 }); 1182 } 1183 } 1184 1185 incorrectTestSuiteFormat() { 1186 if (this.coreContext.getDefaultService('config').filterXdescribe.length !== 0) { 1187 this.coreContext.getDefaultService('config').filterXdescribe.forEach(function (item) { 1188 console.info(`${TAG}xdescribe: ${item} should not contain it`); 1189 }) 1190 } 1191 } 1192 1193 formatPrint(type, msg) { 1194 switch (type) { 1195 case 'pass': 1196 console.info(`${TAG}[pass]${msg}`); 1197 break; 1198 case 'fail': 1199 console.info(`${TAG}[fail]${msg}`); 1200 break; 1201 case 'failDetail': 1202 console.info(`${TAG}[failDetail]${msg}`); 1203 break; 1204 case 'error': 1205 console.info(`${TAG}[error]${msg}`); 1206 break; 1207 case 'errorDetail': 1208 console.info(`${TAG}[errorDetail]${msg}`); 1209 break; 1210 } 1211 } 1212 1213 sleep(numberMillis) { 1214 var now = new Date(); 1215 var exitTime = now.getTime() + numberMillis; 1216 while (true) { 1217 now = new Date(); 1218 if (now.getTime() > exitTime) { 1219 return; 1220 } 1221 } 1222 } 1223} 1224 1225export { 1226 SuiteService, 1227 SpecService, 1228 ExpectService, 1229 ReportService 1230}; 1231