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