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