diff --git a/README.md b/README.md
index 02608ee..971246a 100644
--- a/README.md
+++ b/README.md
@@ -59,6 +59,7 @@ The `constants.yaml` file is *per-flavor*. In the file, you can define strings (
buildDir: 'build'
sourceDir: 'src'
preBuild: 'scripts/main.js'
+runUnitTestsScript: 'scripts/runTests.js'
mainFlavor: 'flavorName'
flavors: {
flavorName: {
@@ -182,29 +183,42 @@ NOTE: you will need to define a `packageReference` and `packageKey` in `ukor.pro
## Testing
-Ukor uses `UnitTestFramework.brs` as part of its unit test runner and test reporting feature. This is especially useful if you plan on having Continuous Integration as part of your workflow.
+With ukor you have possibility to use whatever unit test framework you want !
+
+Checkout ours [unit testing scripts](http://github.com/willowtreeapps/ukor/tree/master/unit-testing-scripts)
### Setup
-First, copy the modified `UnitTestFramework.brs` in [lib/brs/](../master/lib/brs/UnitTestFramework.brs) to your `src/test/source/` folder, so it loads at startup for when testing.
+#### 1. Define you custom tests running js script:
+
+Example: `/scripts/runTests.js`
+```
+const child_process = require('child_process')
+
+const args = process.argv.slice(2)
+const ip = args[0] // the roku device ip given as an argument by ukor
+
+// main function call
+main()
+
+function main() {
+ child_process.execSync(`npx rooibosC -c ./.rooibosrc.json`)
+ child_process.execSync(`curl -d '' "http://${ip}:8060/launch/dev?RunTests=true"`)
+}
+```
-> Note that the original `UnitTestFramework.brs` can be found [here](https://github.com/rokudev/unit-testing-framework)
+#### 2. Add the path of your script to ukor.properties.yaml
-Next, dd the following snippet in your startup function, after `screen.show()` but before the event loop
+```
+...
+buildDir: build
+sourceDir: src
+preBuild: scripts/preBuild.js
-```brightscript
-if params.RunTests = "true"
- runner = TestRunner()
- if params.host <> invalid
- runner.logger.SetServer(params.host, params.port)
- else
- runner.logger.SetServerURL(params.url)
- end if
- ' other setup if needed
- runner.run()
-end if
+runUnitTestsScript: scripts/runTests.js
```
+
You should now be able to execute your test suite using the `test` command.
```
@@ -213,11 +227,14 @@ ukor test [flavor] [roku]
### What's happening?
-Basically, we modified the rokudev `UnitTestFramework.brs` file to make a `JSON` of test results, and then `POST` that to the specified server. `ukor test [flavor]` builds and deploys the specified flavor with the `test` src folder, and then restarts the channel with parameters to run tests and point the results to the client machine. `ukor` will log the results, and also output results in `xml` and `junit` format to `.out/tests/ukorTests.[xml|junit]`.
+After running the test command, ukor will deploy the channel on given device.
+Once the channel is deployed, ukor starts to run the `runTests.js` script with the device `ip` as argument.
-notes:
-- Ukor now copies `UnitTestFramework.brs` with `ukor init`!
-- `UnitTestFramework.brs` is now up to date with the rokudev repo!
+You can get `ip` inside of `runTests.js` by:
+```
+const args = process.argv.slice(2)
+const ip = args[0] // the roku device ip given as an argument by ukor
+```
# Contributing to Ukor
diff --git a/bin/ukor-init.js b/bin/ukor-init.js
index 3105901..97000a6 100644
--- a/bin/ukor-init.js
+++ b/bin/ukor-init.js
@@ -6,7 +6,7 @@ const yaml = require('js-yaml')
try {
fs.copySync(path.resolve(__dirname, '../lib/sample'), './', {
- overwrite: false,
+ overwrite: false,
errorOnExist: false
})
log.info('created src directory')
@@ -14,31 +14,5 @@ try {
log.error('failed to create src directory')
log.error(e)
}
-
-const propsFile = './ukor.properties.yaml'
-if (!fs.existsSync(propsFile)) {
- const project = fs.createWriteStream(propsFile)
- const defaults = {
- name: path.basename(process.cwd()),
- version: '0.0.1',
- buildDir: 'build',
- sourceDir: 'src',
- defaults: {
- flavor: 'main'
- },
- flavors: {
- main: {
- src: ['main']
- }
- }
- }
- project.write(yaml.safeDump(defaults), () => {
- project.close()
- log.info(`created ${propsFile}`)
- })
-} else {
- log.info(`${propsFile} already exists`)
-}
log.info('Ukor works greate with Wist, run "wist -i" to generate a default .wistrc.json')
-
diff --git a/lib/commands/make.js b/lib/commands/make.js
index 64599a6..445026a 100644
--- a/lib/commands/make.js
+++ b/lib/commands/make.js
@@ -1,13 +1,13 @@
-const archiver = require('archiver')
const fs = require('fs')
const path = require('path')
-const log = require('../utils/log')
-const properties = require('../utils/properties')
const mkdirp = require('mkdirp')
const fse = require('fs-extra')
const rimraf = require('rimraf')
+const log = require('../utils/log')
+const archiver = require('archiver')
+const utils = require('../utils/utils')
const inserter = require('../utils/inserter')
-const childProcess = require('child_process')
+const properties = require('../utils/properties')
const temp = '.ukor'
function moveToTempDir(flavor) {
@@ -31,18 +31,8 @@ function moveToTempDir(flavor) {
}
}
-function execPreBuildScript() {
- if (properties.preBuild != null && properties.preBuild != '') {
- log.info('exec ' + properties.preBuild)
- let result = childProcess.execFileSync('node', [properties.preBuild])
- if (log.level == 'verbose') {
- log.verbose([properties.preBuild, 'exec:', result].join(' '))
- }
- }
-}
-
function bundleFlavor(options, callback) {
- execPreBuildScript()
+ if (properties.preBuild) utils.execScript(properties.preBuild)
const flavor = options.flavor
let flavorSrc = properties.flavors[flavor].src
diff --git a/lib/commands/test.js b/lib/commands/test.js
index 9a4a41e..f0a071f 100644
--- a/lib/commands/test.js
+++ b/lib/commands/test.js
@@ -1,169 +1,20 @@
const request = require('request')
const log = require('../utils/log')
-const path = require('path')
const installer = require('./install')
-const http = require('http')
-const fs = require('fs')
-const getIP = require('ip')
-const XMLWriter = require('xml-writer')
-const mkdirp = require('mkdirp')
+const utils = require('../utils/utils')
const properties = require('../utils/properties')
-function runLogServer(ip, port, timeout, callback) {
- let logServer = http.createServer((req, res) => {
- let body = []
- req.on('data', data => {
- body.push(data)
- })
- req.on('end', () => {
- try {
- body = Buffer.concat(body).toString()
- let stats = JSON.parse(body)
- writeJunit(stats)
- if (stats) {
- log.info('------------')
- log.info('Test Results')
- log.info(
- 'Pass: %d | Fail: %d | Crash: %d',
- stats.correct,
- stats.fail,
- stats.crash
- )
- log.info('===================================')
- stats.suites.forEach(suite => {
- log.info('%s: %d tests', suite.name, suite.tests.length)
- suite.tests.forEach(test => {
- log.info('\t%s: %s', test.name, test.result)
- if (test.result != 'Success') {
- log.info(
- '\t\tError %d: %s',
- test.error.code,
- test.error.message
- )
- }
- })
- log.info('---------------------------------')
- })
- log.info(
- 'Pass: %d | Fail: %d | Crash: %d',
- stats.correct,
- stats.fail,
- stats.crash
- )
- if (stats.fail > 0) {
- process.exit(-1)
- }
- }
- callback ? callback(stats) : null
- res.statusCode = '200'
- } catch (e) {
- log.error(e.message)
- res.statusCode = '403'
- }
- res.end('ok', () => {
- logServer.close()
- })
- })
- })
- logServer.listen(parseInt(port), getIP.address(), () => {
- log.info('listening for tests on %s:%s', getIP.address(), port)
- })
- return logServer
+function runTestScript(script, execArgs) {
+ log.info(`Running ${script} script with args ${execArgs}...`)
+ utils.execScript(script, execArgs)
}
-function writeJunit(stats) {
- log.info('building junit xml')
- const xml = new XMLWriter()
- xml.startDocument()
- xml
- .startElement('testsuites')
- .writeAttribute('name', 'rokuTests')
- .writeAttribute('tests', '' + stats.total)
- .writeAttribute('failures', '' + stats.fail)
- stats.suites.forEach(suite => {
- xml
- .startElement('testsuite')
- .writeAttribute('name', suite.name)
- .writeAttribute('tests', '' + suite.total)
- .writeAttribute('failures', suite.fail)
- .writeAttribute('skipped', suite.skipped)
- suite.tests.forEach(test => {
- xml
- .startElement('testcase')
- .writeAttribute('name', test.name)
- .writeAttribute('time', '' + test.time * 0.001)
- if (test.result.toLowerCase() === 'fail') {
- xml
- .startElement('failure')
- .writeAttribute('type', '' + test.error.code)
- .text(test.error.message)
- .endElement()
- } else if (test.result.toLowerCase() === 'skipped') {
- xml
- .startElement('skipped')
- .endElement()
- }
- xml.endElement()
- })
- xml.endElement()
+function test(options, callback) {
+ installer.installTest(options, (ip, success) => {
+ if (success) properties.runUnitTestsScript ? runTestScript(properties.runUnitTestsScript, [ip]) : log.error(`runUnitTestScript missing from properties`)
})
- xml.endElement()
- xml.endDocument()
- const testOut = `./${properties.sourceDir}/tests`
- mkdirp(testOut)
- log.info(`writing ${testOut}/ukorTests.xml and .junit`)
- fs.writeFileSync(path.join(testOut, 'ukorTests.junit'), xml.toString())
- fs.writeFileSync(path.join(testOut, 'ukorTests.xml'), xml.toString())
- log.info('successfully wrote junit xml')
-}
-
-function runTests(ip, auth, port, callback) {
- setTimeout(() => {
- let url =
- 'http://' +
- ip +
- ':8060/launch/dev?RunTests=true&host=' +
- getIP.address() +
- '&port=' +
- port
- log.debug('starting tests with: %s', url)
- request.post(
- url,
- {
- auth: auth
- },
- (err, response, body) => {
- if (response.statusCode != 200) {
- log.error('Error starting tests: %d', response.statusCode)
- process.exit(-1)
- } else {
- log.info('Tests started')
- }
- callback ? callback(response.statusCode == 200) : null
- }
- )
- }, 10*1000) //run tests 10 seconds after pushing
}
module.exports = {
- test: (options, callback) => {
- installer.installTest(options, (ip, success) => {
- log.info('finished install, running tests')
- if (!success) return
- let port = options.port || '8086'
- let server = runLogServer(ip, port, 5 * 60 * 1000, results => {
- if (results) {
- process.exit(0)
- } else {
- process.exit(-1)
- }
- })
- runTests(ip, options.auth, options.port, success => {
- if (!success) {
- server.close()
- process.exit(-1)
- }
- })
- })
- }
+ test
}
diff --git a/lib/sample/src/main/components/AppScene.brs b/lib/sample/src/main/components/AppScene.brs
index b07b4f3..fdbd0f7 100644
--- a/lib/sample/src/main/components/AppScene.brs
+++ b/lib/sample/src/main/components/AppScene.brs
@@ -1,3 +1,3 @@
sub init()
-
-end sub
\ No newline at end of file
+ ' your code here
+end sub
diff --git a/lib/sample/src/main/components/AppScene.xml b/lib/sample/src/main/components/AppScene.xml
index cd21910..9c4b581 100644
--- a/lib/sample/src/main/components/AppScene.xml
+++ b/lib/sample/src/main/components/AppScene.xml
@@ -1,9 +1,3 @@
-
-
-
-
-
-
-
-
-
\ No newline at end of file
+
+
+
diff --git a/lib/sample/src/main/manifest b/lib/sample/src/main/manifest
index e69de29..5095999 100644
--- a/lib/sample/src/main/manifest
+++ b/lib/sample/src/main/manifest
@@ -0,0 +1,8 @@
+title=UkorBaseProject
+major_version=1
+minor_version=0
+build_version=1
+mm_icon_focus_hd=
+mm_icon_focus_sd=
+splash_screen_hd=
+splash_screen_sd=
diff --git a/lib/sample/src/main/source/main.brs b/lib/sample/src/main/source/main.brs
index e6e7d65..ca9c1ba 100644
--- a/lib/sample/src/main/source/main.brs
+++ b/lib/sample/src/main/source/main.brs
@@ -1,19 +1,30 @@
-sub main(params as object)
- ' set up your screen
+sub main(args as object)
+ appInfo = CreateObject("roAppInfo")
+ m.port = CreateObject("roMessagePort")
- 'check for test parameter
- if params.RunTests = "true"
- runner = TestRunner()
- if params.host <> invalid
- runner.logger.SetServer(params.host, params.port, params.protocol)
+ screen = CreateObject("roSGScreen")
+ screen.setMessagePort(m.port)
+
+ scene = screen.CreateScene("AppScene")
+ screen.show()
+
+ if appInfo.isDev() and args.runTests = "true" then
+ runUnitTests()
else
- runner.logger.SetServerURL(param.url)
+ runMainLoop()
end if
- ' other setup if needed
+end sub
- runner.run()
- end if
+sub runUnitTests()
+ ' put your code here
+end sub
- ' start event loop
+sub runMainLoop()
+ while true
+ msg = wait(0, m.port)
-end sub
\ No newline at end of file
+ if type(msg) = "roSGScreenEvent"
+ if msg.isScreenClosed() then return
+ end if
+ end while
+end sub
diff --git a/lib/sample/src/scripts/preBuild.js b/lib/sample/src/scripts/preBuild.js
new file mode 100644
index 0000000..e94e196
--- /dev/null
+++ b/lib/sample/src/scripts/preBuild.js
@@ -0,0 +1,18 @@
+// app flavors
+const FLAVORS = [
+ { name: 'main', path: 'main' }
+]
+
+// main function call
+main()
+
+function main() {
+ generateManifests(FLAVORS)
+}
+
+function generateManifests(flavors) {
+ flavors.forEach(flavor => {
+ console.log(`Create manifest for flavor: ${flavor.name}`)
+ // create your manifest files dynamically
+ })
+}
diff --git a/lib/sample/src/scripts/runTests.js b/lib/sample/src/scripts/runTests.js
new file mode 100644
index 0000000..893ca45
--- /dev/null
+++ b/lib/sample/src/scripts/runTests.js
@@ -0,0 +1,11 @@
+const child_process = require('child_process')
+
+const args = process.argv.slice(2)
+const ip = args[0] // the roku device ip given as an argument by ukor
+
+// main function call
+main()
+
+function main() {
+ child_process.execSync(`curl -d '' "http://${ip}:8060/launch/dev?RunTests=true"`)
+}
diff --git a/lib/sample/src/test/source/UnitTestFramework.brs b/lib/sample/src/test/source/UnitTestFramework.brs
deleted file mode 100644
index 063fd17..0000000
--- a/lib/sample/src/test/source/UnitTestFramework.brs
+++ /dev/null
@@ -1,2145 +0,0 @@
-'*****************************************************************
-'* Roku Unit Testing Framework (BETA)
-'* A beta tool for automating test suites for Roku channels.
-'*
-'* Build Version: 1.3.0
-'* Build Date: 03/06/2018
-'*
-'* Public Documentation is avaliable on GitHub:
-'* https://github.com/rokudev/unit-testing-framework
-'*
-'*****************************************************************
-'*****************************************************************
-'* Copyright Roku 2011-2018
-'* All Rights Reserved
-'*****************************************************************
-
-' Functions in this file:
-'
-' BaseTestSuite
-' BTS__AddTest
-' BTS__CreateTest
-' BTS__Fail
-' BTS__AssertFalse
-' BTS__AssertTrue
-' BTS__AssertEqual
-' BTS__AssertNotEqual
-' BTS__AssertInvalid
-' BTS__AssertNotInvalid
-' BTS__AssertAAHasKey
-' BTS__AssertAANotHasKey
-' BTS__AssertAAHasKeys
-' BTS__AssertAANotHasKeys
-' BTS__AssertArrayContains
-' BTS__AssertArrayNotContains
-' BTS__AssertArrayContainsSubset
-' BTS__AssertArrayNotContainsSubset
-' BTS__AssertArrayCount
-' BTS__AssertArrayNotCount
-' BTS__AssertEmpty
-' BTS__AssertNotEmpty
-' BTS__AssertArrayContainsOnly
-' BTS__EqValues
-' BTS__EqAssocArray
-' BTS__EqArray
-' BTS__BaseComparator
-
-'----------------------------------------------------------------
-' Main function. Create BaseTestSuite object.
-'
-' @return A BaseTestSuite object.
-'----------------------------------------------------------------
-function BaseTestSuite()
-
- this = {}
- this.Name = "BaseTestSuite"
- this.SKIP_TEST_MESSAGE_PREFIX = "SKIP_TEST_MESSAGE_PREFIX__"
- 'Test Cases methods
- this.testCases = []
- this.addTest = BTS__AddTest
- this.createTest = BTS__CreateTest
- this.StorePerformanceData = BTS__StorePerformanceData
-
- 'Assertion methods which determine test failure or skipping
- this.skip = BTS__Skip
- this.fail = BTS__Fail
- this.assertFalse = BTS__AssertFalse
- this.assertTrue = BTS__AssertTrue
- this.assertEqual = BTS__AssertEqual
- this.assertNotEqual = BTS__AssertNotEqual
- this.assertInvalid = BTS__AssertInvalid
- this.assertNotInvalid = BTS__AssertNotInvalid
- this.assertAAHasKey = BTS__AssertAAHasKey
- this.assertAANotHasKey = BTS__AssertAANotHasKey
- this.assertAAHasKeys = BTS__AssertAAHasKeys
- this.assertAANotHasKeys = BTS__AssertAANotHasKeys
- this.assertArrayContains = BTS__AssertArrayContains
- this.assertArrayNotContains = BTS__AssertArrayNotContains
- this.assertArrayContainsSubset = BTS__AssertArrayContainsSubset
- this.assertArrayNotContainsSubset = BTS__AssertArrayNotContainsSubset
- this.assertArrayCount = BTS__AssertArrayCount
- this.assertArrayNotCount = BTS__AssertArrayNotCount
- this.assertEmpty = BTS__AssertEmpty
- this.assertNotEmpty = BTS__AssertnotEmpty
- this.assertArrayContainsOnly = BTS__AssertArrayContainsOnly
-
- 'Type Comparison Functionality
- this.eqValues = BTS__EqValues
- this.eqAssocArrays = BTS__EqAssocArray
- this.eqArrays = BTS__EqArray
- this.baseComparator = BTS__BaseComparator
-
- return this
-End Function
-
-'----------------------------------------------------------------
-' Add a test to a suite's test cases array.
-'
-' @param name (string) A test name.
-' @param func (string) A test function name.
-'----------------------------------------------------------------
-Sub BTS__AddTest(name as String, func as Object, setup = invalid as Object, teardown = invalid as Object)
- m.testCases.Push(m.createTest(name, func, setup, teardown))
-End Sub
-
-'----------------------------------------------------------------
-' Create a test object.
-'
-' @param name (string) A test name.
-' @param func (string) A test function name.
-'----------------------------------------------------------------
-Function BTS__CreateTest(name as String, func as Object, setup = invalid as Object, teardown = invalid as Object) as Object
- return {
- Name: name
- Func: func
- SetUp: setup
- TearDown: teardown
- perfData: {}
- }
-End Function
-
-'----------------------------------------------------------------
-' Store performance data to current test instance.
-'
-' @param name (string) A property name.
-' @param value (Object) A value of data.
-'----------------------------------------------------------------
-Sub BTS__StorePerformanceData(name as String, value as Object)
- timestamp = StrI(CreateObject("roDateTime").AsSeconds())
- m.testInstance.perfData.Append({
- name: {
- "value" : value
- "timestamp": timestamp
- }
- })
- ' print performance data to console
- ? "PERF_DATA: " + m.testInstance.Name + ": " + timestamp + ": " + name + "|" + TF_Utils__AsString(value)
-End Sub
-
-'----------------------------------------------------------------
-' Assertion methods which determine test failure or skipping
-'----------------------------------------------------------------
-
-'----------------------------------------------------------------
-' Should be used to skip test cases. To skip test you must return the result of this method invocation.
-'
-' @param message (string) Optional skip message.
-' Default value: "".
-'
-' @return A skip message, with a specific prefix added, in order to runner know that this test should be skipped.
-'----------------------------------------------------------------
-function BTS__Skip(message = "" as String) as String
- ' add prefix so we know that this test is skipped, but not failed
- return m.SKIP_TEST_MESSAGE_PREFIX + message
-end function
-
-'----------------------------------------------------------------
-' Fail immediately, with the given message
-'
-' @param msg (string) An error message.
-' Default value: "Error".
-'
-' @return An error message.
-'----------------------------------------------------------------
-Function BTS__Fail(msg = "Error" as string) as string
- return msg
-End Function
-
-'----------------------------------------------------------------
-' Fail the test if the expression is true.
-'
-' @param expr (dynamic) An expression to evaluate.
-' @param msg (string) An error message.
-' Default value: "Expression evaluates to true"
-'
-' @return An error message.
-'----------------------------------------------------------------
-Function BTS__AssertFalse(expr as dynamic, msg = "Expression evaluates to true" as string) as string
- if not TF_Utils__IsBoolean(expr) or expr
- return m.fail(msg)
- end if
- return ""
-End Function
-
-'----------------------------------------------------------------
-' Fail the test unless the expression is true.
-'
-' @param expr (dynamic) An expression to evaluate.
-' @param msg (string) An error message.
-' Default value: "Expression evaluates to false"
-'
-' @return An error message.
-'----------------------------------------------------------------
-Function BTS__AssertTrue(expr as dynamic, msg = "Expression evaluates to false" as string) as string
- if not TF_Utils__IsBoolean(expr) or not expr then
- return msg
- End if
- return ""
-End Function
-
-'----------------------------------------------------------------
-' Fail if the two objects are unequal as determined by the '<>' operator.
-'
-' @param first (dynamic) A first object to compare.
-' @param second (dynamic) A second object to compare.
-' @param msg (string) An error message.
-' Default value: ""
-'
-' @return An error message.
-'----------------------------------------------------------------
-Function BTS__AssertEqual(first as dynamic, second as dynamic, msg = "" as string) as string
- if not m.eqValues(first, second)
- if msg = ""
- first_as_string = TF_Utils__AsString(first)
- second_as_string = TF_Utils__AsString(second)
- msg = first_as_string + " != " + second_as_string
- end if
- return msg
- end if
- return ""
-End Function
-
-'----------------------------------------------------------------
-' Fail if the two objects are equal as determined by the '=' operator.
-'
-' @param first (dynamic) A first object to compare.
-' @param second (dynamic) A second object to compare.
-' @param msg (string) An error message.
-' Default value: ""
-'
-' @return An error message.
-'----------------------------------------------------------------
-Function BTS__AssertNotEqual(first as dynamic, second as dynamic, msg = "" as string) as string
- if m.eqValues(first, second)
- if msg = ""
- first_as_string = TF_Utils__AsString(first)
- second_as_string = TF_Utils__AsString(second)
- msg = first_as_string + " == " + second_as_string
- end if
- return msg
- end if
- return ""
-End Function
-
-'----------------------------------------------------------------
-' Fail if the value is not invalid.
-'
-' @param value (dynamic) A value to check.
-' @param msg (string) An error message.
-' Default value: ""
-'
-' @return An error message.
-'----------------------------------------------------------------
-Function BTS__AssertInvalid(value as dynamic, msg = "" as string) as string
- if value <> Invalid
- if msg = ""
- expr_as_string = TF_Utils__AsString(value)
- msg = expr_as_string + " <> Invalid"
- end if
- return msg
- end if
- return ""
-End Function
-
-'----------------------------------------------------------------
-' Fail if the value is invalid.
-'
-' @param value (dynamic) A value to check.
-' @param msg (string) An error message.
-' Default value: ""
-'
-' @return An error message.
-'----------------------------------------------------------------
-Function BTS__AssertNotInvalid(value as dynamic, msg = "" as string) as string
- if value = Invalid
- if msg = ""
- expr_as_string = TF_Utils__AsString(value)
- msg = expr_as_string + " = Invalid"
- end if
- return msg
- end if
- return ""
-End Function
-
-'----------------------------------------------------------------
-' Fail if the array doesn't have the key.
-'
-' @param array (dynamic) A target array.
-' @param key (string) A key name.
-' @param msg (string) An error message.
-' Default value: ""
-'
-' @return An error message.
-'----------------------------------------------------------------
-Function BTS__AssertAAHasKey(array as dynamic, key as string, msg = "" as string) as string
- if TF_Utils__IsAssociativeArray(array)
- if not array.DoesExist(key)
- if msg = ""
- msg = "Array doesn't have the '" + key + "' key."
- end if
- return msg
- end if
- else
- msg = "Input value is not an Associative Array."
- return msg
- end if
- return ""
-End Function
-
-'----------------------------------------------------------------
-' Fail if the array has the key.
-'
-' @param array (dynamic) A target array.
-' @param key (string) A key name.
-' @param msg (string) An error message.
-' Default value: ""
-'
-' @return An error message.
-'----------------------------------------------------------------
-Function BTS__AssertAANotHasKey(array as dynamic, key as string, msg = "" as string) as string
- if TF_Utils__IsAssociativeArray(array)
- if array.DoesExist(key)
- if msg = ""
- msg = "Array has the '" + key + "' key."
- end if
- return msg
- end if
- else
- msg = "Input value is not an Associative Array."
- return msg
- end if
- return ""
-End Function
-
-'----------------------------------------------------------------
-' Fail if the array doesn't have the keys list.
-'
-' @param array (dynamic) A target associative array.
-' @param keys (object) A key names array.
-' @param msg (string) An error message.
-' Default value: ""
-'
-' @return An error message.
-'----------------------------------------------------------------
-Function BTS__AssertAAHasKeys(array as dynamic, keys as object, msg = "" as string) as string
- if TF_Utils__IsAssociativeArray(array) and TF_Utils__IsArray(keys)
- for each key in keys
- if not array.DoesExist(key)
- if msg = ""
- msg = "Array doesn't have the '" + key + "' key."
- end if
- return msg
- end if
- end for
- else
- msg = "Input value is not an Associative Array."
- return msg
- end if
- return ""
-End Function
-
-'----------------------------------------------------------------
-' Fail if the array has the keys list.
-'
-' @param array (dynamic) A target associative array.
-' @param keys (object) A key names array.
-' @param msg (string) An error message.
-' Default value: ""
-'
-' @return An error message.
-'----------------------------------------------------------------
-Function BTS__AssertAANotHasKeys(array as dynamic, keys as object, msg = "" as string) as string
- if TF_Utils__IsAssociativeArray(array) and TF_Utils__IsArray(keys)
- for each key in keys
- if array.DoesExist(key)
- if msg = ""
- msg = "Array has the '" + key + "' key."
- end if
- return msg
- end if
- end for
- else
- msg = "Input value is not an Associative Array."
- return msg
- end if
- return ""
-End Function
-
-'----------------------------------------------------------------
-' Fail if the array doesn't have the item.
-'
-' @param array (dynamic) A target array.
-' @param value (dynamic) A value to check.
-' @param key (object) A key name for associative array.
-' @param msg (string) An error message.
-' Default value: ""
-'
-' @return An error message.
-'----------------------------------------------------------------
-Function BTS__AssertArrayContains(array as dynamic, value as dynamic, key = invalid as string, msg = "" as string) as string
- if TF_Utils__IsAssociativeArray(array) or TF_Utils__IsArray(array)
- if not TF_Utils__ArrayContains(array, value, key)
- msg = "Array doesn't have the '" + TF_Utils__AsString(value) + "' value."
- return msg
- end if
- else
- msg = "Input value is not an Array."
- return msg
- end if
- return ""
-End Function
-
-'----------------------------------------------------------------
-' Fail if the array has the item.
-'
-' @param array (dynamic) A target array.
-' @param value (dynamic) A value to check.
-' @param key (object) A key name for associative array.
-' @param msg (string) An error message.
-' Default value: ""
-'
-' @return An error message.
-'----------------------------------------------------------------
-Function BTS__AssertArrayNotContains(array as dynamic, value as dynamic, key = invalid as string, msg = "" as string) as string
- if TF_Utils__IsAssociativeArray(array) or TF_Utils__IsArray(array)
- if TF_Utils__ArrayContains(array, value, key)
- msg = "Array has the '" + TF_Utils__AsString(value) + "' value."
- return msg
- end if
- else
- msg = "Input value is not an Array."
- return msg
- end if
- return ""
-End Function
-
-'----------------------------------------------------------------
-' Fail if the array doesn't have the item subset.
-'
-' @param array (dynamic) A target array.
-' @param subset (dynamic) An items array to check.
-' @param msg (string) An error message.
-' Default value: ""
-'
-' @return An error message.
-'----------------------------------------------------------------
-Function BTS__AssertArrayContainsSubset(array as dynamic, subset as dynamic, msg = "" as string) as string
- if (TF_Utils__IsAssociativeArray(array) and TF_Utils__IsAssociativeArray(subset)) or (TF_Utils__IsArray(array) and TF_Utils__IsArray(subset))
- isAA = TF_Utils__IsAssociativeArray(subset)
- for each item in subset
- key = invalid
- value = item
- if isAA
- key = item
- value = item[key]
- end if
- if not TF_Utils__ArrayContains(array, value, key)
- msg = "Array doesn't have the '" + TF_Utils__AsString(value) + "' value."
- return msg
- end if
- end for
- else
- msg = "Input value is not an Array."
- return msg
- end if
- return ""
-End Function
-
-'----------------------------------------------------------------
-' Fail if the array have the item from subset.
-'
-' @param array (dynamic) A target array.
-' @param subset (dynamic) A items array to check.
-' @param msg (string) An error message.
-' Default value: ""
-'
-' @return An error message.
-'----------------------------------------------------------------
-Function BTS__AssertArrayNotContainsSubset(array as dynamic, subset as dynamic, msg = "" as string) as string
- if (TF_Utils__IsAssociativeArray(array) and TF_Utils__IsAssociativeArray(subset)) or (TF_Utils__IsArray(array) and TF_Utils__IsArray(subset))
- isAA = TF_Utils__IsAssociativeArray(subset)
- for each item in subset
- key = invalid
- value = item
- if isAA
- key = item
- value = item[key]
- end if
- if TF_Utils__ArrayContains(array, value, key)
- msg = "Array has the '" + TF_Utils__AsString(value) + "' value."
- return msg
- end if
- end for
- else
- msg = "Input value is not an Array."
- return msg
- end if
- return ""
-End Function
-
-'----------------------------------------------------------------
-' Fail if the array items count <> expected count
-'
-' @param array (dynamic) A target array.
-' @param count (integer) An expected array items count.
-' @param msg (string) An error message.
-' Default value: ""
-'
-' @return An error message.
-'----------------------------------------------------------------
-Function BTS__AssertArrayCount(array as dynamic, count as integer, msg = "" as string) as string
- if TF_Utils__IsAssociativeArray(array) or TF_Utils__IsArray(array)
- if array.Count() <> count
- msg = "Array items count <> " + TF_Utils__AsString(count) + "."
- return msg
- end if
- else
- msg = "Input value is not an Array."
- return msg
- end if
- return ""
-End Function
-
-'----------------------------------------------------------------
-' Fail if the array items count = expected count.
-'
-' @param array (dynamic) A target array.
-' @param count (integer) An expected array items count.
-' @param msg (string) An error message.
-' Default value: ""
-'
-' @return An error message.
-'----------------------------------------------------------------
-Function BTS__AssertArrayNotCount(array as dynamic, count as integer, msg = "" as string) as string
- if TF_Utils__IsAssociativeArray(array) or TF_Utils__IsArray(array)
- if array.Count() = count
- msg = "Array items count = " + TF_Utils__AsString(count) + "."
- return msg
- end if
- else
- msg = "Input value is not an Array."
- return msg
- end if
- return ""
-End Function
-
-'----------------------------------------------------------------
-' Fail if the item is not empty array or string.
-'
-' @param item (dynamic) An array or string to check.
-' @param msg (string) An error message.
-' Default value: ""
-'
-' @return An error message.
-'----------------------------------------------------------------
-Function BTS__AssertEmpty(item as dynamic, msg = "" as string) as string
- if TF_Utils__IsAssociativeArray(item) or TF_Utils__IsArray(item)
- if item.Count() > 0
- msg = "Array is not empty."
- return msg
- end if
- else if TF_Utils__AsString(item) <> ""
- msg = "Input value is not empty."
- return msg
- end if
- return ""
-End Function
-
-'----------------------------------------------------------------
-' Fail if the item is empty array or string.
-'
-' @param item (dynamic) An array or string to check.
-' @param msg (string) An error message.
-' Default value: ""
-'
-' @return An error message.
-'----------------------------------------------------------------
-Function BTS__AssertNotEmpty(item as dynamic, msg = "" as string) as string
- if TF_Utils__IsAssociativeArray(item) or TF_Utils__IsArray(item)
- if item.Count() = 0
- msg = "Array is empty."
- return msg
- end if
- else if TF_Utils__AsString(item) = ""
- msg = "Input value is empty."
- return msg
- end if
- return ""
-End Function
-
-'----------------------------------------------------------------
-' Fail if the array doesn't contains items of specific type only.
-'
-' @param array (dynamic) A target array.
-' @param typeStr (string) An items type name.
-' @param msg (string) An error message.
-' Default value: ""
-'
-' @return An error message.
-'----------------------------------------------------------------
-Function BTS__AssertArrayContainsOnly(array as dynamic, typeStr as string, msg = "" as string) as string
- if TF_Utils__IsAssociativeArray(array) or TF_Utils__IsArray(array)
- methodName = "TF_Utils__Is" + typeStr
- for each item in array
- if not methodName(item)
- msg = TF_Utils__AsString(item) + "is not a '" + typeStr + "' type."
- return msg
- end if
- end for
- else
- msg = "Input value is not an Array."
- return msg
- end if
- return ""
-End Function
-
-'----------------------------------------------------------------
-' Type Comparison Functionality
-'----------------------------------------------------------------
-
-'----------------------------------------------------------------
-' Compare two arbitrary values to each other.
-'
-' @param Value1 (dynamic) A first item to compare.
-' @param Value2 (dynamic) A second item to compare.
-' @param comparator (Function, optional) Function, to compare 2 values. Should take in 2 parameters and return either true or false.
-'
-' @return True if values are equal or False in other case.
-'----------------------------------------------------------------
-Function BTS__EqValues(Value1 as dynamic, Value2 as dynamic, comparator = invalid as Object) as Boolean
- if comparator = invalid
- return m.baseComparator(value1, value2)
- else
- return comparator(value1, value2)
- end if
-End Function
-
-'----------------------------------------------------------------
-' Base comparator for comparing two values.
-'
-' @param Value1 (dynamic) A first item to compare.
-' @param Value2 (dynamic) A second item to compare.
-'
-' @return True if values are equal or False in other case.
-function BTS__BaseComparator(value1 as Dynamic, value2 as Dynamic) as Boolean
- value1Type = type(value1)
- value2Type = type(value2)
-
- if (value1Type = "roList" or value1Type = "roArray") and (value2Type = "roList" or value2Type = "roArray")
- return m.eqArrays(value1, value2)
- else if value1Type = "roAssociativeArray" and value2Type = "roAssociativeArray"
- return m.eqAssocArrays(value1, value2)
- else
- return value1 = value2
- end if
-end function
-
-'----------------------------------------------------------------
-' Compare two roAssociativeArray objects for equality.
-'
-' @param Value1 (object) A first associative array.
-' @param Value2 (object) A second associative array.
-'
-' @return True if arrays are equal or False in other case.
-'----------------------------------------------------------------
-Function BTS__EqAssocArray(Value1 as Object, Value2 as Object) as Boolean
- l1 = Value1.Count()
- l2 = Value2.Count()
-
- if not l1 = l2
- return False
- else
- for each k in Value1
- if not Value2.DoesExist(k)
- return False
- else
- v1 = Value1[k]
- v2 = Value2[k]
- if not m.eqValues(v1, v2)
- return False
- end if
- end if
- end for
- return True
- end if
-End Function
-
-'----------------------------------------------------------------
-' Compare two roArray objects for equality.
-'
-' @param Value1 (object) A first array.
-' @param Value2 (object) A second array.
-'
-' @return True if arrays are equal or False in other case.
-'----------------------------------------------------------------
-Function BTS__EqArray(Value1 as Object, Value2 as Object) as Boolean
- l1 = Value1.Count()
- l2 = Value2.Count()
-
- if not l1 = l2
- return False
- else
- for i = 0 to l1 - 1
- v1 = Value1[i]
- v2 = Value2[i]
- if not m.eqValues(v1, v2) then
- return False
- end if
- end for
- return True
- end if
-End Function
-'*****************************************************************
-'* Copyright Roku 2011-2018
-'* All Rights Reserved
-'*****************************************************************
-
-' Functions in this file:
-' ItemGenerator
-' IG_GetItem
-' IG_GetAssocArray
-' IG_GetArray
-' IG_GetSimpleType
-' IG_GetBoolean
-' IG_GetInteger
-' IG_GetFloat
-' IG_GetString
-'
-
-'----------------------------------------------------------------
-' Main function to generate object according to specified scheme.
-'
-' @param scheme (object) A scheme with desired object structure. Can be
-' any simple type, array of types or associative array in form
-' { propertyName1 : "propertyType1"
-' propertyName2 : "propertyType2"
-' ...
-' propertyNameN : "propertyTypeN" }
-'
-' @return An object according to specified scheme or invalid,
-' if scheme is not valid.
-'----------------------------------------------------------------
-Function ItemGenerator(scheme as object) as Object
-
- this = {}
-
- this.getItem = IG_GetItem
- this.getAssocArray = IG_GetAssocArray
- this.getArray = IG_GetArray
- this.getSimpleType = IG_GetSimpleType
- this.getInteger = IG_GetInteger
- this.getFloat = IG_GetFloat
- this.getString = IG_GetString
- this.getBoolean = IG_GetBoolean
-
- if not TF_Utils__IsValid(scheme)
- return invalid
- end if
-
- return this.getItem(scheme)
-End Function
-
-' TODO: Create IG_GetInvalidItem function with random type fields
-
-'----------------------------------------------------------------
-' Generate object according to specified scheme.
-'
-' @param scheme (object) A scheme with desired object structure.
-' Can be any simple type, array of types or associative array.
-'
-' @return An object according to specified scheme or invalid,
-' if scheme is not one of simple type, array or
-' associative array.
-'----------------------------------------------------------------
-Function IG_GetItem(scheme as object) as object
-
- item = invalid
-
- if TF_Utils__IsAssociativeArray(scheme)
- item = m.getAssocArray(scheme)
- else if TF_Utils__IsArray(scheme)
- item = m.getArray(scheme)
- else if TF_Utils__IsString(scheme)
- item = m.getSimpleType(lCase(scheme))
- end if
-
- return item
-End Function
-
-'----------------------------------------------------------------
-' Generates associative array according to specified scheme.
-'
-' @param scheme (object) An associative array with desired
-' object structure in form
-' { propertyName1 : "propertyType1"
-' propertyName2 : "propertyType2"
-' ...
-' propertyNameN : "propertyTypeN" }
-'
-' @return An associative array according to specified scheme.
-'----------------------------------------------------------------
-Function IG_GetAssocArray(scheme as object) as object
-
- item = {}
-
- for each key in scheme
- if not item.DoesExist(key)
- item[key] = m.getItem(scheme[key])
- end if
- end for
-
- return item
-End Function
-
-'----------------------------------------------------------------
-' Generates array according to specified scheme.
-'
-' @param scheme (object) An array with desired object types.
-'
-' @return An array according to specified scheme.
-'----------------------------------------------------------------
-Function IG_GetArray(scheme as object) as object
-
- item = []
-
- for each key in scheme
- item.Push(m.getItem(key))
- end for
-
- return item
-End Function
-
-'----------------------------------------------------------------
-' Generates random value of specified type.
-'
-' @param typeStr (string) A name of desired object type.
-'
-' @return A simple type object or invalid if type is not supported.
-'----------------------------------------------------------------
-Function IG_GetSimpleType(typeStr as string) as object
-
- item = invalid
-
- if typeStr = "integer" or typeStr = "int" or typeStr = "roint"
- item = m.getInteger()
- else if typeStr = "float" or typeStr = "rofloat"
- item = m.getFloat()
- else if typeStr = "string" or typeStr = "rostring"
- item = m.getString(10)
- else if typeStr = "boolean" or typeStr = "roboolean"
- item = m.getBoolean()
- end if
-
- return item
-End Function
-
-'----------------------------------------------------------------
-' Generates random boolean value.
-'
-' @return A random boolean value.
-'----------------------------------------------------------------
-Function IG_GetBoolean() as boolean
- return TF_Utils__AsBoolean(Rnd(2) \ Rnd(2))
-End Function
-
-'----------------------------------------------------------------
-' Generates random integer value from 1 to specified seed value.
-'
-' @param seed (integer) A seed value for Rnd function.
-' Default value: 100.
-'
-' @return A random integer value.
-'----------------------------------------------------------------
-Function IG_GetInteger(seed = 100 as integer) as integer
- return Rnd(seed)
-End Function
-
-'----------------------------------------------------------------
-' Generates random float value.
-'
-' @return A random float value.
-'----------------------------------------------------------------
-Function IG_GetFloat() as float
- return Rnd(0)
-End Function
-
-'----------------------------------------------------------------
-' Generates random string with specified length.
-'
-' @param seed (integer) A string length.
-'
-' @return A random string value or empty string if seed is 0.
-'----------------------------------------------------------------
-Function IG_GetString(seed as integer) as string
-
- item = ""
- if seed > 0
- stringLength = Rnd(seed)
-
- for i = 0 to stringLength
- chType = Rnd(3)
-
- if chType = 1 'Chr(48-57) - numbers
- chNumber = 47 + Rnd(10)
- else if chType = 2 'Chr(65-90) - Uppercase Letters
- chNumber = 64 + Rnd(26)
- else 'Chr(97-122) - Lowercase Letters
- chNumber = 96 + Rnd(26)
- end if
-
- item = item + Chr(chNumber)
- end for
- end if
-
- return item
-End Function
-'*****************************************************************
-'* Copyright Roku 2011-2018
-'* All Rights Reserved
-'*****************************************************************
-
-' Functions in this file:
-' Logger
-' Logger__SetVerbosity
-' Logger__SetEcho
-' Logger__SetServerURL
-' Logger__PrintStatistic
-' Logger__SendToServer
-' Logger__CreateTotalStatistic
-' Logger__CreateSuiteStatistic
-' Logger__CreateTestStatistic
-' Logger__AppendSuiteStatistic
-' Logger__AppendTestStatistic
-' Logger__PrintSuiteStatistic
-' Logger__PrintTestStatistic
-' Logger__PrintStart
-' Logger__PrintEnd
-' Logger__PrintSuiteSetUp
-' Logger__PrintSuiteStart
-' Logger__PrintSuiteEnd
-' Logger__PrintSuiteTearDown
-' Logger__PrintTestSetUp
-' Logger__PrintTestStart
-' Logger__PrintTestEnd
-' Logger__PrintTestTearDown
-
-'----------------------------------------------------------------
-' Main function. Create Logger object.
-'
-' @return A Logger object.
-'----------------------------------------------------------------
-function Logger() as Object
- this = {}
-
- this.verbosityLevel = {
- basic : 0
- normal : 1
- verboseFailed : 2
- verbose : 3
- }
-
- ' Internal properties
- this.verbosity = this.verbosityLevel.normal
- this.echoEnabled = false
- this.serverURL = invalid
-
- ' Interface
- this.SetVerbosity = Logger__SetVerbosity
- this.SetEcho = Logger__SetEcho
- this.SetServerURL = Logger__SetServerURL
- this.SetServer = Logger__SetServer
- this.PrintStatistic = Logger__PrintStatistic
- this.SendToServer = Logger__SendToServer
-
- this.CreateTotalStatistic = Logger__CreateTotalStatistic
- this.CreateSuiteStatistic = Logger__CreateSuiteStatistic
- this.CreateTestStatistic = Logger__CreateTestStatistic
- this.AppendSuiteStatistic = Logger__AppendSuiteStatistic
- this.AppendTestStatistic = Logger__AppendTestStatistic
-
- ' Internal functions
- this.PrintSuiteStatistic = Logger__PrintSuiteStatistic
- this.PrintTestStatistic = Logger__PrintTestStatistic
- this.PrintStart = Logger__PrintStart
- this.PrintEnd = Logger__PrintEnd
- this.PrintSuiteSetUp = Logger__PrintSuiteSetUp
- this.PrintSuiteStart = Logger__PrintSuiteStart
- this.PrintSuiteEnd = Logger__PrintSuiteEnd
- this.PrintSuiteTearDown = Logger__PrintSuiteTearDown
- this.PrintTestSetUp = Logger__PrintTestSetUp
- this.PrintTestStart = Logger__PrintTestStart
- this.PrintTestEnd = Logger__PrintTestEnd
- this.PrintTestTearDown = Logger__PrintTestTearDown
-
- return this
-end function
-
-'----------------------------------------------------------------
-' Set logging verbosity parameter.
-'
-' @param verbosity (integer) A verbosity level.
-' Posible values:
-' 0 - basic
-' 1 - normal
-' 2 - verbose failed tests
-' 3 - verbose
-' Default level: 1
-'----------------------------------------------------------------
-sub Logger__SetVerbosity(verbosity = m.verbosityLevel.normal as Integer)
- if verbosity >= m.verbosityLevel.basic and verbosity <= m.verbosityLevel.verbose
- m.verbosity = verbosity
- end if
-end sub
-
-'----------------------------------------------------------------
-' Set logging echo parameter.
-'
-' @param enable (boolean) A echo trigger.
-' Posible values: true or false
-' Default value: false
-'----------------------------------------------------------------
-sub Logger__SetEcho(enable = false as Boolean)
- m.echoEnabled = enable
-end sub
-
-'----------------------------------------------------------------
-' Set storage server URL parameter.
-'
-' @param url (string) A storage server URL.
-' Default level: invalid
-'----------------------------------------------------------------
-sub Logger__SetServerURL(url = invalid as String)
- if url <> invalid
- m.serverURL = url
- end if
-end sub
-
-'----------------------------------------------------------------
-' Set storage server URL parameter.
-'
-' @param url (string) host for url.
-' @param port (string) port for url.
-' Default level: invalid
-'----------------------------------------------------------------
-sub Logger__SetServer(host = invalid as String, port = invalid as String)
- if host <> invalid
- if port <> invalid
- m.serverURL = "http://" + host + ":" + port
- else
- m.serverURL = "http://" + host
- end if
- end if
-end sub
-
-'----------------------------------------------------------------
-' Send test results as a POST json payload.
-'
-' @param statObj (object) stats of the test run.
-' Default level: invalid
-'----------------------------------------------------------------
-sub Logger__SendToServer(statObj as Object)
- if m.serverURL <> invalid
- ? "Sending statsObj to server: "; m.serverURL
- request = CreateObject("roUrlTransfer")
- request.SetUrl(m.serverURL)
- statString = FormatJson(statObj)
- ? "Response: "; request.postFromString(statString)
- end if
-end sub
-
-'----------------------------------------------------------------
-' Print statistic object with specified verbosity.
-'
-' @param statObj (object) A statistic object to print.
-'----------------------------------------------------------------
-sub Logger__PrintStatistic(statObj as Object)
- if not m.echoEnabled
- m.PrintStart()
-
- if m.verbosity = m.verbosityLevel.normal or m.verbosity = m.verbosityLevel.verboseFailed
- for each testSuite in statObj.Suites
- for each testCase in testSuite.Tests
- if m.verbosity = m.verbosityLevel.verboseFailed and testCase.result = "Fail"
- m.printTestStatistic(testCase)
- else
- ? "*** "; testSuite.Name; ": "; testCase.Name; " - "; testCase.Result
- end if
- end for
- end for
- else if m.verbosity = m.verbosityLevel.verbose
- for each testSuite in statObj.Suites
- m.PrintSuiteStatistic(testSuite)
- end for
- end if
- end if
-
- ? "***"
- ? "*** Total = "; TF_Utils__AsString(statObj.Total); " ; Passed = "; statObj.Correct; " ; Failed = "; statObj.Fail; " ; Skipped = "; statObj.skipped; " ; Crashes = "; statObj.Crash;
- ? " Time spent: "; statObj.Time; "ms"
- ? "***"
-
- m.PrintEnd()
-end sub
-
-'----------------------------------------------------------------
-' Create an empty statistic object for totals in output log.
-'
-' @return An empty statistic object.
-'----------------------------------------------------------------
-function Logger__CreateTotalStatistic() as Object
- statTotalItem = {
- Suites : []
- Time : 0
- Total : 0
- Correct : 0
- Fail : 0
- Skipped : 0
- Crash : 0
- }
-
- if m.echoEnabled
- m.PrintStart()
- end if
-
- return statTotalItem
-end function
-
-'----------------------------------------------------------------
-' Create an empty statistic object for test suite with specified name.
-'
-' @param name (string) A test suite name for statistic object.
-'
-' @return An empty statistic object for test suite.
-'----------------------------------------------------------------
-function Logger__CreateSuiteStatistic(name as String) as Object
- statSuiteItem = {
- Name : name
- Tests : []
- Time : 0
- Total : 0
- Correct : 0
- Fail : 0
- Skipped : 0
- Crash : 0
- }
-
- if m.echoEnabled
- if m.verbosity = m.verbosityLevel.verbose
- m.PrintSuiteStart(name)
- end if
- end if
-
- return statSuiteItem
-end function
-
-'----------------------------------------------------------------
-' Create statistic object for test with specified name.
-'
-' @param name (string) A test name.
-' @param result (string) A result of test running.
-' Posible values: "Success", "Fail".
-' Default value: "Success"
-' @param time (integer) A test running time.
-' Default value: 0
-' @param errorCode (integer) An error code for failed test.
-' Posible values:
-' 252 (&hFC) : ERR_NORMAL_END
-' 226 (&hE2) : ERR_VALUE_RETURN
-' 233 (&hE9) : ERR_USE_OF_UNINIT_VAR
-' 020 (&h14) : ERR_DIV_ZERO
-' 024 (&h18) : ERR_TM
-' 244 (&hF4) : ERR_RO2
-' 236 (&hEC) : ERR_RO4
-' 002 (&h02) : ERR_SYNTAX
-' 241 (&hF1) : ERR_WRONG_NUM_PARAM
-' Default value: 0
-' @param errorMessage (string) An error message for failed test.
-'
-' @return A statistic object for test.
-'----------------------------------------------------------------
-function Logger__CreateTestStatistic(name as String, result = "Success" as String, time = 0 as Integer, errorCode = 0 as Integer, errorMessage = "" as String) as Object
- statTestItem = {
- Name : name
- Result : result
- Time : time
- PerfData: {}
- Error : {
- Code : errorCode
- Message : errorMessage
- }
- }
-
- if m.echoEnabled
- if m.verbosity = m.verbosityLevel.verbose
- m.PrintTestStart(name)
- end if
- end if
-
- return statTestItem
-end function
-
-'----------------------------------------------------------------
-' Append test statistic to test suite statistic.
-'
-' @param statSuiteObj (object) A target test suite object.
-' @param statTestObj (object) A test statistic to append.
-'----------------------------------------------------------------
-sub Logger__AppendTestStatistic(statSuiteObj as Object, statTestObj as Object)
- if TF_Utils__IsAssociativeArray(statSuiteObj) and TF_Utils__IsAssociativeArray(statTestObj)
- statSuiteObj.Tests.Push(statTestObj)
-
- if TF_Utils__IsInteger(statTestObj.time)
- statSuiteObj.Time = statSuiteObj.Time + statTestObj.Time
- end if
-
- statSuiteObj.Total = statSuiteObj.Total + 1
-
- if lCase(statTestObj.Result) = "success"
- statSuiteObj.Correct = statSuiteObj.Correct + 1
- else if lCase(statTestObj.result) = "fail"
- statSuiteObj.Fail = statSuiteObj.Fail + 1
- else if lCase(statTestObj.result) = "skipped"
- statSuiteObj.skipped++
- else
- statSuiteObj.crash = statSuiteObj.crash + 1
- end if
-
- if m.echoEnabled
- if m.verbosity = m.verbosityLevel.normal
- ? "*** "; statSuiteObj.Name; ": "; statTestObj.Name; " - "; statTestObj.Result
- else if m.verbosity = m.verbosityLevel.verbose
- m.PrintTestStatistic(statTestObj)
- end if
- end if
- end if
-end sub
-
-'----------------------------------------------------------------
-' Append suite statistic to total statistic object.
-'
-' @param statTotalObj (object) A target total statistic object.
-' @param statSuiteObj (object) A test suite statistic object to append.
-'----------------------------------------------------------------
-sub Logger__AppendSuiteStatistic(statTotalObj as Object, statSuiteObj as Object)
- if TF_Utils__IsAssociativeArray(statTotalObj) and TF_Utils__IsAssociativeArray(statSuiteObj)
- statTotalObj.Suites.Push(statSuiteObj)
- statTotalObj.Time = statTotalObj.Time + statSuiteObj.Time
-
- if TF_Utils__IsInteger(statSuiteObj.Total)
- statTotalObj.Total = statTotalObj.Total + statSuiteObj.Total
- end if
-
- if TF_Utils__IsInteger(statSuiteObj.Correct)
- statTotalObj.Correct = statTotalObj.Correct + statSuiteObj.Correct
- end if
-
- if TF_Utils__IsInteger(statSuiteObj.Fail)
- statTotalObj.Fail = statTotalObj.Fail + statSuiteObj.Fail
- end if
-
- if TF_Utils__IsInteger(statSuiteObj.skipped)
- statTotalObj.skipped += statSuiteObj.skipped
- end if
-
- if TF_Utils__IsInteger(statSuiteObj.Crash)
- statTotalObj.Crash = statTotalObj.Crash + statSuiteObj.Crash
- end if
-
- if m.echoEnabled
- if m.verbosity = m.verbosityLevel.verbose
- m.PrintSuiteStatistic(statSuiteObj)
- end if
- end if
- end if
-end sub
-
-'----------------------------------------------------------------
-' Print test suite statistic.
-'
-' @param statSuiteObj (object) A target test suite object to print.
-'----------------------------------------------------------------
-sub Logger__PrintSuiteStatistic(statSuiteObj as Object)
- if not m.echoEnabled
- m.PrintSuiteStart(statSuiteObj.Name)
-
- for each testCase in statSuiteObj.Tests
- m.PrintTestStatistic(testCase)
- end for
- end if
-
- ? "==="
- ? "=== Total = "; TF_Utils__AsString(statSuiteObj.Total); " ; Passed = "; statSuiteObj.Correct; " ; Failed = "; statSuiteObj.Fail; " ; Skipped = "; statSuiteObj.skipped; " ; Crashes = "; statSuiteObj.Crash;
- ? " Time spent: "; statSuiteObj.Time; "ms"
- ? "==="
-
- m.PrintSuiteEnd(statSuiteObj.Name)
-end sub
-
-'----------------------------------------------------------------
-' Print test statistic.
-'
-' @param statTestObj (object) A target test object to print.
-'----------------------------------------------------------------
-sub Logger__PrintTestStatistic(statTestObj as Object)
- if not m.echoEnabled
- m.PrintTestStart(statTestObj.Name)
- end if
-
- ? "--- Result: "; statTestObj.Result
- ? "--- Time: "; statTestObj.Time
-
- if lCase(statTestObj.result) = "skipped"
- if len(statTestObj.message) > 0
- ? "--- Message: "; statTestObj.message
- end if
- else if LCase(statTestObj.Result) <> "success"
- ? "--- Error Code: "; statTestObj.Error.Code
- ? "--- Error Message: "; statTestObj.Error.Message
- end if
-
- m.PrintTestEnd(statTestObj.Name)
-end sub
-
-'----------------------------------------------------------------
-' Print testting start message.
-'----------------------------------------------------------------
-sub Logger__PrintStart()
- ? ""
- ? "******************************************************************"
- ? "******************************************************************"
- ? "************* Start testing *************"
- ? "******************************************************************"
-end sub
-
-'----------------------------------------------------------------
-' Print testing end message.
-'----------------------------------------------------------------
-sub Logger__PrintEnd()
- ? "******************************************************************"
- ? "************* End testing *************"
- ? "******************************************************************"
- ? "******************************************************************"
- ? ""
-end sub
-
-'----------------------------------------------------------------
-' Print test suite SetUp message.
-'----------------------------------------------------------------
-sub Logger__PrintSuiteSetUp(sName as String)
- if m.verbosity = m.verbosityLevel.verbose
- ? "================================================================="
- ? "=== SetUp "; sName; " suite."
- ? "================================================================="
- end if
-end sub
-
-'----------------------------------------------------------------
-' Print test suite start message.
-'----------------------------------------------------------------
-sub Logger__PrintSuiteStart(sName as String)
- ? "================================================================="
- ? "=== Start "; sName; " suite:"
- ? "==="
-end sub
-
-'----------------------------------------------------------------
-' Print test suite end message.
-'----------------------------------------------------------------
-sub Logger__PrintSuiteEnd(sName as String)
- ? "==="
- ? "=== End "; sName; " suite."
- ? "================================================================="
-End Sub
-
-'----------------------------------------------------------------
-' Print test suite TearDown message.
-'----------------------------------------------------------------
-sub Logger__PrintSuiteTearDown(sName as String)
- if m.verbosity = m.verbosityLevel.verbose
- ? "================================================================="
- ? "=== TearDown "; sName; " suite."
- ? "================================================================="
- end if
-end sub
-
-'----------------------------------------------------------------
-' Print test setUp message.
-'----------------------------------------------------------------
-sub Logger__PrintTestSetUp(tName as String)
- if m.verbosity = m.verbosityLevel.verbose
- ? "----------------------------------------------------------------"
- ? "--- SetUp "; tName; " test."
- ? "----------------------------------------------------------------"
- end if
-end sub
-
-'----------------------------------------------------------------
-' Print test start message.
-'----------------------------------------------------------------
-sub Logger__PrintTestStart(tName as String)
- ? "----------------------------------------------------------------"
- ? "--- Start "; tName; " test:"
- ? "---"
-end sub
-
-'----------------------------------------------------------------
-' Print test end message.
-'----------------------------------------------------------------
-sub Logger__PrintTestEnd(tName as String)
- ? "---"
- ? "--- End "; tName; " test."
- ? "----------------------------------------------------------------"
-end sub
-
-'----------------------------------------------------------------
-' Print test TearDown message.
-'----------------------------------------------------------------
-sub Logger__PrintTestTearDown(tName as String)
- if m.verbosity = m.verbosityLevel.verbose
- ? "----------------------------------------------------------------"
- ? "--- TearDown "; tName; " test."
- ? "----------------------------------------------------------------"
- end if
-end sub
-'*****************************************************************
-'* Copyright Roku 2011-2018
-'* All Rights Reserved
-'*****************************************************************
-
-' Functions in this file:
-' TestRunner
-' TestRunner__Run
-' TestRunner__SetTestsDirectory
-' TestRunner__SetTestFilePrefix
-' TestRunner__SetTestSuitePrefix
-' TestRunner__SetTestSuiteName
-' TestRunner__SetTestCaseName
-' TestRunner__SetFailFast
-' TestRunner__GetTestSuitesList
-' TestRunner__GetTestSuiteNamesList
-' TestRunner__GetTestFilesList
-' TestRunner__GetTestNodesList
-' TestFramework__RunNodeTests
-
-'----------------------------------------------------------------
-' Main function. Create TestRunner object.
-'
-' @return A TestRunner object.
-'----------------------------------------------------------------
-function TestRunner() as Object
-
- this = {}
-
- this.isNodeMode = getGlobalAA().top <> invalid
- this.logger = Logger()
-
- ' Internal properties
- this.SKIP_TEST_MESSAGE_PREFIX = "SKIP_TEST_MESSAGE_PREFIX__"
- this.nodesTestDirectory = "pkg:/components/tests"
- if this.isNodeMode
- this.testsDirectory = this.nodesTestDirectory
- this.testFilePrefix = m.top.subtype()
- else
- this.testsDirectory = "pkg:/source/tests"
- this.testFilePrefix = "Test__"
- end if
- this.testSuitePrefix = "TestSuite__"
- this.testSuiteName = ""
- this.testCaseName = ""
- this.failFast = false
-
- ' Interface
- this.Run = TestRunner__Run
- this.SetTestsDirectory = TestRunner__SetTestsDirectory
- this.SetTestFilePrefix = TestRunner__SetTestFilePrefix
- this.SetTestSuitePrefix = TestRunner__SetTestSuitePrefix
- this.SetTestSuiteName = TestRunner__SetTestSuiteName
- this.SetTestCaseName = TestRunner__SetTestCaseName
- this.SetFailFast = TestRunner__SetFailFast
-
- ' Internal functions
- this.GetTestFilesList = TestRunner__GetTestFilesList
- this.GetTestSuitesList = TestRunner__GetTestSuitesList
- this.GetTestNodesList = TestRunner__GetTestNodesList
- this.GetTestSuiteNamesList = TestRunner__GetTestSuiteNamesList
-
- return this
-end function
-
-'----------------------------------------------------------------
-' Run main test loop.
-'
-' @param statObj (object, optional) statistic object to be used in tests
-' @param testSuiteNamesList (array, optional) array of test suite function names to be used in tests
-'
-' @return Statistic object if run in node mode, invalid otherwise
-'----------------------------------------------------------------
-function TestRunner__Run(statObj = m.logger.CreateTotalStatistic() as Object, testSuiteNamesList = [] as Object) as Object
- alltestCount = 0
- totalStatObj = statObj
- testSuitesList = m.GetTestSuitesList(testSuiteNamesList)
- for each testSuite in testSuitesList
- testCases = testSuite.testCases
- testCount = testCases.Count()
- alltestCount = alltestCount + testCount
-
- if TF_Utils__IsFunction(testSuite.SetUp)
- m.logger.PrintSuiteSetUp(testSuite.Name)
- testSuite.SetUp()
- end if
-
- suiteStatObj = m.logger.CreateSuiteStatistic(testSuite.Name)
-
- for each testCase in testCases
- if m.testCaseName = "" or (m.testCaseName <> "" and LCase(testCase.Name) = LCase(m.testCaseName))
- if TF_Utils__IsFunction(testCase.SetUp)
- m.logger.PrintTestSetUp(testCase.Name)
- testCase.SetUp()
- end if
-
- testTimer = CreateObject("roTimespan")
- testStatObj = m.logger.CreateTestStatistic(testCase.Name)
- testSuite.testInstance = testCase
- testSuite.testCase = testCase.Func
-
- runResult = testSuite.testCase()
-
- if runResult <> ""
- if instr(0, runResult, m.SKIP_TEST_MESSAGE_PREFIX) = 1
- testStatObj.result = "Skipped"
- testStatObj.message = runResult.mid(len(m.SKIP_TEST_MESSAGE_PREFIX)) ' remove prefix from the message
- else
- testStatObj.Result = "Fail"
- testStatObj.Error.Code = 1
- testStatObj.Error.Message = runResult
- end if
- else
- testStatObj.Result = "Success"
- end if
-
- testStatObj.Time = testTimer.TotalMilliseconds()
- m.logger.AppendTestStatistic(suiteStatObj, testStatObj)
-
- if TF_Utils__IsFunction(testCase.TearDown)
- m.logger.PrintTestTearDown(testCase.Name)
- testCase.TearDown()
- end if
-
- if testStatObj.Result = "Fail" and m.failFast
- exit for
- end if
- end if
- end for
-
- m.logger.AppendSuiteStatistic(totalStatObj, suiteStatObj)
-
- if TF_Utils__IsFunction(testSuite.TearDown)
- m.logger.PrintSuiteTearDown(testSuite.Name)
- testSuite.TearDown()
- end if
-
- if testStatObj.Result = "Fail" and m.failFast
- exit for
- end if
- end for
-
- if m.isNodeMode
- return totalStatObj
- else
- testNodes = m.getTestNodesList()
-
- for each testNodeName in testNodes
- testNode = createObject("roSGNode", testNodeName)
- if testNode <> invalid
- testSuiteNamesList = m.GetTestSuiteNamesList(testNodeName)
- totalStatObj = testNode.callFunc("TestFramework__RunNodeTests", [totalStatObj, testSuiteNamesList])
- end if
- end for
-
- m.logger.PrintStatistic(totalStatObj)
- end if
- m.logger.SendToServer(totalStatObj)
-end function
-
-'----------------------------------------------------------------
-' Set testsDirectory property.
-'----------------------------------------------------------------
-sub TestRunner__SetTestsDirectory(testsDirectory as String)
- if testsDirectory <> invalid
- m.testsDirectory = testsDirectory
- end if
-end sub
-
-'----------------------------------------------------------------
-' Set setTestFilePrefix property.
-'----------------------------------------------------------------
-sub TestRunner__SetTestFilePrefix(testFilePrefix as String)
- if testFilePrefix <> invalid
- m.testFilePrefix = testFilePrefix
- end if
-end sub
-
-'----------------------------------------------------------------
-' Set testSuitePrefix property.
-'----------------------------------------------------------------
-sub TestRunner__SetTestSuitePrefix(testSuitePrefix as String)
- if testSuitePrefix <> invalid
- m.testSuitePrefix = testSuitePrefix
- end if
-end sub
-
-'----------------------------------------------------------------
-' Set testSuiteName property.
-'----------------------------------------------------------------
-sub TestRunner__SetTestSuiteName(testSuiteName as String)
- if testSuiteName <> invalid
- m.testSuiteName = testSuiteName
- end if
-end sub
-
-'----------------------------------------------------------------
-' Set testCaseName property.
-'----------------------------------------------------------------
-sub TestRunner__SetTestCaseName(testCaseName as String)
- if testCaseName <> invalid
- m.testCaseName = testCaseName
- end if
-end sub
-
-'----------------------------------------------------------------
-' Set failFast property.
-'----------------------------------------------------------------
-sub TestRunner__SetFailFast(failFast = false as Boolean)
- m.failFast = failFast
-end sub
-
-'----------------------------------------------------------------
-' Builds an array of test suite objects.
-'
-' @param testSuiteNamesList (string, optional) array of names of test suite functions. If not passed, scans all test files for test suites
-'
-' @return An array of test suites.
-'----------------------------------------------------------------
-function TestRunner__GetTestSuitesList(testSuiteNamesList = [] as Object) as Object
- result = []
-
- if testSuiteNamesList.count() > 0
- for each functionName in testSuiteNamesList
- eval("testSuite=" + functionName)
- testSuite = testSuite()
-
- if TF_Utils__IsAssociativeArray(testSuite)
- result.Push(testSuite)
- end if
- end for
- else
- testSuiteRegex = CreateObject("roRegex", "^(function|sub)\s(" + m.testSuitePrefix + m.testSuiteName + "[0-9a-z\_]*)\s*\(", "i")
- testFilesList = m.GetTestFilesList()
-
- for each filePath in testFilesList
- code = TF_Utils__AsString(ReadAsciiFile(filePath))
-
- if code <> ""
- for each line in code.Tokenize(chr(10))
- line.Trim()
-
- if testSuiteRegex.IsMatch(line)
- testSuite = invalid
- functionName = testSuiteRegex.Match(line).Peek()
-
- eval("testSuite=" + functionName)
- testSuite = testSuite()
-
- if TF_Utils__IsAssociativeArray(testSuite)
- result.Push(testSuite)
- end if
- end if
- end for
- end if
- end for
- end if
-
- return result
-end function
-
-
-'----------------------------------------------------------------
-' Scans all test files for test suite function names for a given test node.
-'
-' @param testNodeName (string) name of a test node, test suites for which are needed
-'
-' @return An array of test suite names.
-'----------------------------------------------------------------
-function TestRunner__GetTestSuiteNamesList(testNodeName as String) as Object
- result = []
- testSuiteRegex = CreateObject("roRegex", "^(function|sub)\s(" + m.testSuitePrefix + m.testSuiteName + "[0-9a-z\_]*)\s*\(", "i")
- testFilesList = m.GetTestFilesList(m.nodesTestDirectory, testNodeName)
-
- for each filePath in testFilesList
- code = TF_Utils__AsString(ReadAsciiFile(filePath))
-
- if code <> ""
- for each line in code.Tokenize(chr(10))
- line.Trim()
-
- if testSuiteRegex.IsMatch(line)
- functionName = testSuiteRegex.Match(line).Peek()
- result.Push(functionName)
- end if
- end for
- end if
- end for
-
- return result
-end function
-
-
-'----------------------------------------------------------------
-' Scan testsDirectory and all subdirectories for test files.
-'
-' @param testsDirectory (string, optional) A target directory with test files.
-' @param testFilePrefix (string, optional) prefix, used by test files
-'
-' @return An array of test files.
-'----------------------------------------------------------------
-function TestRunner__GetTestFilesList(testsDirectory = m.testsDirectory as String, testFilePrefix = m.testFilePrefix as String) as Object
- result = []
- testsFileRegex = CreateObject("roRegex", "^(" + testFilePrefix + ")[0-9a-z\_]*\.brs$", "i")
-
- if testsDirectory <> ""
- fileSystem = CreateObject("roFileSystem")
- listing = fileSystem.GetDirectoryListing(testsDirectory)
-
- for each item in listing
- itemPath = testsDirectory + "/" + item
- itemStat = fileSystem.Stat(itemPath)
-
- if itemStat.type = "directory" then
- result.Append(m.getTestFilesList(itemPath, testFilePrefix))
- else if testsFileRegex.IsMatch(item) then
- result.Push(itemPath)
- end if
- end for
- end if
-
- return result
-end function
-
-
-'----------------------------------------------------------------
-' Scan nodesTestDirectory and all subdirectories for test nodes.
-'
-' @param nodesTestDirectory (string, optional) A target directory with test nodes.
-'
-' @return An array of test node names.
-'----------------------------------------------------------------
-function TestRunner__GetTestNodesList(testsDirectory = m.nodesTestDirectory as String) as Object
- result = []
- testsFileRegex = CreateObject("roRegex", "^(" + m.testFilePrefix + ")[0-9a-z\_]*\.xml$", "i")
-
- if testsDirectory <> ""
- fileSystem = CreateObject("roFileSystem")
- listing = fileSystem.GetDirectoryListing(testsDirectory)
-
- for each item in listing
- itemPath = testsDirectory + "/" + item
- itemStat = fileSystem.Stat(itemPath)
-
- if itemStat.type = "directory" then
- result.Append(m.getTestNodesList(itemPath))
- else if testsFileRegex.IsMatch(item) then
- result.Push(item.replace(".xml", ""))
- end if
- end for
- end if
-
- return result
-end function
-
-
-'----------------------------------------------------------------
-' Creates and runs test runner. Should be used ONLY within a node.
-'
-' @param params (array) parameters, passed from main thread, used to setup new test runner
-'
-' @return statistic object.
-'----------------------------------------------------------------
-function TestFramework__RunNodeTests(params as Object) as Object
- statObj = params[0]
- testSuiteNamesList = params[1]
-
- Runner = TestRunner()
- return Runner.RUN(statObj, testSuiteNamesList)
-end function
-'*****************************************************************
-'* Copyright Roku 2011-2018
-'* All Rights Reserved
-'*****************************************************************
-' Common framework utility functions
-'*****************************************************************
-
-'*************************************************
-' TF_Utils__IsXmlElement - check if value contains XMLElement interface
-' @param value As Dynamic
-' @return As Boolean - true if value contains XMLElement interface, else return false
-'*************************************************
-function TF_Utils__IsXmlElement(value as Dynamic) as Boolean
- return TF_Utils__IsValid(value) and GetInterface(value, "ifXMLElement") <> invalid
-end function
-
-'*************************************************
-' TF_Utils__IsFunction - check if value contains Function interface
-' @param value As Dynamic
-' @return As Boolean - true if value contains Function interface, else return false
-'*************************************************
-function TF_Utils__IsFunction(value as Dynamic) as Boolean
- return TF_Utils__IsValid(value) and GetInterface(value, "ifFunction") <> invalid
-end function
-
-'*************************************************
-' TF_Utils__IsBoolean - check if value contains Boolean interface
-' @param value As Dynamic
-' @return As Boolean - true if value contains Boolean interface, else return false
-'*************************************************
-function TF_Utils__IsBoolean(value as Dynamic) as Boolean
- return TF_Utils__IsValid(value) and GetInterface(value, "ifBoolean") <> invalid
-end function
-
-'*************************************************
-' TF_Utils__IsInteger - check if value type equals Integer
-' @param value As Dynamic
-' @return As Boolean - true if value type equals Integer, else return false
-'*************************************************
-function TF_Utils__IsInteger(value as Dynamic) as Boolean
- return TF_Utils__IsValid(value) and GetInterface(value, "ifInt") <> invalid and (Type(value) = "roInt" or Type(value) = "roInteger" or Type(value) = "Integer")
-end function
-
-'*************************************************
-' TF_Utils__IsFloat - check if value contains Float interface
-' @param value As Dynamic
-' @return As Boolean - true if value contains Float interface, else return false
-'*************************************************
-function TF_Utils__IsFloat(value as Dynamic) as Boolean
- return TF_Utils__IsValid(value) and GetInterface(value, "ifFloat") <> invalid
-end function
-
-'*************************************************
-' TF_Utils__IsDouble - check if value contains Double interface
-' @param value As Dynamic
-' @return As Boolean - true if value contains Double interface, else return false
-'*************************************************
-function TF_Utils__IsDouble(value as Dynamic) as Boolean
- return TF_Utils__IsValid(value) and GetInterface(value, "ifDouble") <> invalid
-end function
-
-'*************************************************
-' TF_Utils__IsLongInteger - check if value contains LongInteger interface
-' @param value As Dynamic
-' @return As Boolean - true if value contains LongInteger interface, else return false
-'*************************************************
-function TF_Utils__IsLongInteger(value as Dynamic) as Boolean
- return TF_Utils__IsValid(value) and GetInterface(value, "ifLongInt") <> invalid
-end function
-
-'*************************************************
-' TF_Utils__IsNumber - check if value contains LongInteger or Integer or Double or Float interface
-' @param value As Dynamic
-' @return As Boolean - true if value is number, else return false
-'*************************************************
-function TF_Utils__IsNumber(value as Dynamic) as Boolean
- return TF_Utils__IsLongInteger(value) or TF_Utils__IsDouble(value) or TF_Utils__IsInteger(value) or TF_Utils__IsFloat(value)
-end function
-
-'*************************************************
-' TF_Utils__IsList - check if value contains List interface
-' @param value As Dynamic
-' @return As Boolean - true if value contains List interface, else return false
-'*************************************************
-function TF_Utils__IsList(value as Dynamic) as Boolean
- return TF_Utils__IsValid(value) and GetInterface(value, "ifList") <> invalid
-end function
-
-'*************************************************
-' TF_Utils__IsArray - check if value contains Array interface
-' @param value As Dynamic
-' @return As Boolean - true if value contains Array interface, else return false
-'*************************************************
-function TF_Utils__IsArray(value as Dynamic) as Boolean
- return TF_Utils__IsValid(value) and GetInterface(value, "ifArray") <> invalid
-end function
-
-'*************************************************
-' TF_Utils__IsAssociativeArray - check if value contains AssociativeArray interface
-' @param value As Dynamic
-' @return As Boolean - true if value contains AssociativeArray interface, else return false
-'*************************************************
-function TF_Utils__IsAssociativeArray(value as Dynamic) as Boolean
- return TF_Utils__IsValid(value) and GetInterface(value, "ifAssociativeArray") <> invalid
-end function
-
-'*************************************************
-' TF_Utils__IsSGNode - check if value contains SGNodeChildren interface
-' @param value As Dynamic
-' @return As Boolean - true if value contains SGNodeChildren interface, else return false
-'*************************************************
-function TF_Utils__IsSGNode(value as Dynamic) as Boolean
- return TF_Utils__IsValid(value) and GetInterface(value, "ifSGNodeChildren") <> invalid
-end function
-
-'*************************************************
-' TF_Utils__IsString - check if value contains String interface
-' @param value As Dynamic
-' @return As Boolean - true if value contains String interface, else return false
-'*************************************************
-function TF_Utils__IsString(value as Dynamic) as Boolean
- return TF_Utils__IsValid(value) and GetInterface(value, "ifString") <> invalid
-end function
-
-'*************************************************
-' TF_Utils__IsNotEmptyString - check if value contains String interface and length more 0
-' @param value As Dynamic
-' @return As Boolean - true if value contains String interface and length more 0, else return false
-'*************************************************
-function TF_Utils__IsNotEmptyString(value as Dynamic) as Boolean
- return TF_Utils__IsString(value) and len(value) > 0
-end function
-
-'*************************************************
-' TF_Utils__IsDateTime - check if value contains DateTime interface
-' @param value As Dynamic
-' @return As Boolean - true if value contains DateTime interface, else return false
-'*************************************************
-function TF_Utils__IsDateTime(value as Dynamic) as Boolean
- return TF_Utils__IsValid(value) and (GetInterface(value, "ifDateTime") <> invalid or Type(value) = "roDateTime")
-end function
-
-'*************************************************
-' TF_Utils__IsValid - check if value initialized and not equal invalid
-' @param value As Dynamic
-' @return As Boolean - true if value initialized and not equal invalid, else return false
-'*************************************************
-function TF_Utils__IsValid(value as Dynamic) as Boolean
- return Type(value) <> "" and value <> invalid
-end function
-
-'*************************************************
-' TF_Utils__ValidStr - return value if his contains String interface else return empty string
-' @param value As Object
-' @return As String - value if his contains String interface else return empty string
-'*************************************************
-function TF_Utils__ValidStr(obj as Object) as String
- if obj <> invalid and GetInterface(obj, "ifString") <> invalid
- return obj
- else
- return ""
- end if
-end function
-
-'*************************************************
-' TF_Utils__AsString - convert input to String if this possible, else return empty string
-' @param input As Dynamic
-' @return As String - return converted string
-'*************************************************
-function TF_Utils__AsString(input as Dynamic) as String
- if TF_Utils__IsValid(input) = false
- return ""
- else if TF_Utils__IsString(input)
- return input
- else if TF_Utils__IsInteger(input) or TF_Utils__IsLongInteger(input) or TF_Utils__IsBoolean(input)
- return input.ToStr()
- else if TF_Utils__IsFloat(input) or TF_Utils__IsDouble(input)
- return Str(input).Trim()
- else
- return ""
- end If
-end function
-
-'*************************************************
-' TF_Utils__AsInteger - convert input to Integer if this possible, else return 0
-' @param input As Dynamic
-' @return As Integer - return converted Integer
-'*************************************************
-function TF_Utils__AsInteger(input as Dynamic) as Integer
- if TF_Utils__IsValid(input) = false
- return 0
- else if TF_Utils__IsString(input)
- return input.ToInt()
- else if TF_Utils__IsInteger(input)
- return input
- else if TF_Utils__IsFloat(input) or TF_Utils__IsDouble(input) or TF_Utils__IsLongInteger(input)
- return Int(input)
- else
- return 0
- end if
-end function
-
-'*************************************************
-' TF_Utils__AsLongInteger - convert input to LongInteger if this possible, else return 0
-' @param input As Dynamic
-' @return As Integer - return converted LongInteger
-'*************************************************
-function TF_Utils__AsLongInteger(input as Dynamic) as LongInteger
- if TF_Utils__IsValid(input) = false
- return 0
- else if TF_Utils__IsString(input)
- return TF_Utils__AsInteger(input)
- else if TF_Utils__IsLongInteger(input) or TF_Utils__IsFloat(input) or TF_Utils__IsDouble(input) or TF_Utils__IsInteger(input)
- return input
- else
- return 0
- end if
-end function
-
-'*************************************************
-' TF_Utils__AsFloat - convert input to Float if this possible, else return 0.0
-' @param input As Dynamic
-' @return As Float - return converted Float
-'*************************************************
-function TF_Utils__AsFloat(input as Dynamic) as Float
- if TF_Utils__IsValid(input) = false
- return 0.0
- else if TF_Utils__IsString(input)
- return input.ToFloat()
- else if TF_Utils__IsInteger(input)
- return (input / 1)
- else if TF_Utils__IsFloat(input) or TF_Utils__IsDouble(input) or TF_Utils__IsLongInteger(input)
- return input
- else
- return 0.0
- end if
-end function
-
-'*************************************************
-' TF_Utils__AsDouble - convert input to Double if this possible, else return 0.0
-' @param input As Dynamic
-' @return As Float - return converted Double
-'*************************************************
-function TF_Utils__AsDouble(input as Dynamic) as Double
- if TF_Utils__IsValid(input) = false
- return 0.0
- else if TF_Utils__IsString(input)
- return TF_Utils__AsFloat(input)
- else if TF_Utils__IsInteger(input) or TF_Utils__IsLongInteger(input) or TF_Utils__IsFloat(input) or TF_Utils__IsDouble(input)
- return input
- else
- return 0.0
- end if
-end function
-
-'*************************************************
-' TF_Utils__AsBoolean - convert input to Boolean if this possible, else return False
-' @param input As Dynamic
-' @return As Boolean
-'*************************************************
-function TF_Utils__AsBoolean(input as Dynamic) as Boolean
- if TF_Utils__IsValid(input) = false
- return false
- else if TF_Utils__IsString(input)
- return LCase(input) = "true"
- else if TF_Utils__IsInteger(input) or TF_Utils__IsFloat(input)
- return input <> 0
- else if TF_Utils__IsBoolean(input)
- return input
- else
- return false
- end if
-end function
-
-'*************************************************
-' TF_Utils__AsArray - if type of value equals array return value, else return array with one element [value]
-' @param value As Object
-' @return As Object - roArray
-'*************************************************
-function TF_Utils__AsArray(value as Object) as Object
- if TF_Utils__IsValid(value)
- if not TF_Utils__IsArray(value)
- return [value]
- else
- return value
- end if
- end if
- return []
-end function
-
-'=====================
-' Strings
-'=====================
-
-'*************************************************
-' TF_Utils__IsNullOrEmpty - check if value is invalid or empty
-' @param value As Dynamic
-' @return As Boolean - true if value is null or empty string, else return false
-'*************************************************
-function TF_Utils__IsNullOrEmpty(value as Dynamic) as Boolean
- if TF_Utils__IsString(value)
- return Len(value) = 0
- else
- return not TF_Utils__IsValid(value)
- end if
-end function
-
-'=====================
-' Arrays
-'=====================
-
-'*************************************************
-' TF_Utils__FindElementIndexInArray - find an element index in array
-' @param array As Object
-' @param value As Object
-' @param compareAttribute As Dynamic
-' @param caseSensitive As Boolean
-' @return As Integer - element index if array contains a value, else return -1
-'*************************************************
-function TF_Utils__FindElementIndexInArray(array as Object, value as Object, compareAttribute = invalid as Dynamic, caseSensitive = false as Boolean) as Integer
- if TF_Utils__IsArray(array)
- for i = 0 to TF_Utils__AsArray(array).Count() - 1
- compareValue = array[i]
-
- if compareAttribute <> invalid and TF_Utils__IsAssociativeArray(compareValue)
- compareValue = compareValue.LookupCI(compareAttribute)
- end If
-
- if TF_Utils__IsString(compareValue) and TF_Utils__IsString(value) and not caseSensitive
- if LCase(compareValue) = LCase(value)
- return i
- end If
- else if compareValue = value
- return i
- end if
-
- item = array[i]
- next
- end if
- return -1
-end function
-
-'*************************************************
-' TF_Utils__ArrayContains - check if array contains specified value
-' @param array As Object
-' @param value As Object
-' @param compareAttribute As Dynamic
-' @return As Boolean - true if array contains a value, else return false
-'*************************************************
-function TF_Utils__ArrayContains(array as Object, value as Object, compareAttribute = invalid as Dynamic) as Boolean
- return (TF_Utils__FindElementIndexInArray(array, value, compareAttribute) > -1)
-end function
diff --git a/lib/sample/ukor.properties.yaml b/lib/sample/ukor.properties.yaml
new file mode 100644
index 0000000..1d8a441
--- /dev/null
+++ b/lib/sample/ukor.properties.yaml
@@ -0,0 +1,12 @@
+name: UkorBaseProject
+version: 1.0.1
+buildDir: build
+sourceDir: src
+preBuild: scripts/preBuild.js
+runUnitTestsScript: scripts/runTests.js
+defaults:
+ flavor: main
+flavors:
+ main:
+ src:
+ - main
diff --git a/lib/utils/schema.js b/lib/utils/schema.js
index b1d83fd..2160747 100644
--- a/lib/utils/schema.js
+++ b/lib/utils/schema.js
@@ -82,6 +82,10 @@ module.exports = {
type: 'string',
pattern: '\S*',
},
+ runUnitTestsScript: {
+ type: 'string',
+ pattern: '\S*',
+ },
version: {
type: 'string',
pattern: '^([0-9]+)\.([0-9]+)\.([0-9]+)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:[\+][0-9A-Za-z-]+)?$',
diff --git a/lib/utils/utils.js b/lib/utils/utils.js
index ffe05a1..1ae6ba0 100644
--- a/lib/utils/utils.js
+++ b/lib/utils/utils.js
@@ -4,6 +4,7 @@ const log = require('./log')
const xml2js = require('xml2js')
const request = require('request')
const properties = require('./properties')
+const childProcess = require('child_process')
const matchers = {
ip: /^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/,
usn: /^[A-Z0-9]{12}$/
@@ -49,6 +50,12 @@ function parseAuth(auth) {
return null
}
+function execScript(script, args=[]) {
+ log.info('exec: ' + script)
+ let result = childProcess.execFileSync('node', [script].concat(args))
+ log.verbose([script, 'result: ', result].join(' '))
+}
+
function continueIfExists(object, message) {
if (!object) {
log.error(message)
@@ -73,6 +80,7 @@ function getUsn(options) {
module.exports = {
parseRoku,
parseAuth,
+ execScript,
continueIfExists,
getAllSourceFiles,
getDeviceInfo,
diff --git a/unit-testing-scripts/UnitTestFrameworkV1.js b/unit-testing-scripts/UnitTestFrameworkV1.js
new file mode 100644
index 0000000..302d058
--- /dev/null
+++ b/unit-testing-scripts/UnitTestFrameworkV1.js
@@ -0,0 +1,115 @@
+const fs = require('fs')
+const ip = require('ip')
+const path = require('path')
+const http = require('http')
+const mkdirp = require('mkdirp')
+const request = require('request')
+const XMLWriter = require('xml-writer')
+const child_process = require('child_process')
+
+const args = process.argv.slice(2)
+
+const ROKU_IP = args[0] // the roku device ip given as an argument by ukor
+const LOG_SERVER_PORT = 8086
+
+// main function call
+main()
+
+function main() {
+ ipAddress = ip.address()
+
+ runLogServer(ipAddress, LOG_SERVER_PORT)
+ child_process.execSync(`curl -d '' "http://${ROKU_IP}:8060/launch/dev?RunTests=true&host=${ipAddress}&port=${LOG_SERVER_PORT}"`)
+}
+
+function runLogServer(ip, port, callback) {
+ let logServer = http.createServer((req, res) => {
+ let body = []
+ req.on('data', data => {
+ body.push(data)
+ })
+ req.on('end', () => {
+ try {
+ body = Buffer.concat(body).toString()
+ let stats = JSON.parse(body)
+ writeJunit(stats)
+ if (stats) {
+ console.log('------------')
+ console.log('Test Results')
+ console.log('Pass: %d | Fail: %d | Crash: %d', stats.correct, stats.fail, stats.crash)
+ console.log('===================================')
+ stats.suites.forEach(suite => {
+ console.log('%s: %d tests', suite.name, suite.tests.length)
+ suite.tests.forEach(test => {
+ console.log('\t%s: %s', test.name, test.result)
+ if (test.result != 'Success') console.log('\t\tError %d: %s', test.error.code, test.error.message)
+ })
+ console.log('---------------------------------')
+ })
+ console.log('Pass: %d | Fail: %d | Crash: %d', stats.correct, stats.fail, stats.crash)
+ if (stats.fail > 0) process.exit(-1)
+ }
+ callback ? callback(stats) : null
+ res.statusCode = '200'
+ } catch (e) {
+ log.error(e.message)
+ res.statusCode = '403'
+ }
+ res.end('ok', () => {
+ logServer.close()
+ })
+ })
+ })
+
+ logServer.listen(port, ip, () => {
+ console.log('listening for tests on %s:%d', ip, port)
+ })
+
+ return logServer
+}
+
+function writeJunit(stats) {
+ console.log('building junit xml')
+ const xml = new XMLWriter()
+ xml.startDocument()
+ xml
+ .startElement('testsuites')
+ .writeAttribute('name', 'rokuTests')
+ .writeAttribute('tests', '' + stats.total)
+ .writeAttribute('failures', '' + stats.fail)
+ stats.suites.forEach(suite => {
+ xml
+ .startElement('testsuite')
+ .writeAttribute('name', suite.name)
+ .writeAttribute('tests', '' + suite.total)
+ .writeAttribute('failures', suite.fail)
+ .writeAttribute('skipped', suite.skipped)
+ suite.tests.forEach(test => {
+ xml
+ .startElement('testcase')
+ .writeAttribute('name', test.name)
+ .writeAttribute('time', '' + test.time * 0.001)
+ if (test.result.toLowerCase() === 'fail') {
+ xml
+ .startElement('failure')
+ .writeAttribute('type', '' + test.error.code)
+ .text(test.error.message)
+ .endElement()
+ } else if (test.result.toLowerCase() === 'skipped') {
+ xml
+ .startElement('skipped')
+ .endElement()
+ }
+ xml.endElement()
+ })
+ xml.endElement()
+ })
+ xml.endElement()
+ xml.endDocument()
+ const testOut = `./tests`
+ mkdirp(testOut)
+ console.log(`writing ${testOut}/ukorTests.xml and .junit`)
+ fs.writeFileSync(path.join(testOut, 'ukorTests.junit'), xml.toString())
+ fs.writeFileSync(path.join(testOut, 'ukorTests.xml'), xml.toString())
+ console.log('successfully wrote junit xml')
+}