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