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