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