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