/* * Copyright (c) 2021-2022 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ function processFunc (coreContext, func) { let argNames = ((func || '').toString() .replace(/((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg, '') .match(/^(function)?\s*[^\(]*\(\s*([^\)]*)\)/m) || ['', '', ''])[2] .split(',') // split parameters .map(item => item.replace(/^\s*(_?)(.+?)\1\s*$/, name => name.split('=')[0].trim())) .filter(String) let funcLen = func.length let processedFunc coreContext.getDefaultService('config').setSupportAsync(true) switch (funcLen) { case 0: { processedFunc = func break } case 1: { if (argNames[0] === 'data') { processedFunc = function (paramItem) { func(paramItem) } } else { processedFunc = function () { return new Promise((resolve, reject) => { function done () { resolve() } let funcType = func(done) if (funcType instanceof Promise) { funcType.catch(err => {reject(err)}) } }) } } break } default: { processedFunc = function (paramItem) { return new Promise((resolve, reject) => { function done () { resolve() } let funcType = func(done, paramItem) if (funcType instanceof Promise) { funcType.catch(err => {reject(err)}) } }) } break } } return processedFunc } function secureRandomNumber () { return crypto.randomBytes(8).readUInt32LE() / 0xffffffff } class SuiteService { constructor(attr) { this.id = attr.id this.rootSuite = new SuiteService.Suite({}) this.currentRunningSuite = this.rootSuite } describe (desc, func) { if (this.coreContext.getDefaultService('config').filterSuite(desc)) { console.info('filter suite :' + desc) return } const suite = new SuiteService.Suite({ description: desc }) if (typeof this.coreContext.getServices('dataDriver') !== 'undefined') { let suiteStress = this.coreContext.getServices('dataDriver').dataDriver.getSuiteStress(desc) for (let i = 1; i < suiteStress; i++) { this.currentRunningSuite.childSuites.push(suite) } } const currentSuiteCache = this.currentRunningSuite this.currentRunningSuite.childSuites.push(suite) this.currentRunningSuite = suite func.call() this.currentRunningSuite = currentSuiteCache } beforeAll (func) { this.currentRunningSuite.beforeAll.push(processFunc(this.coreContext, func)) } beforeEach (func) { this.currentRunningSuite.beforeEach.push(processFunc(this.coreContext, func)) } afterAll (func) { this.currentRunningSuite.afterAll.push(processFunc(this.coreContext, func)) } afterEach (func) { this.currentRunningSuite.afterEach.push(processFunc(this.coreContext, func)) } getCurrentRunningSuite () { return this.currentRunningSuite } setCurrentRunningSuite (suite) { this.currentRunningSuite = suite } getSummary () { let total = 0 let error = 0 let failure = 0 let rootSuite = this.coreContext.getDefaultService('suite').rootSuite if (rootSuite && rootSuite.childSuites) { for (let i = 0; i < rootSuite.childSuites.length; i++) { let testsuite = rootSuite.childSuites[i] let specs = testsuite['specs'] for (let j = 0; j < specs.length; j++) { total++ let testcase = specs[j] if (testcase.error) { error++ } else if (testcase.result.failExpects.length > 0) { failure++ } } } } return { total: total, failure: failure, error: error } } init (coreContext) { this.coreContext = coreContext } execute () { if (this.coreContext.getDefaultService('config').filterValid.length !== 0) { this.coreContext.fireEvents('task', 'incorrectFormat') return } this.coreContext.fireEvents('task', 'taskStart') if (this.coreContext.getDefaultService('config').isSupportAsync()) { let asyncExecute = async () => { await this.rootSuite.asyncRun(this.coreContext) } asyncExecute().then(() => { this.coreContext.fireEvents('task', 'taskDone') }) } else { this.rootSuite.run(this.coreContext) this.coreContext.fireEvents('task', 'taskDone') } } apis () { const _this = this return { describe: function (desc, func) { return _this.describe(desc, func) }, beforeAll: function (func) { return _this.beforeAll(func) }, beforeEach: function (func) { return _this.beforeEach(func) }, afterAll: function (func) { return _this.afterAll(func) }, afterEach: function (func) { return _this.afterEach(func) } } } } SuiteService.Suite = class { constructor(attrs) { this.description = attrs.description || '' this.childSuites = [] this.specs = [] this.beforeAll = [] this.afterAll = [] this.beforeEach = [] this.afterEach = [] this.duration = 0 } pushSpec (spec) { this.specs.push(spec) } removeSpec (desc) { this.specs = this.specs.filter((item, index) => { return item.description !== desc }) } getSpecsNum () { return this.specs.length } run (coreContext) { const suiteService = coreContext.getDefaultService('suite') suiteService.setCurrentRunningSuite(this) if (this.description !== '') { coreContext.fireEvents('suite', 'suiteStart', this) } this.runHookFunc('beforeAll') if (this.specs.length > 0) { const configService = coreContext.getDefaultService('config') if (configService.isRandom()) { this.specs.sort(function () { return secureRandomNumber() > 0.5 ? -1 : 1 }) } this.specs.forEach(spec => { this.runHookFunc('beforeEach') spec.run(coreContext) this.runHookFunc('afterEach') }) } if (this.childSuites.length > 0) { this.childSuites.forEach(childSuite => { childSuite.run(coreContext) suiteService.setCurrentRunningSuite(childSuite) }) } this.runHookFunc('afterAll') if (this.description !== '') { coreContext.fireEvents('suite', 'suiteDone') } } asyncRun (coreContext) { const suiteService = coreContext.getDefaultService('suite') suiteService.setCurrentRunningSuite(this) return new Promise(async resolve => { if (this.description !== '') { coreContext.fireEvents('suite', 'suiteStart', this) } await this.runAsyncHookFunc('beforeAll') if (this.specs.length > 0) { const configService = coreContext.getDefaultService('config') if (configService.isRandom()) { this.specs.sort(function () { return secureRandomNumber() > 0.5 ? -1 : 1 }) } for (let i = 0; i < this.specs.length; i++) { await this.runAsyncHookFunc('beforeEach') await this.specs[i].asyncRun(coreContext) await this.runAsyncHookFunc('afterEach') } } if (this.childSuites.length > 0) { for (let i = 0; i < this.childSuites.length; i++) { suiteService.setCurrentRunningSuite(this.childSuites[i]) await this.childSuites[i].asyncRun(coreContext) } } await this.runAsyncHookFunc('afterAll') if (this.description !== '') { coreContext.fireEvents('suite', 'suiteDone') } resolve() }) } runHookFunc (hookName) { if (this[hookName] && this[hookName].length > 0) { this[hookName].forEach(func => { try { func() } catch (e) { console.error(e) } }) } } runAsyncHookFunc (hookName) { if (this[hookName] && this[hookName].length > 0) { return new Promise(async resolve => { for (let i = 0; i < this[hookName].length; i++) { try { await this[hookName][i]() } catch (e) { console.error(e) } } resolve() }) } } } class SpecService { constructor(attr) { this.id = attr.id } init (coreContext) { this.coreContext = coreContext } setCurrentRunningSpec (spec) { this.currentRunningSpec = spec } getCurrentRunningSpec () { return this.currentRunningSpec } it (desc, filter, func) { const configService = this.coreContext.getDefaultService('config') const currentSuiteName = this.coreContext.getDefaultService('suite').getCurrentRunningSuite().description if (configService.filterDesc(currentSuiteName, desc, filter, this.coreContext)) { console.info('filter it :' + desc) return } else { let processedFunc = processFunc(this.coreContext, func) const spec = new SpecService.Spec({ description: desc, fi: filter, fn: processedFunc }) const suiteService = this.coreContext.getDefaultService('suite') if (typeof this.coreContext.getServices('dataDriver') !== 'undefined') { let specStress = this.coreContext.getServices('dataDriver').dataDriver.getSpecStress(desc) for (let i = 1; i < specStress; i++) { suiteService.getCurrentRunningSuite().pushSpec(spec) } } suiteService.getCurrentRunningSuite().pushSpec(spec) } } apis () { const _this = this return { it: function (desc, filter, func) { return _this.it(desc, filter, func) } } } } SpecService.Spec = class { constructor(attrs) { this.description = attrs.description || '' this.fi = attrs.fi this.fn = attrs.fn || function () { } this.result = { failExpects: [], passExpects: [] } this.error = undefined this.duration = 0 } run (coreContext) { const specService = coreContext.getDefaultService('spec') specService.setCurrentRunningSpec(this) coreContext.fireEvents('spec', 'specStart', this) let startTime = new Date().getTime() try { let dataDriver = coreContext.getServices('dataDriver') if (typeof dataDriver === 'undefined') { this.fn() } else { let suiteParams = dataDriver.dataDriver.getSuiteParams() let specParams = dataDriver.dataDriver.getSpecParams() console.info('[suite params] ' + JSON.stringify(suiteParams)) console.info('[spec params] ' + JSON.stringify(specParams)) if (this.fn.length === 0) { this.fn() } else if (specParams.length === 0) { this.fn(suiteParams) } else { specParams.forEach(paramItem => this.fn(Object.assign({}, paramItem, suiteParams))) } } } catch (e) { this.error = e } let endTime = new Date().getTime() this.duration = ((endTime - startTime) / 1000).toFixed(3) coreContext.fireEvents('spec', 'specDone', this) } asyncRun (coreContext) { const specService = coreContext.getDefaultService('spec') specService.setCurrentRunningSpec(this) const config = coreContext.getDefaultService('config') const timeout = + (config.timeout == undefined ? 5000 : config.timeout) return new Promise(async resolve => { coreContext.fireEvents('spec', 'specStart', this) let startTime = new Date().getTime() function timeoutPromise (param) { return new Promise(function (resolve, reject) { setTimeout(() => reject(new Error('execute timeout ' + timeout + 'ms')), timeout) }) } try { let dataDriver = coreContext.getServices('dataDriver') if (typeof dataDriver === 'undefined') { const p = Promise.race([this.fn(), timeoutPromise()]) await p.then(console.info('async fn finish')).catch(this.result.pass = false) } else { let suiteParams = dataDriver.dataDriver.getSuiteParams() let specParams = dataDriver.dataDriver.getSpecParams() console.info('[suite params] ' + JSON.stringify(suiteParams)) console.info('[spec params] ' + JSON.stringify(specParams)) if (this.fn.length === 0) { const p = Promise.race([this.fn(), timeoutPromise()]) await p.then(console.info('async fn finish')).catch(this.result.pass = false) } else if (specParams.length === 0) { const p = Promise.race([this.fn(suiteParams), timeoutPromise()]) await p.then(console.info('async fn finish')).catch(this.result.pass = false) } else { for (const paramItem of specParams) { const p = Promise.race([this.fn(Object.assign({}, paramItem, suiteParams)), timeoutPromise()]) await p.then(console.info('async fn finish')).catch(this.result.pass = false) } } } } catch (e) { this.error = e } let endTime = new Date().getTime() this.duration = ((endTime - startTime) / 1000).toFixed(3) coreContext.fireEvents('spec', 'specDone', this) resolve() }) } filterCheck (coreContext) { const specService = coreContext.getDefaultService('spec') specService.setCurrentRunningSpec(this) return true } addExpectationResult (expectResult) { if (expectResult.pass) { this.result.passExpects.push(expectResult) } else { this.result.failExpects.push(expectResult) } } } class ExpectService { constructor(attr) { this.id = attr.id this.matchers = {} } expect (actualValue) { return this.wrapMatchers(actualValue) } init (coreContext) { this.coreContext = coreContext this.addMatchers(this.basicMatchers()) } addMatchers (matchers) { for (const matcherName in matchers) { if (Object.prototype.hasOwnProperty.call(matchers, matcherName)) { this.matchers[matcherName] = matchers[matcherName] } } } basicMatchers () { return { assertTrue: function (actualValue) { return { pass: actualValue === true } }, assertEqual: function (actualValue, args) { return { pass: actualValue === args[0], expectValue: args[0] } }, assertThrow: function (actual, args) { const result = { pass: false } if (typeof actual !== 'function') { result.message = 'toThrow\'s Actual should be a Function' } else { let hasThrow = false let throwError try { actual() } catch (e) { hasThrow = true throwError = e } if (!hasThrow) { result.message = 'function did not throw an exception' } else { if (throwError && throwError.message === args[0]) { result.pass = true } else { result.message = 'expect to throw ${args[0]} , actual throw ${throwError.message}' } } } return result } } } wrapMatchers (actualValue) { const _this = this const wrappedMatchers = {} const specService = _this.coreContext.getDefaultService('spec') const currentRunningSpec = specService.getCurrentRunningSpec() for (const matcherName in this.matchers) { if (Object.prototype.hasOwnProperty.call(this.matchers, matcherName)) { wrappedMatchers[matcherName] = function () { const result = _this.matchers[matcherName](actualValue, arguments) result.actualValue = actualValue result.checkFunc = matcherName currentRunningSpec.addExpectationResult(result) } } } return wrappedMatchers } apis () { const _this = this return { expect: function (actualValue) { return _this.expect(actualValue) } } } } class ReportService { constructor(attr) { this.id = attr.id } init (coreContext) { this.coreContext = coreContext this.specService = this.coreContext.getDefaultService('spec') this.suiteService = this.coreContext.getDefaultService('suite') this.duration = 0 } taskStart () { this.taskStartTime = new Date().getTime() this.sleep(200) console.info('[start] start run suites') } suiteStart () { this.sleep(200) console.info('[suite start]' + this.suiteService.getCurrentRunningSuite().description) } specStart () { this.sleep(200) console.info('start running case \'' + this.specService.currentRunningSpec.description + '\'') this.index = this.index + 1 } specDone () { this.sleep(200) let msg = '' let spec = this.specService.currentRunningSpec if (spec.error) { this.formatPrint('fail', spec.description) this.formatPrint('failDetail', spec.error) } else if (spec.result) { if (spec.result.failExpects.length > 0) { this.formatPrint('fail', spec.description) spec.result.failExpects.forEach(failExpect => { msg = failExpect.message || ('expect ' + failExpect.actualValue + ' ' + failExpect.checkFunc + ' ' + (failExpect.expectValue || '')) this.formatPrint('failDetail', msg) }) } else { this.formatPrint('pass', spec.description + ' ; consuming ' + spec.duration + 'S') } } this.formatPrint(this.specService.currentRunningSpec.error, msg) } suiteDone () { this.sleep(200) console.info('[suite end]') } taskDone () { let msg = '' this.sleep(200) this.taskDoneTime = new Date().getTime() this.duration = ((this.taskDoneTime - this.taskStartTime) / 1000).toFixed(2) let summary = this.suiteService.getSummary() msg = 'total cases:' + summary.total + ';failure ' + summary.failure + ',' + 'error ' + summary.error msg += ',pass ' + (summary.total - summary.error - summary.failure) + '; consuming ' + this.duration + 'S' console.info(msg) console.info('[end] run suites end') } incorrectFormat () { if (this.coreContext.getDefaultService('config').filterValid.length !== 0) { this.coreContext.getDefaultService('config').filterValid.forEach(function (item) { console.info('this param ' + item + ' is invalid') }) return } } formatPrint (type, msg) { switch (type) { case 'pass': console.info('[pass]' + msg) break case 'fail': console.info('[fail]' + msg) break case 'failDetail': console.info('[failDetail]' + msg) break } } sleep (numberMillis) { var now = new Date() var exitTime = now.getTime() + numberMillis while (true) { now = new Date() if (now.getTime() > exitTime) { return } } } } export { SuiteService, SpecService, ExpectService, ReportService }