1/* 2 * Copyright (c) 2025 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 abilityDelegatorRegistry from '@ohos.app.ability.abilityDelegatorRegistry'; 17import { ServiceAttrIF, TestcaseSummaryIF, ItItemIF, ConfigIf, DryrunResultIF, ApiIF } from '../../interface'; 18import { StaticSuite } from './StaticSuite'; 19import { StaticSpec } from './StaticSpec'; 20import { TAG } from '../../Constant'; 21import { Core } from '../../core'; 22import { processFunc, processAsyncFunc, processAsyncFuncWithArgOne, processFuncWithArgOne, checkIsAsyncFunction, getFunctionArgumentsCount } from '../../util'; 23import { SysTestKit } from '../kit/SysTestKit'; 24import { AssertException } from './AssertException'; 25import { ConfigService } from '../config/configService'; 26import { SpecService } from './SpecService'; 27import DataDriver from '../config/DataDriver'; 28import { HookFuncType } from '../types/common'; 29 30class SuiteService { 31 public id: string; 32 public rootSuite: StaticSuite; 33 public currentRunningSuite: StaticSuite; 34 public suitesStack: Array<StaticSuite>; 35 public suiteSkipReason: string; 36 public isSkipSuite: boolean; 37 public coreContext: Core | null; 38 public currentRunningSuiteDesc: string; 39 public fullRun: boolean; 40 public targetSuiteArray: Array<string>; 41 public targetSpecArray: Array<string>; 42 private dryrunResult: DryrunResultIF; 43 constructor(attr: ServiceAttrIF) { 44 this.id = attr.id; 45 this.rootSuite = new StaticSuite({ 46 description: '', 47 }); 48 this.currentRunningSuite = this.rootSuite; 49 this.suitesStack = new Array<StaticSuite>(this.rootSuite); 50 this.targetSuiteArray = new Array<string>(); 51 this.targetSpecArray = new Array<string>(); 52 this.currentRunningSuiteDesc = ''; 53 this.fullRun = false; 54 this.isSkipSuite = false; 55 this.suiteSkipReason = ''; 56 this.coreContext = null; 57 const suites = new Array<Map<string, Array<ConfigIf>>>(); 58 const skipSuites = new Array<Map<string, Array<ConfigIf>>>(); 59 this.dryrunResult = { 60 suites, 61 skipSuites, 62 }; 63 } 64 65 async describe(desc: string, func: () => void): Promise<undefined> { 66 const core = this.coreContext; 67 if (core) { 68 const cc = core as Core; 69 const cService = cc.getDefaultService('config'); 70 if (cService !== null) { 71 const configService = cService as ConfigService; 72 if ( 73 this.suitesStack.some((suite: StaticSuite) => { 74 return suite.description === desc; 75 }) 76 ) { 77 console.error(`${TAG} Loop nesting occurs : ${desc}`); 78 this.suiteSkipReason = ''; 79 this.isSkipSuite = false; 80 return Promise.resolve(undefined); 81 } 82 const isFilter = this.analyzeConfigServiceClass(configService.className, desc); 83 if (configService.filterSuite(desc) && isFilter) { 84 if (this.currentRunningSuite.description === '' || this.currentRunningSuite.description == null) { 85 console.info(`${TAG}filter suite : ${desc}`); 86 this.suiteSkipReason = ''; 87 this.isSkipSuite = false; 88 return Promise.resolve(undefined); 89 } 90 } 91 const suite = new StaticSuite({ description: desc }); 92 if (this.isSkipSuite) { 93 suite.isSkip = true; 94 suite.skipReason = this.suiteSkipReason; 95 } 96 this.suiteSkipReason = ''; 97 this.isSkipSuite = false; 98 99 const dataDriverMap = core.getServices('dataDriver'); 100 if (dataDriverMap && configService.dryRun !== 'true') { 101 const dataDriver = dataDriverMap.get('dataDriver') as DataDriver; 102 const suiteStress = dataDriver.getSuiteStress(desc); 103 for (let i = 1; i < suiteStress; i++) { 104 this.currentRunningSuite.childSuites.push(suite); 105 } 106 } 107 this.currentRunningSuite.childSuites.push(suite); 108 this.currentRunningSuite = suite; 109 this.suitesStack.push(suite); 110 try { 111 func() 112 } catch (err: Error) { 113 console.info(`${TAG} describe ${desc} 执行报错: ${err.message}`); 114 console.info(`${err.stack}`); 115 } 116 this.suitesStack.pop(); 117 118 const staticService = this.suitesStack.pop(); 119 if (staticService) { 120 this.currentRunningSuite = staticService; 121 this.suitesStack.push(staticService); 122 } 123 } 124 } 125 } 126 async xdescribe(desc: string, func: () => void, reason: string): Promise<void> { 127 const core = this.coreContext; 128 if (core) { 129 const cc = core as Core; 130 const cService = cc.getDefaultService('config'); 131 if (cService !== null) { 132 const configService = cService as ConfigService; 133 if (!configService.skipMessage && configService.runSkipped !== 'all') { 134 if (configService.runSkipped != null && configService.runSkipped !== '') { 135 let finalDesc = ''; 136 this.suitesStack.map((suite: StaticSuite) => { 137 finalDesc = finalDesc + '.' + suite.description; 138 }); 139 finalDesc = (finalDesc + '.' + desc).substring(2); 140 console.info(`${TAG} finalDesc ${finalDesc}`); 141 if (configService.checkIfSuiteInSkipRun(finalDesc)) { 142 console.info(`${TAG} runSkipped suite: ${desc}`); 143 } else { 144 console.info( 145 reason == null 146 ? `${TAG} skip suite: ${desc}` 147 : `${TAG} skip suite: ${desc}, and the reason is ${reason}` 148 ); 149 return Promise.resolve(); 150 } 151 } else { 152 console.info( 153 reason == null ? `${TAG} skip suite: ${desc}` : `${TAG} skip suite: ${desc}, and the reason is ${reason}` 154 ); 155 return Promise.resolve(); 156 } 157 } 158 this.isSkipSuite = true; 159 this.suiteSkipReason = reason; 160 await this.describe(desc, func); 161 } 162 } 163 return Promise.resolve(); 164 } 165 166 handleHookFunction(core: Core, func: HookFuncType) { 167 const funcStr = Type.of(func as object).getLiteral(); 168 const isAsyncFunc = checkIsAsyncFunction(funcStr); 169 const argsCount = getFunctionArgumentsCount(funcStr); 170 let fn = func 171 if (argsCount === 1) { 172 if (isAsyncFunc) { 173 fn = processAsyncFuncWithArgOne(core, func as ((done: () => void) => Promise<void>)); 174 } else { 175 fn = processFuncWithArgOne(core, func as ((done: () => void) => void)); 176 } 177 } else { 178 if (isAsyncFunc) { 179 fn = processAsyncFunc(core, func as (() => Promise<void>)); 180 } else { 181 fn = processFunc(core, func as (() => void)); 182 } 183 } 184 return fn 185 } 186 187 beforeAll(func: HookFuncType) { 188 const core = this.coreContext; 189 if (core !== null) { 190 const fn = this.handleHookFunction(core as Core, func); 191 this.currentRunningSuite.beforeAll.add(fn); 192 } 193 } 194 195 beforeEach(func: HookFuncType) { 196 const core = this.coreContext; 197 if (core !== null) { 198 const fn = this.handleHookFunction(core as Core, func); 199 this.currentRunningSuite.beforeEach.add(fn); 200 } 201 } 202 203 beforeItSpecified(itDescs: string | string[], func: HookFuncType) { 204 const core = this.coreContext; 205 if (core !== null) { 206 const fn = this.handleHookFunction(core as Core, func); 207 this.currentRunningSuite.beforeItSpecified.set(itDescs, fn); 208 } 209 } 210 211 afterItSpecified(itDescs: string | string[], func: HookFuncType) { 212 const core = this.coreContext; 213 if (core !== null) { 214 const fn = this.handleHookFunction(core as Core, func); 215 this.currentRunningSuite.afterItSpecified.set(itDescs, fn); 216 } 217 } 218 219 afterAll(func: HookFuncType) { 220 const core = this.coreContext; 221 if (core !== null) { 222 const fn = this.handleHookFunction(core as Core, func); 223 this.currentRunningSuite.afterAll.add(fn); 224 } 225 } 226 227 afterEach(func: HookFuncType) { 228 const core = this.coreContext; 229 if (core) { 230 const fn = this.handleHookFunction(core as Core, func); 231 this.currentRunningSuite.afterEach.add(fn); 232 } 233 } 234 235 getCurrentRunningSuite() { 236 return this.currentRunningSuite; 237 } 238 239 setCurrentRunningSuite(suite: StaticSuite) { 240 this.currentRunningSuite = suite; 241 } 242 243 getRootSuite() { 244 return this.rootSuite; 245 } 246 247 getCurrentRunningSuiteDesc() { 248 return this.currentRunningSuiteDesc; 249 } 250 setCurrentRunningSuiteDesc(suite: StaticSuite, currentSuite: StaticSuite, prefix: string) { 251 if (suite != null && suite === currentSuite) { 252 this.currentRunningSuiteDesc = prefix; 253 } else if (suite != null && suite !== currentSuite) { 254 suite.childSuites.forEach((it: StaticSuite) => { 255 let temp = prefix; 256 if (it.description != null || it.description !== '') { 257 temp = prefix === '' ? it.description : prefix + '.' + it.description; 258 } 259 this.setCurrentRunningSuiteDesc(it, currentSuite, temp); 260 }); 261 } 262 } 263 analyzeConfigServiceClass(configServiceClass: string, desc: string) { 264 if (configServiceClass == null || configServiceClass === '') { 265 this.fullRun = true; 266 return false; 267 } 268 const targetList = configServiceClass.split(','); 269 const targetArray = new Array<string>(); 270 for (let target of targetList) { 271 targetArray.push(target); 272 } 273 const mapList = targetArray.map((item: string) => item.trim()); 274 const mapArray = new Array<string>(); 275 for (let mapValue of mapList) { 276 mapArray.push(mapValue); 277 } 278 const filterList = mapArray.filter((item: string) => item !== ''); 279 if (this.targetSuiteArray.length === 0) { 280 for (let target of filterList) { 281 if (target.includes('#')) { 282 this.targetSpecArray.push(target); 283 } else { 284 this.targetSuiteArray.push(target); 285 } 286 } 287 } 288 return targetArray.indexOf(desc) === -1; 289 } 290 traversalResults(suite: StaticSuite, obj: TestcaseSummaryIF, breakOnError: boolean) { 291 if (suite.childSuites.length === 0 && suite.specs.length === 0) { 292 return; 293 } 294 if (suite.specs.length > 0 && obj) { 295 for (const itItem of suite.specs) { 296 obj.total++; 297 const itInfo: ItItemIF = { 298 currentThreadName: 'mainThread', 299 description: suite.description + '#' + itItem.description, 300 result: -3, 301 }; 302 obj.itItemList.push(itInfo); 303 if (breakOnError && (obj.error > 0 || obj.failure > 0)) { 304 // breakOnError模式 305 continue; 306 } 307 if (itItem.error) { 308 obj.error++; 309 itInfo.result = -1; 310 } else if (itItem.fail) { 311 obj.failure++; 312 itInfo.result = -2; 313 } else if (itItem.pass === true) { 314 obj.pass++; 315 itInfo.result = 0; 316 } 317 } 318 } 319 320 obj.duration += suite.duration; 321 322 if (suite.childSuites.length > 0) { 323 for (const suiteItem of suite.childSuites) { 324 this.traversalResults(suiteItem, obj, breakOnError); 325 } 326 } 327 return; 328 } 329 330 async setSuiteResults(suite: StaticSuite, error: Error, coreContext: Core) { 331 if (suite.specs.length > 0) { 332 const specService = coreContext.getDefaultService('spec'); 333 if (specService !== null) { 334 const ss = specService as SpecService; 335 for (const specItem of suite.specs) { 336 ss.setCurrentRunningSpec(specItem); 337 if ((error as Error) instanceof AssertException) { 338 specItem.fail = error; 339 } else { 340 specItem.error = error; 341 } 342 await coreContext.fireEvents('spec', 'specStart'); 343 await coreContext.fireEvents('spec', 'specDone'); 344 } 345 } 346 } 347 if (suite.childSuites.length > 0) { 348 for (const suiteItem of suite.childSuites) { 349 await this.setSuiteResults(suiteItem, error, coreContext); 350 } 351 } 352 } 353 354 getSummary(): TestcaseSummaryIF { 355 const core = this.coreContext; 356 const obj: TestcaseSummaryIF = { 357 total: 0, 358 failure: 0, 359 error: 0, 360 pass: 0, 361 ignore: 0, 362 duration: 0, 363 itItemList: new Array<ItItemIF>(), 364 }; 365 if (core) { 366 const suiteService = core.getDefaultService('suite'); 367 const specService = core.getDefaultService('spec'); 368 const configService = core.getDefaultService('config'); 369 if (suiteService !== null && specService !== null && configService !== null) { 370 const suiteS = suiteService as SuiteService; 371 const specS = specService as SpecService; 372 const configS = configService as ConfigService; 373 const rootSuite = suiteS.rootSuite; 374 const breakOnError = configS.isBreakOnError(); 375 const isError = specS.getStatus(); 376 const isBreaKOnError = breakOnError && isError; 377 378 const childrenSuite = rootSuite.childSuites as Array<StaticSuite>; 379 for (const suiteItem of childrenSuite) { 380 this.traversalResults(suiteItem, obj, isBreaKOnError); 381 } 382 obj.ignore = obj.total - obj.pass - obj.failure - obj.error; 383 } 384 } 385 return obj; 386 } 387 388 init(coreContext: Core) { 389 this.coreContext = coreContext; 390 } 391 392 getDryResultStr(dryrunResult: DryrunResultIF) { 393 let message = '{'; 394 for (const suiteEntries of Object.entries(dryrunResult)) { 395 if (!suiteEntries) { 396 continue; 397 } 398 const suiteKey = suiteEntries[0]; 399 const suiteValue = suiteEntries[1]; 400 if (!suiteValue || !Array.isArray(suiteValue)) { 401 continue; 402 } 403 const suiteValueArray = suiteValue as Array<Map<string, Array<ConfigIf>>>; 404 message += `"${suiteKey}":[`; 405 for (const itMap of suiteValueArray) { 406 message += '{'; 407 for (const itMapArray of itMap.entries()) { 408 if (!itMapArray) { 409 continue; 410 } 411 const itKey = itMapArray[0]; 412 const itValue = itMapArray[1]; 413 if (!itValue || !Array.isArray(itValue)) { 414 continue; 415 } 416 const itValueArray = itValue as Array<ConfigIf>; 417 message += `"${itKey}":[`; 418 for (const configInfo of itValueArray) { 419 if (!configInfo) { 420 continue; 421 } 422 message += JSON.stringify(configInfo) + ','; 423 } 424 message = message.slice(0, -1); 425 message += '],'; 426 } 427 message = message.slice(0, -1); 428 message += '},'; 429 } 430 message = message.slice(0, -1); 431 message += '],'; 432 } 433 message = message.slice(0, -1); 434 message += '}'; 435 return message; 436 } 437 438 async dryRun(abilityDelegator: abilityDelegatorRegistry.AbilityDelegator) { 439 const suiteArray = new Array<Map<string, Array<ConfigIf>>>(); 440 const skipSuiteArray = new Array<Map<string, Array<ConfigIf>>>(); 441 this.analyzeSuitesArray(new Array<string>(), suiteArray, skipSuiteArray, this.rootSuite); 442 const core = this.coreContext; 443 if (core) { 444 const configService = core.getDefaultService('config'); 445 if (configService !== null) { 446 const configS = configService as ConfigService; 447 if (configS.skipMessage) { 448 this.dryrunResult = { 449 suites: suiteArray, 450 skipSuites: skipSuiteArray, 451 }; 452 } else { 453 this.dryrunResult = { 454 suites: suiteArray, 455 }; 456 } 457 const message = this.getDryResultStr(this.dryrunResult); 458 const strLen = message.length; 459 const maxLen = 500; 460 const maxCount = Math.floor(strLen / maxLen); 461 for (let count = 0; count <= maxCount; count++) { 462 await SysTestKit.print(message.substring(count * maxLen, (count + 1) * maxLen)); 463 } 464 console.info(`${TAG}dryRun print success`); 465 abilityDelegator.finishTest('dry run finished!!!', 0); 466 } 467 } 468 } 469 analyzeSuitesArray( 470 prefixStack: Array<string>, 471 suiteArray: Array<Map<string, Array<ConfigIf>>>, 472 skipSuiteArray: Array<Map<string, Array<ConfigIf>>>, 473 rootSuite: StaticSuite 474 ) { 475 rootSuite.childSuites.map((suite: StaticSuite) => { 476 if (suite.description != null && suite.description !== '') { 477 let prefix = ''; 478 if (prefixStack.length > 0) { 479 prefix = prefixStack.join('.') + '.' + suite.description; 480 } else { 481 prefix = suite.description; 482 } 483 prefixStack.push(suite.description); 484 const temp = new Map<string, Array<ConfigIf>>(); 485 temp.set(prefix, new Array<ConfigIf>()); 486 const skipTemp = new Map<string, Array<ConfigIf>>(); 487 skipTemp.set(prefix, new Array<ConfigIf>()); 488 suite.specs.map((spec: StaticSpec) => { 489 const it: ConfigIf = { itName: spec.description }; 490 const skipArr = skipTemp.get(prefix); 491 const tempArr = temp.get(prefix); 492 if (spec.isSkip) { 493 if (skipArr) { 494 skipArr.push(it); 495 } 496 } else if (tempArr) { 497 tempArr.push(it); 498 } 499 }); 500 suiteArray.push(temp); 501 skipSuiteArray.push(skipTemp); 502 } 503 this.analyzeSuitesArray(prefixStack, suiteArray, skipSuiteArray, suite); 504 prefixStack.pop(); 505 }); 506 } 507 508 getAllChildSuiteNum(suite: StaticSuite, specArray: Array<StaticSpec>) { 509 if (suite.specs != null) { 510 suite.specs.forEach((spec: StaticSpec) => { 511 specArray.push(spec); 512 }); 513 } 514 if (suite.childSuites != null) { 515 suite.childSuites.forEach((it: StaticSuite) => { 516 this.getAllChildSuiteNum(it, specArray); 517 }); 518 } 519 } 520 521 async executeCases(core: Core) { 522 await core.fireEvents('task', 'taskStart'); 523 await this.rootSuite.asyncRun(core); 524 await core.fireEvents('task', 'taskDone'); 525 } 526 527 execute() { 528 const cc = this.coreContext; 529 if (cc) { 530 const core = cc as Core; 531 const configS = core.getDefaultService('config'); 532 if (configS !== null) { 533 const configService = configS as ConfigService; 534 if (configService.filterValid.length !== 0) { 535 core.fireEvents('task', 'incorrectFormat'); 536 return; 537 } 538 if (configService.filterXdescribe.length !== 0) { 539 core.fireEvents('task', 'incorrectTestSuiteFormat'); 540 return; 541 } 542 if (configService.isRandom() && this.rootSuite.childSuites.length > 0) { 543 this.rootSuite.childSuites.sort((a: StaticSuite, b: StaticSuite) => { 544 return Math.random() > 0.5 ? -1 : 1; 545 }); 546 this.currentRunningSuite = this.rootSuite.childSuites[0]; 547 } 548 if (configService.isSupportAsync()) { 549 let asyncExecute = async () => { 550 await core.fireEvents('task', 'taskStart'); 551 await this.rootSuite.asyncRun(core); 552 }; 553 asyncExecute().then(async () => { 554 await core.fireEvents('task', 'taskDone'); 555 }); 556 } else { 557 core.fireEvents('task', 'taskStart'); 558 this.rootSuite.run(core); 559 core.fireEvents('task', 'taskDone'); 560 } 561 } 562 } 563 } 564 565 apis(): ApiIF { 566 const _this = this; 567 return { 568 name: 'SuiteService', 569 describe: (desc: string, func: () => void) => { 570 const c = _this.describe(desc, func); 571 return c; 572 }, 573 xdescribe: (desc: string, func: () => void, reason: string) => { 574 return _this.xdescribe(desc, func, reason); 575 }, 576 beforeItSpecified: (itDescs: string | string[], func: HookFuncType) => { 577 return _this.beforeItSpecified(itDescs, func); 578 }, 579 afterItSpecified: (itDescs: string | string[], func: HookFuncType) => { 580 return _this.afterItSpecified(itDescs, func); 581 }, 582 beforeAll: (func: HookFuncType) => { 583 return _this.beforeAll(func); 584 }, 585 beforeEach: (func: HookFuncType) => { 586 return _this.beforeEach(func); 587 }, 588 afterAll: (func: HookFuncType) => { 589 return _this.afterAll(func); 590 }, 591 afterEach: (func: HookFuncType) => { 592 return _this.afterEach(func); 593 }, 594 }; 595 } 596} 597 598export { SuiteService }; 599