• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2021-2022 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 */
15function processFunc (coreContext, func) {
16  let argNames = ((func || '').toString()
17    .replace(/((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg, '')
18    .match(/^(function)?\s*[^\(]*\(\s*([^\)]*)\)/m) || ['', '', ''])[2]
19    .split(',') // split parameters
20    .map(item => item.replace(/^\s*(_?)(.+?)\1\s*$/, name => name.split('=')[0].trim()))
21    .filter(String)
22  let funcLen = func.length
23  let processedFunc
24  coreContext.getDefaultService('config').setSupportAsync(true)
25  switch (funcLen) {
26    case 0: {
27      processedFunc = func
28      break
29    }
30    case 1: {
31      if (argNames[0] === 'data') {
32        processedFunc = function (paramItem) {
33          func(paramItem)
34        }
35      } else {
36        processedFunc = function () {
37          return new Promise((resolve, reject) => {
38            function done () {
39              resolve()
40            }
41
42            let funcType = func(done)
43            if (funcType instanceof Promise) {
44              funcType.catch(err => {reject(err)})
45            }
46          })
47        }
48      }
49      break
50    }
51    default: {
52      processedFunc = function (paramItem) {
53        return new Promise((resolve, reject) => {
54          function done () {
55            resolve()
56          }
57
58          let funcType = func(done, paramItem)
59          if (funcType instanceof Promise) {
60            funcType.catch(err => {reject(err)})
61          }
62        })
63      }
64      break
65    }
66  }
67  return processedFunc
68}
69
70function secureRandomNumber () {
71  return crypto.randomBytes(8).readUInt32LE() / 0xffffffff
72}
73
74class SuiteService {
75  constructor(attr) {
76    this.id = attr.id
77    this.rootSuite = new SuiteService.Suite({})
78    this.currentRunningSuite = this.rootSuite
79  }
80
81  describe (desc, func) {
82    if (this.coreContext.getDefaultService('config').filterSuite(desc)) {
83      console.info('filter suite :' + desc)
84      return
85    }
86    const suite = new SuiteService.Suite({ description: desc })
87    if (typeof this.coreContext.getServices('dataDriver') !== 'undefined') {
88      let suiteStress = this.coreContext.getServices('dataDriver').dataDriver.getSuiteStress(desc)
89      for (let i = 1; i < suiteStress; i++) {
90        this.currentRunningSuite.childSuites.push(suite)
91      }
92    }
93    const currentSuiteCache = this.currentRunningSuite
94    this.currentRunningSuite.childSuites.push(suite)
95    this.currentRunningSuite = suite
96    func.call()
97    this.currentRunningSuite = currentSuiteCache
98  }
99
100  beforeAll (func) {
101    this.currentRunningSuite.beforeAll.push(processFunc(this.coreContext, func))
102  }
103
104  beforeEach (func) {
105    this.currentRunningSuite.beforeEach.push(processFunc(this.coreContext, func))
106  }
107
108  afterAll (func) {
109    this.currentRunningSuite.afterAll.push(processFunc(this.coreContext, func))
110  }
111
112  afterEach (func) {
113    this.currentRunningSuite.afterEach.push(processFunc(this.coreContext, func))
114  }
115
116  getCurrentRunningSuite () {
117    return this.currentRunningSuite
118  }
119
120  setCurrentRunningSuite (suite) {
121    this.currentRunningSuite = suite
122  }
123
124  getSummary () {
125    let total = 0
126    let error = 0
127    let failure = 0
128    let rootSuite = this.coreContext.getDefaultService('suite').rootSuite
129    if (rootSuite && rootSuite.childSuites) {
130      for (let i = 0; i < rootSuite.childSuites.length; i++) {
131        let testsuite = rootSuite.childSuites[i]
132        let specs = testsuite['specs']
133        for (let j = 0; j < specs.length; j++) {
134          total++
135          let testcase = specs[j]
136          if (testcase.error) {
137            error++
138          } else if (testcase.result.failExpects.length > 0) {
139            failure++
140          }
141        }
142      }
143    }
144    return { total: total, failure: failure, error: error }
145  }
146
147  init (coreContext) {
148    this.coreContext = coreContext
149  }
150
151  execute () {
152    if (this.coreContext.getDefaultService('config').filterValid.length !== 0) {
153      this.coreContext.fireEvents('task', 'incorrectFormat')
154      return
155    }
156    this.coreContext.fireEvents('task', 'taskStart')
157    if (this.coreContext.getDefaultService('config').isSupportAsync()) {
158      let asyncExecute = async () => {
159        await this.rootSuite.asyncRun(this.coreContext)
160      }
161      asyncExecute().then(() => {
162        this.coreContext.fireEvents('task', 'taskDone')
163      })
164    } else {
165      this.rootSuite.run(this.coreContext)
166      this.coreContext.fireEvents('task', 'taskDone')
167    }
168  }
169
170  apis () {
171    const _this = this
172    return {
173      describe: function (desc, func) {
174        return _this.describe(desc, func)
175      },
176      beforeAll: function (func) {
177        return _this.beforeAll(func)
178      },
179      beforeEach: function (func) {
180        return _this.beforeEach(func)
181      },
182      afterAll: function (func) {
183        return _this.afterAll(func)
184      },
185      afterEach: function (func) {
186        return _this.afterEach(func)
187      }
188    }
189  }
190}
191
192SuiteService.Suite = class {
193  constructor(attrs) {
194    this.description = attrs.description || ''
195    this.childSuites = []
196    this.specs = []
197    this.beforeAll = []
198    this.afterAll = []
199    this.beforeEach = []
200    this.afterEach = []
201    this.duration = 0
202  }
203
204  pushSpec (spec) {
205    this.specs.push(spec)
206  }
207
208  removeSpec (desc) {
209    this.specs = this.specs.filter((item, index) => {
210      return item.description !== desc
211    })
212  }
213
214  getSpecsNum () {
215    return this.specs.length
216  }
217
218  run (coreContext) {
219    const suiteService = coreContext.getDefaultService('suite')
220    suiteService.setCurrentRunningSuite(this)
221    if (this.description !== '') {
222      coreContext.fireEvents('suite', 'suiteStart', this)
223    }
224    this.runHookFunc('beforeAll')
225    if (this.specs.length > 0) {
226      const configService = coreContext.getDefaultService('config')
227      if (configService.isRandom()) {
228        this.specs.sort(function () {
229          return secureRandomNumber() > 0.5 ? -1 : 1
230        })
231      }
232      this.specs.forEach(spec => {
233        this.runHookFunc('beforeEach')
234        spec.run(coreContext)
235        this.runHookFunc('afterEach')
236      })
237    }
238    if (this.childSuites.length > 0) {
239      this.childSuites.forEach(childSuite => {
240        childSuite.run(coreContext)
241        suiteService.setCurrentRunningSuite(childSuite)
242      })
243    }
244    this.runHookFunc('afterAll')
245    if (this.description !== '') {
246      coreContext.fireEvents('suite', 'suiteDone')
247    }
248  }
249
250  asyncRun (coreContext) {
251    const suiteService = coreContext.getDefaultService('suite')
252    suiteService.setCurrentRunningSuite(this)
253    return new Promise(async resolve => {
254      if (this.description !== '') {
255        coreContext.fireEvents('suite', 'suiteStart', this)
256      }
257      await this.runAsyncHookFunc('beforeAll')
258      if (this.specs.length > 0) {
259        const configService = coreContext.getDefaultService('config')
260        if (configService.isRandom()) {
261          this.specs.sort(function () {
262            return secureRandomNumber() > 0.5 ? -1 : 1
263          })
264        }
265        for (let i = 0; i < this.specs.length; i++) {
266          await this.runAsyncHookFunc('beforeEach')
267          await this.specs[i].asyncRun(coreContext)
268          await this.runAsyncHookFunc('afterEach')
269        }
270      }
271
272      if (this.childSuites.length > 0) {
273        for (let i = 0; i < this.childSuites.length; i++) {
274          suiteService.setCurrentRunningSuite(this.childSuites[i])
275          await this.childSuites[i].asyncRun(coreContext)
276        }
277      }
278
279      await this.runAsyncHookFunc('afterAll')
280      if (this.description !== '') {
281        coreContext.fireEvents('suite', 'suiteDone')
282      }
283      resolve()
284    })
285  }
286
287  runHookFunc (hookName) {
288    if (this[hookName] && this[hookName].length > 0) {
289      this[hookName].forEach(func => {
290        try {
291          func()
292        } catch (e) {
293          console.error(e)
294        }
295      })
296    }
297  }
298
299  runAsyncHookFunc (hookName) {
300    if (this[hookName] && this[hookName].length > 0) {
301      return new Promise(async resolve => {
302        for (let i = 0; i < this[hookName].length; i++) {
303          try {
304            await this[hookName][i]()
305          } catch (e) {
306            console.error(e)
307          }
308        }
309        resolve()
310      })
311    }
312  }
313}
314
315class SpecService {
316  constructor(attr) {
317    this.id = attr.id
318  }
319
320  init (coreContext) {
321    this.coreContext = coreContext
322  }
323
324  setCurrentRunningSpec (spec) {
325    this.currentRunningSpec = spec
326  }
327
328  getCurrentRunningSpec () {
329    return this.currentRunningSpec
330  }
331
332  it (desc, filter, func) {
333    const configService = this.coreContext.getDefaultService('config')
334    const currentSuiteName = this.coreContext.getDefaultService('suite').getCurrentRunningSuite().description
335    if (configService.filterDesc(currentSuiteName, desc, filter, this.coreContext)) {
336      console.info('filter it :' + desc)
337      return
338    } else {
339      let processedFunc = processFunc(this.coreContext, func)
340      const spec = new SpecService.Spec({ description: desc, fi: filter, fn: processedFunc })
341      const suiteService = this.coreContext.getDefaultService('suite')
342      if (typeof this.coreContext.getServices('dataDriver') !== 'undefined') {
343        let specStress = this.coreContext.getServices('dataDriver').dataDriver.getSpecStress(desc)
344        for (let i = 1; i < specStress; i++) {
345          suiteService.getCurrentRunningSuite().pushSpec(spec)
346        }
347      }
348      suiteService.getCurrentRunningSuite().pushSpec(spec)
349    }
350  }
351
352  apis () {
353    const _this = this
354    return {
355      it: function (desc, filter, func) {
356        return _this.it(desc, filter, func)
357      }
358    }
359  }
360}
361
362SpecService.Spec = class {
363  constructor(attrs) {
364    this.description = attrs.description || ''
365    this.fi = attrs.fi
366    this.fn = attrs.fn || function () {
367    }
368    this.result = {
369      failExpects: [],
370      passExpects: []
371    }
372    this.error = undefined
373    this.duration = 0
374  }
375
376  run (coreContext) {
377    const specService = coreContext.getDefaultService('spec')
378    specService.setCurrentRunningSpec(this)
379    coreContext.fireEvents('spec', 'specStart', this)
380    let startTime = new Date().getTime()
381    try {
382      let dataDriver = coreContext.getServices('dataDriver')
383      if (typeof dataDriver === 'undefined') {
384        this.fn()
385      } else {
386        let suiteParams = dataDriver.dataDriver.getSuiteParams()
387        let specParams = dataDriver.dataDriver.getSpecParams()
388        console.info('[suite params] ' + JSON.stringify(suiteParams))
389        console.info('[spec params] ' + JSON.stringify(specParams))
390        if (this.fn.length === 0) {
391          this.fn()
392        } else if (specParams.length === 0) {
393          this.fn(suiteParams)
394        } else {
395          specParams.forEach(paramItem => this.fn(Object.assign({}, paramItem, suiteParams)))
396        }
397      }
398    } catch (e) {
399      this.error = e
400    }
401    let endTime = new Date().getTime()
402    this.duration = ((endTime - startTime) / 1000).toFixed(3)
403    coreContext.fireEvents('spec', 'specDone', this)
404  }
405
406  asyncRun (coreContext) {
407    const specService = coreContext.getDefaultService('spec')
408    specService.setCurrentRunningSpec(this)
409    const config = coreContext.getDefaultService('config')
410    const timeout = + (config.timeout == undefined ? 5000 : config.timeout)
411    return new Promise(async resolve => {
412      coreContext.fireEvents('spec', 'specStart', this)
413      let startTime = new Date().getTime()
414
415      function timeoutPromise (param) {
416        return new Promise(function (resolve, reject) {
417          setTimeout(() => reject(new Error('execute timeout ' + timeout + 'ms')), timeout)
418        })
419      }
420
421      try {
422        let dataDriver = coreContext.getServices('dataDriver')
423        if (typeof dataDriver === 'undefined') {
424          const p = Promise.race([this.fn(), timeoutPromise()])
425          await p.then(console.info('async fn finish')).catch(this.result.pass = false)
426        } else {
427          let suiteParams = dataDriver.dataDriver.getSuiteParams()
428          let specParams = dataDriver.dataDriver.getSpecParams()
429          console.info('[suite params] ' + JSON.stringify(suiteParams))
430          console.info('[spec params] ' + JSON.stringify(specParams))
431          if (this.fn.length === 0) {
432            const p = Promise.race([this.fn(), timeoutPromise()])
433            await p.then(console.info('async fn finish')).catch(this.result.pass = false)
434          } else if (specParams.length === 0) {
435            const p = Promise.race([this.fn(suiteParams), timeoutPromise()])
436            await p.then(console.info('async fn finish')).catch(this.result.pass = false)
437          } else {
438            for (const paramItem of specParams) {
439              const p = Promise.race([this.fn(Object.assign({}, paramItem, suiteParams)), timeoutPromise()])
440              await p.then(console.info('async fn finish')).catch(this.result.pass = false)
441            }
442          }
443        }
444      } catch (e) {
445        this.error = e
446      }
447      let endTime = new Date().getTime()
448      this.duration = ((endTime - startTime) / 1000).toFixed(3)
449      coreContext.fireEvents('spec', 'specDone', this)
450      resolve()
451    })
452  }
453
454  filterCheck (coreContext) {
455    const specService = coreContext.getDefaultService('spec')
456    specService.setCurrentRunningSpec(this)
457    return true
458  }
459
460  addExpectationResult (expectResult) {
461    if (expectResult.pass) {
462      this.result.passExpects.push(expectResult)
463    } else {
464      this.result.failExpects.push(expectResult)
465    }
466  }
467}
468
469class ExpectService {
470  constructor(attr) {
471    this.id = attr.id
472    this.matchers = {}
473  }
474
475  expect (actualValue) {
476    return this.wrapMatchers(actualValue)
477  }
478
479  init (coreContext) {
480    this.coreContext = coreContext
481    this.addMatchers(this.basicMatchers())
482  }
483
484  addMatchers (matchers) {
485    for (const matcherName in matchers) {
486      if (Object.prototype.hasOwnProperty.call(matchers, matcherName)) {
487        this.matchers[matcherName] = matchers[matcherName]
488      }
489    }
490  }
491
492  basicMatchers () {
493    return {
494      assertTrue: function (actualValue) {
495        return {
496          pass: actualValue === true
497        }
498      },
499      assertEqual: function (actualValue, args) {
500        return {
501          pass: actualValue === args[0],
502          expectValue: args[0]
503        }
504      },
505      assertThrow: function (actual, args) {
506        const result = {
507          pass: false
508        }
509        if (typeof actual !== 'function') {
510          result.message = 'toThrow\'s Actual should be a Function'
511        } else {
512          let hasThrow = false
513          let throwError
514          try {
515            actual()
516          } catch (e) {
517            hasThrow = true
518            throwError = e
519          }
520          if (!hasThrow) {
521            result.message = 'function did not throw an exception'
522          } else {
523            if (throwError && throwError.message === args[0]) {
524              result.pass = true
525            } else {
526              result.message = 'expect to throw ${args[0]} , actual throw ${throwError.message}'
527            }
528          }
529        }
530        return result
531      }
532    }
533  }
534
535  wrapMatchers (actualValue) {
536    const _this = this
537    const wrappedMatchers = {}
538    const specService = _this.coreContext.getDefaultService('spec')
539    const currentRunningSpec = specService.getCurrentRunningSpec()
540    for (const matcherName in this.matchers) {
541      if (Object.prototype.hasOwnProperty.call(this.matchers, matcherName)) {
542        wrappedMatchers[matcherName] = function () {
543          const result = _this.matchers[matcherName](actualValue, arguments)
544          result.actualValue = actualValue
545          result.checkFunc = matcherName
546          currentRunningSpec.addExpectationResult(result)
547        }
548      }
549    }
550    return wrappedMatchers
551  }
552
553  apis () {
554    const _this = this
555    return {
556      expect: function (actualValue) {
557        return _this.expect(actualValue)
558      }
559    }
560  }
561}
562
563class ReportService {
564  constructor(attr) {
565    this.id = attr.id
566  }
567
568  init (coreContext) {
569    this.coreContext = coreContext
570    this.specService = this.coreContext.getDefaultService('spec')
571    this.suiteService = this.coreContext.getDefaultService('suite')
572    this.duration = 0
573  }
574
575  taskStart () {
576    this.taskStartTime = new Date().getTime()
577    this.sleep(200)
578    console.info('[start] start run suites')
579  }
580
581  suiteStart () {
582    this.sleep(200)
583    console.info('[suite start]' + this.suiteService.getCurrentRunningSuite().description)
584  }
585
586  specStart () {
587    this.sleep(200)
588    console.info('start running case \'' + this.specService.currentRunningSpec.description + '\'')
589    this.index = this.index + 1
590  }
591
592  specDone () {
593    this.sleep(200)
594    let msg = ''
595    let spec = this.specService.currentRunningSpec
596    if (spec.error) {
597      this.formatPrint('fail', spec.description)
598      this.formatPrint('failDetail', spec.error)
599    } else if (spec.result) {
600      if (spec.result.failExpects.length > 0) {
601        this.formatPrint('fail', spec.description)
602        spec.result.failExpects.forEach(failExpect => {
603          msg = failExpect.message || ('expect ' + failExpect.actualValue + ' ' + failExpect.checkFunc + ' ' + (failExpect.expectValue || ''))
604          this.formatPrint('failDetail', msg)
605        })
606      } else {
607        this.formatPrint('pass', spec.description + ' ; consuming ' + spec.duration + 'S')
608      }
609    }
610    this.formatPrint(this.specService.currentRunningSpec.error, msg)
611  }
612
613  suiteDone () {
614    this.sleep(200)
615    console.info('[suite end]')
616  }
617
618  taskDone () {
619    let msg = ''
620    this.sleep(200)
621    this.taskDoneTime = new Date().getTime()
622    this.duration = ((this.taskDoneTime - this.taskStartTime) / 1000).toFixed(2)
623    let summary = this.suiteService.getSummary()
624    msg = 'total cases:' + summary.total + ';failure ' + summary.failure + ',' + 'error ' + summary.error
625    msg += ',pass ' + (summary.total - summary.error - summary.failure) + '; consuming ' + this.duration + 'S'
626    console.info(msg)
627    console.info('[end] run suites end')
628  }
629
630  incorrectFormat () {
631    if (this.coreContext.getDefaultService('config').filterValid.length !== 0) {
632      this.coreContext.getDefaultService('config').filterValid.forEach(function (item) {
633        console.info('this param ' + item + ' is invalid')
634      })
635      return
636    }
637  }
638
639  formatPrint (type, msg) {
640    switch (type) {
641      case 'pass':
642        console.info('[pass]' + msg)
643        break
644      case 'fail':
645        console.info('[fail]' + msg)
646        break
647      case 'failDetail':
648        console.info('[failDetail]' + msg)
649        break
650    }
651  }
652
653  sleep (numberMillis) {
654    var now = new Date()
655    var exitTime = now.getTime() + numberMillis
656    while (true) {
657      now = new Date()
658      if (now.getTime() > exitTime) {
659        return
660      }
661    }
662  }
663}
664
665export {
666  SuiteService,
667  SpecService,
668  ExpectService,
669  ReportService
670}
671