• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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