├── .gitignore
├── test-project
├── manifest
├── package.json
├── source
│ ├── tests
│ │ ├── testConfig.json
│ │ ├── RequestsUtilsTests.brs
│ │ ├── RequestsHeadersTests.brs
│ │ ├── RequestsCacheTest.brs
│ │ ├── RequestsQueryStringTests.brs
│ │ └── RequestsTests.brs
│ ├── main.brs
│ ├── roku_modules
│ │ └── rr
│ │ │ └── Requests.cat.brs
│ └── rooibos.cat.brs
└── components
│ └── TestsScene.xml
├── bsconfig.json
├── .vscode
└── settings.json
├── roku-requests.code-workspace
├── .github
└── workflows
│ ├── build.yml
│ ├── publish-release.yml
│ ├── make-release-artifacts.yml
│ └── initialize-release.yml
├── package.json
├── LICENSE
├── CHANGELOG.md
├── README.md
└── src
└── source
└── Requests.brs
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | out
3 | dist
4 | *.tgz
--------------------------------------------------------------------------------
/test-project/manifest:
--------------------------------------------------------------------------------
1 | title=Roku Requests
2 | major_version=0
3 | minor_version=1
4 | build_version=2
5 |
--------------------------------------------------------------------------------
/bsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "rootDir": "src",
3 | "plugins": [
4 | "@rokucommunity/bslint"
5 | ]
6 | }
7 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "files.exclude": {
3 | "test-project": true,
4 | "*.code-workspace": true,
5 | "node_modules": true
6 | }
7 | }
--------------------------------------------------------------------------------
/roku-requests.code-workspace:
--------------------------------------------------------------------------------
1 | {
2 | "folders": [
3 | {
4 | "path": "test-project"
5 | },
6 | {
7 | "path": "."
8 | }
9 | ],
10 | "settings": {}
11 | }
--------------------------------------------------------------------------------
/test-project/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "dependencies": {
4 | "rr": "file:../"
5 | },
6 | "devDependencies": {
7 | "ropm": "^0.6.3"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/test-project/source/tests/testConfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "logLevel": 1,
3 | "testsDirectory": "pkg:/source/Tests",
4 | "testFilePrefix": "Test__",
5 | "failFast": false,
6 | "showOnlyFailures": false,
7 | "maxLinesWithoutSuiteDirective": 100
8 | }
9 |
--------------------------------------------------------------------------------
/test-project/components/TestsScene.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: build
2 | on: [push, pull_request]
3 |
4 | jobs:
5 | ci:
6 | runs-on: ubuntu-latest
7 | steps:
8 | - uses: actions/checkout@master
9 | - uses: actions/setup-node@master
10 | with:
11 | node-version: "16.15.1"
12 | - run: npm ci
13 | - run: npm run validate
14 |
--------------------------------------------------------------------------------
/.github/workflows/publish-release.yml:
--------------------------------------------------------------------------------
1 | name: Publish Release
2 |
3 | on:
4 | pull_request:
5 | types:
6 | - closed
7 | paths:
8 | - 'package.json'
9 | - 'package-lock.json'
10 |
11 | jobs:
12 | run:
13 | if: startsWith( github.head_ref, 'release/') && (github.event.pull_request.merged == true)
14 | uses: rokucommunity/workflows/.github/workflows/publish-release.yml@master
15 | with:
16 | release-type: "npm" # "vsce"
17 | ref: ${{ github.event.pull_request.merge_commit_sha }}
18 | secrets: inherit
19 |
--------------------------------------------------------------------------------
/.github/workflows/make-release-artifacts.yml:
--------------------------------------------------------------------------------
1 | name: Make Release Artifacts
2 |
3 | on:
4 | pull_request:
5 | types:
6 | - reopened
7 | - opened
8 | - synchronize
9 |
10 | jobs:
11 | run:
12 | if: startsWith( github.head_ref, 'release/')
13 | uses: rokucommunity/workflows/.github/workflows/make-release-artifacts.yml@master
14 | with:
15 | branch: ${{ github.event.pull_request.head.ref }}
16 | node-version: "16.15.1"
17 | artifact-paths: "./*.tgz" # "*.vsix"
18 | secrets: inherit
19 |
20 | success-or-skip:
21 | if: always()
22 | needs: [run]
23 | runs-on: ubuntu-latest
24 | steps:
25 | - run: if [ "${{ needs.run.result }}" = "failure" ]; then exit 1; fi
26 |
27 |
--------------------------------------------------------------------------------
/test-project/source/tests/RequestsUtilsTests.brs:
--------------------------------------------------------------------------------
1 | '@TestSuite [ATST] Requests Utils tests
2 |
3 | '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
4 | '@It Utils
5 | '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
6 |
7 | '@Test inString true
8 | '@Params["\", "\n"]
9 | '@Params["=", "test=123"]
10 | '@Params[".", "."]
11 | '@Params[".", "19.99"]
12 | '@Params["4", "12345"]
13 | function Atst__Test_Requests_Utils_inString_true(char, strValue) as void
14 |
15 | rtn = rr_Requests_Utils_inString(char, strValue)
16 | m.AssertTrue(rtn)
17 |
18 | end function
19 |
20 |
21 | '@Test inString false
22 | '@Params["test=123", "="]
23 | '@Params[".", ""]
24 | '@Params["a", "12345"]
25 | function Atst__Test_Requests_Utils_inString_false(char, strValue) as void
26 |
27 | rtn = rr_Requests_Utils_inString(char, strValue)
28 | m.AssertFalse(rtn)
29 |
30 | end function
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "roku-requests",
3 | "version": "1.2.0",
4 | "description": "BrightScript http framework for Roku apps, inspired by Pyton Requests",
5 | "main": "index.js",
6 | "scripts": {
7 | "preversion": "npm run validate",
8 | "validate": "bsc --create-package=false --copy-to-staging=false",
9 | "package": "npm run build && npm pack"
10 | },
11 | "devDependencies": {
12 | "@rokucommunity/bslint": "^0.8.1",
13 | "brighterscript": "^0.61.2"
14 | },
15 | "repository": {
16 | "type": "git",
17 | "url": "git+https://github.com/rokucommunity/roku-requests.git"
18 | },
19 | "keywords": [
20 | "ropm",
21 | "roku",
22 | "http"
23 | ],
24 | "author": "Blake Visin",
25 | "license": "MIT",
26 | "bugs": {
27 | "url": "https://github.com/rokucommunity/roku-requests/issues"
28 | },
29 | "homepage": "https://github.com/rokucommunity/roku-requests#readme",
30 | "ropm": {
31 | "packageRootDir": "src"
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/test-project/source/tests/RequestsHeadersTests.brs:
--------------------------------------------------------------------------------
1 | '@TestSuite [ATST] Requests Headers tests
2 |
3 | '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
4 | '@It Headers
5 | '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
6 |
7 | '@Test addHeader
8 | function Atst__Test_Requests_headers_addHeader() as void
9 |
10 | headers = rr_Requests_headers()
11 | headers.addHeader("foo", "bar")
12 | headers.addHeader("x-api-key", "123")
13 | m.AssertAAHasKeys(headers._headers, ["foo", "x-api-key"])
14 | m.AssertEqual(headers._headers["foo"], "bar")
15 | m.AssertEqual(headers._headers["x-api-key"], "123")
16 |
17 | end function
18 |
19 | '@Test addHeadersAA
20 | function Atst__Test_Requests_headers_addHeadersAA() as void
21 |
22 | headers = rr_Requests_headers()
23 | headers.addHeadersAA({"foo":"bar", "x-api-key":"123"})
24 | m.AssertAAHasKeys(headers._headers, ["foo", "x-api-key"])
25 | m.AssertEqual(headers._headers["foo"], "bar")
26 | m.AssertEqual(headers._headers["x-api-key"], "123")
27 |
28 | end function
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Blake Visin
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/.github/workflows/initialize-release.yml:
--------------------------------------------------------------------------------
1 | name: Initialize Release
2 |
3 | on:
4 | workflow_dispatch:
5 | inputs:
6 | branch:
7 | type: string
8 | description: "Create release from this branch:"
9 | default: "master"
10 | required: true
11 | releaseType:
12 | type: choice
13 | description: "Release type:"
14 | required: true
15 | default: "patch"
16 | options:
17 | - major
18 | - minor
19 | - patch
20 | - prerelease
21 | customVersion:
22 | type: string
23 | description: "Version override: (ignore release type)"
24 | default: ""
25 | required: false
26 | installDependencies:
27 | type: boolean
28 | description: "Install latest RokuCommunity dependencies"
29 | required: true
30 | default: true
31 |
32 | jobs:
33 | run:
34 | uses: rokucommunity/workflows/.github/workflows/initialize-release.yml@master
35 | with:
36 | branch: ${{ github.event.inputs.branch }}
37 | releaseType: ${{ github.event.inputs.releaseType }}
38 | customVersion: ${{ github.event.inputs.customVersion }}
39 | installDependencies: ${{ github.event.inputs.installDependencies }}
40 | secrets: inherit
41 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 | All notable changes to this project will be documented in this file.
3 |
4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6 |
7 |
8 |
9 | ## [1.2.0](https://github.com/rokucommunity/roku-requests/compare/v1.1.0...v1.2.0) - 2023-04-21
10 | ### Changed
11 | - only cache valid response ([#31](https://github.com/rokucommunity/roku-requests/pull/31))
12 | ### Fixed
13 | - crash where fs was sometimes invalid ([#36](https://github.com/rokucommunity/roku-requests/pull/36))
14 |
15 |
16 |
17 | ## [1.1.0](https://github.com/rokucommunity/roku-requests/compare/v1.0.0...v1.1.0) - 2022-01-12
18 | ### Added
19 | - Add `parseJson` and `parseJsonFlags` arguments ([#28](https://github.com/rokucommunity/roku-requests/pull/28))
20 |
21 |
22 |
23 | ## [1.0.0](https://github.com/rokucommunity/roku-requests/compare/v0.2.0...v1.0.0) - 2022-01-12
24 | ### Changed
25 | - This project has been in the wild for years even before ropm. Releasing as v1 now that it's clear the ropm-published package is stable.
26 |
27 |
28 |
29 | ## [0.2.0](https://github.com/rokucommunity/roku-requests/compare/v0.1.0...v0.2.0) - 2020-10-18
30 | ### Changed
31 | - turned over to RokuCommunity to maintain
32 | - Renamed `Requests.cat.brs` to `Requests.brs`
33 | - Retain all code comments
34 | - initial `ropm`/`npm` release
35 |
36 |
37 |
38 | ## [0.1.0](https://github.com/rokucommunity/roku-requests) - 2018-09-21
39 | ### Added
40 | - Basic code setup
41 |
--------------------------------------------------------------------------------
/test-project/source/main.brs:
--------------------------------------------------------------------------------
1 | sub Main(args as dynamic)
2 | ? "Launching with args "
3 | ? args
4 | m.args = args
5 |
6 | if (type(Rooibos__Init) = "Function") then Rooibos__Init(args, SetupGlobals, AddTestUtils)
7 |
8 | InitScreen()
9 | end sub
10 |
11 | sub AddTestUtils(testCase)
12 | 'add your own test utils you want access to when testing here
13 |
14 | 'e.g.
15 | 'testCase.testUtils = TestUtils()
16 | 'testCase.r = rodash()
17 | end sub
18 |
19 | function InitScreen() as void
20 | 'this will be where you setup your typical roku app
21 | 'it will not be launched when running unit tests
22 | screen = CreateObject("roSGScreen")
23 | m.port = CreateObject("roMessagePort")
24 | screen.setMessagePort(m.port)
25 |
26 | rootScene = screen.CreateScene("MainScene")
27 | rootScene.id = "ROOT"
28 |
29 | screen.show()
30 |
31 | SetupGlobals(screen)
32 |
33 | while(true)
34 | msg = wait(0, m.port)
35 | msgType = type(msg)
36 |
37 | if msgType = "roSGScreenEvent"
38 | if msg.isScreenClosed()
39 | return
40 | end if
41 | end if
42 | end while
43 | end function
44 |
45 |
46 | '*************************************************************
47 | '** SetupGlobals
48 | '** @param screen as roScreen - screen to set globals on
49 | '*************************************************************
50 | function SetupGlobals(screen) as void
51 | ? "SETTTING UP GLOBALS - do your standard setup stuff here"
52 |
53 | m.global = screen.getGlobalNode()
54 |
55 | m.roDeviceInfo = CreateObject("roDeviceInfo")
56 |
57 | m.displayInfo = {
58 | resolution: m.roDeviceInfo.GetUIResolution()
59 | displayType: m.roDeviceInfo.GetDisplayType()
60 | width: m.roDeviceInfo.GetDisplaySize().w
61 | height: m.roDeviceInfo.GetDisplaySize().h
62 | wFactor: m.roDeviceInfo.GetDisplaySize().w/1920
63 | hFactor: m.roDeviceInfo.GetDisplaySize().h/1080
64 | }
65 |
66 | m.modelLocator = {"displayInfo":m.displayInfo} ' contrived example : this would be a specifc modelLocator node/other setup thing
67 |
68 | m.global.addFields({"modelLocator": m.modelLocator})
69 | end function
70 |
--------------------------------------------------------------------------------
/test-project/source/tests/RequestsCacheTest.brs:
--------------------------------------------------------------------------------
1 | '@TestSuite [ATST] Requests Cache tests
2 |
3 | '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
4 | '@It Cache Sanity Checks
5 | '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
6 |
7 | '@Test Requests cache method miss
8 | '@Params["POST"]
9 | '@Params["PUT"]
10 | '@Params["PATCH"]
11 | '@Params["DELETE"]
12 | function Atst__Test_Requests_cache_post(method) as void
13 |
14 | m.cache = rr_Requests_cache(method, "", {})
15 | m.AssertInvalid(m.cache)
16 |
17 | end function
18 |
19 |
20 | '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
21 | '@It Cache File Manipulations
22 | '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
23 |
24 | '@BeforeEach
25 | function Atst__Test_Requests_cache_BeforeEach() as void
26 | m.cache = rr_Requests_cache("GET", "google.com", {"foo": "bar"})
27 | end function
28 |
29 | '@AfterEach
30 | function Atst__Test_Requests_cache_AfterEach() as void
31 | 'TODO: this is not ran
32 | m.cache.delete()
33 | end function
34 |
35 | '@Test Requests Cache check key and md5
36 | function Atst__Test_Requests_cache_key_md5() as void
37 |
38 | m.assertEqual(m.cache._cacheKey, "google.com{" + Chr(34) + "foo" + Chr(34) + ":" + Chr(34) + "bar" + Chr(34) + "}")
39 | m.assertEqual(m.cache._md5Key, "9445e3daf9d959fee3e82b72c1e1d7f3")
40 |
41 | end function
42 |
43 | '@Test Requests Cache write, read, delete happy path
44 | function Atst__Test_Requests_cache_put() as void
45 |
46 | m.assertTrue(m.cache.put({"test":"ing"}))
47 | m.AssertAAContainsSubset(m.cache.get(60), {"test":"ing"})
48 | m.assertTrue(m.cache.delete())
49 |
50 | end function
51 |
52 | '@Test Requests Cache read does not exits
53 | function Atst__Test_Requests_cache_does_not_exist() as void
54 |
55 | m.assertInvalid(m.cache.get(60))
56 | m.assertFalse(m.cache.exists())
57 |
58 | end function
59 |
60 | '@Test Requests Cache read exists but file is empty
61 | function Atst__Test_Requests_cache_file_empty() as void
62 |
63 | m.assertTrue(WriteAsciiFile(m.cache.location, ""))
64 | m.assertInvalid(m.cache.get(60))
65 | m.assertTrue(m.cache.delete())
66 |
67 | end function
68 |
69 | '@Test Requests Cache read file no breaks
70 | function Atst__Test_Requests_cache_file_no_breaks() as void
71 |
72 | m.assertTrue(WriteAsciiFile(m.cache.location, "bad file data"))
73 | m.assertInvalid(m.cache.get(60))
74 | m.assertTrue(m.cache.delete())
75 |
76 | end function
77 |
78 | '@Test Requests Cache read bad file data
79 | function Atst__Test_Requests_cache_file_three_breaks() as void
80 |
81 | m.assertTrue(WriteAsciiFile(m.cache.location, "bad file data" + chr(10) + chr(10)))
82 | m.assertInvalid(m.cache.get(60))
83 | m.assertTrue(m.cache.delete())
84 |
85 | end function
86 |
87 | '@Test Requests Cache read bad timestamp
88 | function Atst__Test_Requests_cache_file_bad_timestamp() as void
89 |
90 | m.assertTrue(WriteAsciiFile(m.cache.location, "not a timestamp" + chr(10) + "more bad data"))
91 | m.assertInvalid(m.cache.get(60))
92 | m.assertTrue(m.cache.delete())
93 |
94 | end function
95 |
96 | '@Test Requests Cache read bad json
97 | function Atst__Test_Requests_cache_file_bad_json() as void
98 | date = CreateObject("roDateTime")
99 | timestamp = date.AsSeconds()
100 | m.assertTrue(WriteAsciiFile(m.cache.location, stri(timestamp) + chr(10) + "not json data"))
101 | m.assertInvalid(m.cache.get(60))
102 | m.cache.delete()
103 |
104 | end function
105 |
106 | '@Test Requests Cache write, read with expired time
107 | function Atst__Test_Requests_cache_expired() as void
108 |
109 | m.assertTrue(m.cache.put({"test":"ing"}))
110 | m.assertInvalid(m.cache.get(-1))
111 | m.cache.delete()
112 |
113 | end function
114 |
115 | '@Test Requests Cache write, hit, sleep, miss
116 | function Atst__Test_Requests_cache_sleep() as void
117 |
118 | m.assertTrue(m.cache.put({"test":"ing"}))
119 | m.assertNotInvalid(m.cache.get(2))
120 | sleep(3000)
121 | m.assertInvalid(m.cache.get(2))
122 | m.cache.delete()
123 |
124 | end function
--------------------------------------------------------------------------------
/test-project/source/tests/RequestsQueryStringTests.brs:
--------------------------------------------------------------------------------
1 | '@TestSuite [ATST] Requests Query String tests
2 |
3 | '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
4 | '@It QueryString
5 | '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
6 |
7 | '@Test addString
8 | '@Params["test=123", [["test", "123"]]]
9 | '@Params["test=123&foo=bar", [["test", "123"], ["foo", "bar"]]]
10 | '@Params["test", [["test", ""]]]
11 | '@Params["=123", [["", "123"]]]
12 | '@Params["", []]
13 | function Atst__Test_Requests_QueryString_addString(addString, qs_array) as void
14 |
15 | qs = rr_Requests_queryString()
16 | qs.addString(addString)
17 | 'TODO: fix AssertArrayContainsOnly
18 | 'm.AssertArrayContainsOnly(qs._qs_array, "Array")
19 | m.EqArrays([qs._qs_array], qs_array)
20 |
21 | end function
22 |
23 | '@Test addParamKeyValue
24 | '@Params["test", "123", [["test", "123"]]]
25 | '@Params["test", "", [["test", ""]]]
26 | '@Params["", "123", [["", "123"]]]
27 | '@Params["", "", [["", ""]]]
28 | function Atst__Test_Requests_QueryString_addParamKeyValue(param, key, qs_array) as void
29 |
30 | qs = rr_Requests_queryString()
31 | qs.addParamKeyValue(param, key)
32 | 'TODO: fix AssertArrayContainsOnly
33 | 'm.AssertArrayContainsOnly(qs._qs_array, "Array")
34 | m.EqArrays([qs._qs_array], qs_array)
35 |
36 | end function
37 |
38 | '@Test addParamsAA
39 | '@Params[{"test": "123"}, [["test", "123"]]]
40 | '@Params[{"test": "123", "foo": "bar"}, [["test", "123"], ["foo", "bar"]]]
41 | '@Params[{"test": ""}, [["test", ""]]]
42 | '@Params[{"": "123"}, [["", "123"]]]
43 | '@Params[{"": ""}, [["", ""]]]
44 | function Atst__Test_Requests_QueryString_addParamsAA(params, qs_array) as void
45 |
46 | qs = rr_Requests_queryString()
47 | qs.addParamsAA(params)
48 | 'TODO: fix AssertArrayContainsOnly
49 | 'm.AssertArrayContainsOnly(qs._qs_array, "Array")
50 | m.EqArrays([qs._qs_array], qs_array)
51 |
52 | end function
53 |
54 | '@Test addParamsArray
55 | '@Params[[["test", "123"]], [["test", "123"]]]
56 | '@Params[[["test", "123"], ["foo", "bar"]], [["test", "123"], ["foo", "bar"]]]
57 | '@Params[[["test", ""]], [["test", ""]]]
58 | '@Params[[["", "123"]], [["", "123"]]]
59 | '@Params[[["", ""]], [["", ""]]]
60 | '@Params[[["test"]], [["test", ""]]]
61 | '@Params[[[""]], [["", ""]]]
62 | '@Params[[], []]
63 | function Atst__Test_Requests_QueryString_addParamsArray(params, qs_array) as void
64 |
65 | qs = rr_Requests_queryString()
66 | qs.addParamsArray(params)
67 | 'TODO: fix AssertArrayContainsOnly
68 | 'm.AssertArrayContainsOnly(qs._qs_array, "Array")
69 | m.EqArrays([qs._qs_array], qs_array)
70 |
71 | end function
72 |
73 | '@Test build
74 | function Atst__Test_Requests_QueryString_build() as void
75 |
76 | qs = rr_Requests_queryString()
77 | qs.addString("test=123&foo=bar")
78 | qs.addParamKeyValue("name", "jim")
79 | qs.addParamsAA({"forcast": "sunny"})
80 | qs.addParamsArray([["chocolate", "yummy"]])
81 | 'm.AssertArrayContainsOnly(qs._qs_array, "Array")
82 | built = qs.build()
83 | m.AssertEqual(built, "test=123&foo=bar&name=jim&forcast=sunny&chocolate=yummy")
84 |
85 | end function
86 |
87 | '@Test build
88 | function Atst__Test_Requests_QueryString_append() as void
89 |
90 | qs = rr_Requests_queryString()
91 | qs.addString("test=123&foo=bar")
92 | qs.addParamKeyValue("name", "jim")
93 | qs.addParamsAA({"forcast": "sunny"})
94 | qs.addParamsArray([["chocolate", "yummy"]])
95 | 'm.AssertArrayContainsOnly(qs._qs_array, "Array")
96 | append = qs.append("google.com")
97 | m.AssertEqual(append, "google.com?test=123&foo=bar&name=jim&forcast=sunny&chocolate=yummy")
98 |
99 | append = qs.append("google.com?")
100 | m.AssertEqual(append, "google.com?test=123&foo=bar&name=jim&forcast=sunny&chocolate=yummy")
101 |
102 | append = qs.append("google.com?id=555")
103 | m.AssertEqual(append, "google.com?id=555&test=123&foo=bar&name=jim&forcast=sunny&chocolate=yummy")
104 |
105 | end function
106 |
107 | '@Test build empty
108 | function Atst__Test_Requests_QueryString_append_empty() as void
109 |
110 | qs = rr_Requests_queryString()
111 | append = qs.append("google.com")
112 | m.AssertEqual(append, "google.com")
113 |
114 | end function
--------------------------------------------------------------------------------
/test-project/source/tests/RequestsTests.brs:
--------------------------------------------------------------------------------
1 | '@TestSuite [ATST] Requests tests
2 |
3 | '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
4 | '@It Some Basic Integration Testing
5 | '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
6 |
7 | '@Test URL Match
8 | '@Params["http://httpbin.org/get"]
9 | function Atst__url_match(url) as void
10 |
11 | response = rr_Requests().get(url)
12 | m.AssertEqual(response.url, url)
13 |
14 | end function
15 |
16 | '@Test Status Code Match
17 | '@Params["http://httpbin.org/status/200", 200]
18 | '@Params["http://httpbin.org/status/201", 201]
19 | '@Params["http://httpbin.org/status/204", 204]
20 | '@Params["http://httpbin.org/status/304", 304]
21 | '@Params["http://httpbin.org/status/400", 400]
22 | '@Params["http://httpbin.org/status/404", 404]
23 | function Atst__status_code_match(url, statusCode) as void
24 |
25 | response = rr_Requests().get(url, {retryCount:0})
26 | m.AssertEqual(response.statusCode, statusCode)
27 |
28 | end function
29 |
30 | '@Test Retry Count
31 | '@Params["http://httpbin.org/status/400", 0]
32 | '@Params["http://httpbin.org/status/404", 3]
33 | '@Params["http://httpbin.org/status/401", 4]
34 | function Atst__status_code_fail(url, retryCount) as void
35 |
36 | response = rr_Requests().get(url, {retryCount:retryCount})
37 | m.AssertEqual(response.timesTried, retryCount + 1)
38 |
39 | end function
40 |
41 | '@Test Negative Retry (don't get response)
42 | '@Params["http://httpbin.org/status/400", -1]
43 | function Atst__negative_retry(url, retryCount) as void
44 |
45 | response = rr_Requests().get(url, {retryCount:retryCount})
46 | m.AssertEqual(response.timesTried, retryCount + 1)
47 | m.AssertEqual(response.statusCode, invalid)
48 |
49 | end function
50 |
51 | '@Test JSON response
52 | '@Params["http://httpbin.org/json"]
53 | function Atst__json_response(url) as void
54 |
55 | response = rr_Requests().get(url)
56 | m.AssertNotInvalid(response.json)
57 | m.AssertAAHasKey(response.json, "slideshow")
58 |
59 | end function
60 |
61 | '@Test Query String
62 | '@Params["http://httpbin.org/get", {"a": "test", "this": "is"}, "http://httpbin.org/get?a=test&this=is"]
63 | function Atst__json_qs(url, params, finalUrl) as void
64 |
65 | response = rr_Requests().get(url, {"params": params})
66 | m.AssertNotInvalid(response.json)
67 | m.AssertAAContainsSubset(response.json.args, params)
68 | m.AssertEqual(response.url, finalUrl)
69 |
70 | end function
71 |
72 | '@Test Headers
73 | '@Params["http://httpbin.org/get", {"testHeaderKey": "testHeaderValue"}]
74 | function Atst__json_Headers(url, headers) as void
75 |
76 | response = rr_Requests().get(url, {"headers": headers})
77 | m.AssertNotInvalid(response.json)
78 | m.AssertAAContainsSubset(response.json.headers, params)
79 |
80 | end function
81 |
82 | '@Test Follows Redirects
83 | '@Params["http://httpbin.org/absolute-redirect/5"]
84 | function Atst___Redirects(url) as void
85 |
86 | response = rr_Requests().get(url)
87 | m.AssertNotInvalid(response.json)
88 | m.AssertEqual(response.json.url, "http://httpbin.org/get")
89 |
90 | end function
91 |
92 | '@Test POST form data
93 | function Atst__post_form_data() as void
94 |
95 | response = rr_Requests().post("http://httpbin.org/post", {"data": "test=data"})
96 | m.AssertNotInvalid(response.json)
97 | m.AssertAAContainsSubset(response.json.form, {"test": "data"})
98 |
99 | end function
100 |
101 | '@Test POST json data
102 | '@Params[{"test":1}]
103 | '@Params[{"test":1, "foo":"bar"}]
104 | function Atst__post_json_data(jsonData) as void
105 |
106 | response = rr_Requests().post("http://httpbin.org/post", {"json": jsonData})
107 | m.AssertNotInvalid(response.json)
108 | m.AssertAAContainsSubset(response.json.json, jsonData)
109 |
110 | end function
111 |
112 | '@Test Requests Cache write, read with Cache-Contol headers
113 | function Atst__Test_Requests_cache_read_headers() as void
114 | url = "http://httpbin.org/cache/100"
115 |
116 | cache = rr_Requests_cache("GET", url, {})
117 | cache.delete()
118 |
119 | response = rr_Requests().get(url, {useCache: true})
120 | m.AssertNotInvalid(response.json)
121 | m.AssertFalse(response.cacheHit)
122 |
123 | response = rr_Requests().get("http://httpbin.org/cache/100", {useCache: true})
124 | m.AssertNotInvalid(response.json)
125 | m.AssertTrue(response.cacheHit)
126 |
127 | end function
128 |
129 |
130 |
131 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # roku-requests
2 |
3 | Simple, python requests inspired Brightscript requests framework for Roku apps
4 |
5 | [](https://github.com/rokucommunity/roku-requests/actions/workflows/build.yml)
6 | [](https://npmcharts.com/compare/roku-requests?minimal=true)
7 | [](https://www.npmjs.com/package/roku-requests)
8 | [](LICENSE)
9 | [](https://join.slack.com/t/rokudevelopers/shared_invite/zt-4vw7rg6v-NH46oY7hTktpRIBM_zGvwA)
10 |
11 | ## Installation
12 |
13 | ### Using ropm
14 |
15 | ```bash
16 | ropm install roku-requests
17 | ```
18 |
19 | ### Manually
20 |
21 | Copy `src/source/Requests.brs` into your project as `source/Requests.brs` folder
22 |
23 | ## Usage
24 |
25 | ### Make a Request
26 |
27 | Making a request with Requests is very simple.
28 |
29 | ```
30 | Brightscript Debugger> r = Requests().get("https://api.github.com/events")
31 | ```
32 |
33 | Now, we have a Response object called r. We can get all the information we need from this object.
34 |
35 | ```
36 | Brightscript Debugger> ?r.ok
37 | Brightscript Debugger> true
38 | Brightscript Debugger> ?r.statuscode
39 | Brightscript Debugger> 200
40 | ```
41 |
42 | Requests’ simple API means that all forms of HTTP request are as obvious. For example, this is how you make an HTTP POST request:
43 |
44 | ```
45 | Brightscript Debugger> r = Requests().post("https://httpbin.org/post", {"data":"value"})
46 | ```
47 |
48 | What about the other HTTP request types: PUT, DELETE, HEAD and OPTIONS? These are all supported and simple by using the `.request(VERB...` method:
49 |
50 | ```
51 | Brightscript Debugger> r = Requests().request("PUT", "https://httpbin.org/put", {"key":"value"})
52 | Brightscript Debugger> r = Requests().request("DELETE", "https://httpbin.org/delete", {})
53 | Brightscript Debugger> r = Requests().request("HEAD", "https://httpbin.org/get", {})
54 | Brightscript Debugger> r = Requests().request("OPTIONS", "https://httpbin.org/get", {})
55 | ```
56 |
57 | ### Passing Parameters In URLs
58 |
59 | ```
60 | Brightscript Debugger> payload = {"key1": "value1", "key2": "value2"}
61 | Brightscript Debugger> r = Requests().get("https://httpbin.org/get", {"params":payload})
62 | ```
63 |
64 | You can see that the URL has been correctly encoded by printing the URL:
65 |
66 | ```
67 | Brightscript Debugger> ?r.url
68 | Brightscript Debugger> https://httpbin.org/get?key1=value1&key2=value2
69 | ```
70 |
71 | ### Response Content
72 |
73 | We can read the content of the server’s response. Consider the GitHub timeline again:
74 |
75 | ```
76 | Brightscript Debugger> r = Requests().get("https://api.github.com/events")`
77 | Brightscript Debugger> ?r.text
78 | Brightscript Debugger> [{"id":"8575373301","type":"WatchEvent","actor":{"id":4537355,"login":"...
79 | ```
80 |
81 | ### JSON Response Content
82 |
83 | There’s also a builtin JSON encoder/decoder, in case you’re dealing with JSON data:
84 |
85 | ```
86 | Brightscript Debugger> r = Requests().get("https://api.github.com/events")
87 | Brightscript Debugger> ?r.json
88 | Brightscript Debugger> =
89 | [
90 |
91 |
92 | ...
93 | ]
94 | ```
95 |
96 | You also also pass flags for json parsing. `parseJsonFlags` is passed to the [ParseJson()](https://developer.roku.com/en-ca/docs/references/brightscript/language/global-utility-functions.md#parsejsonjsonstring-as-string-flags---as-string-as-object) function.
97 |
98 | ```
99 | Brightscript Debugger> r = Requests().get("https://api.github.com/events", {parseJsonFlags:"i"})
100 | Brightscript Debugger> ?r.json
101 | ```
102 |
103 | Or disable json parsing
104 |
105 | ```
106 | Brightscript Debugger> r = Requests().get("https://api.github.com/events", {parseJson:false})
107 | Brightscript Debugger> ?r.json
108 | ```
109 |
110 | ### Custom Headers
111 |
112 | If you’d like to add HTTP headers to a request, simply pass in an `AA` to the `headers` key in the args dictionary.
113 |
114 | ```
115 | Brightscript Debugger> url =
116 | Brightscript Debugger> headers = {"user-agent": "my-app/0.0.1"}
117 | Brightscript Debugger> r = Requests().get(url, {"headers":headers})
118 | ```
119 |
120 | ### More complicated POST requests
121 |
122 | Instead of encoding the `AA` yourself, you can also pass it directly using the `json` parameter
123 |
124 | ```
125 | Brightscript Debugger> url = "https://httpbin.org/post"
126 | Brightscript Debugger> payload = {"some": "data"}
127 | Brightscript Debugger> r = Requests().post(url, {"json":payload})
128 | ```
129 |
130 | Using the `json` parameter in the request will change the `Content-Type` in the header to `application/json`.
131 |
132 | ### Response Status Codes
133 |
134 | ```
135 | Brightscript Debugger> r = Requests().get("https://httpbin.org/get")
136 | Brightscript Debugger> ?r.statuscode
137 | Brightscript Debugger> 200
138 | ```
139 |
140 | ### Response Headers
141 |
142 | We can view the server’s response headers using an AA:
143 |
144 | ```
145 | Brightscript Debugger> ?r.headers
146 | Brightscript Debugger> =
147 | {
148 | access-control-allow-credentials: "true"
149 | access-control-allow-origin: "*"
150 | connection: "keep-alive"
151 | content-length: "272"
152 | content-type: "application/json"
153 | date: "Mon, 12 Nov 2018 17:25:53 GMT"
154 | server: "gunicorn/19.9.0"
155 | via: "1.1 vegur"
156 | }
157 | ```
158 |
159 | ### Timeouts
160 |
161 | You can tell Requests to stop waiting for a response after a given number of seconds with the `timeout` parameter (int).
162 |
163 | ```
164 | Brightscript Debugger> r = Requests().get("https://httpbin.org/delay/10", {"timeout":1})
165 | Brightscript Debugger> =
166 | {
167 | cachehit: false
168 | ok: false
169 | timestried: 1
170 | url: "https://httpbin.org/delay/10"
171 | }
172 | ```
173 |
174 | ### Caching
175 |
176 | You can tell Requests to use cache (on by default) by passing the `useCache` parameter (boolean). This will automatically cache the request if there are `cache-control` headers in the response.
177 |
178 | ```
179 | Brightscript Debugger> r = Requests().get("https://httpbin.org/cache/60", {"useCache":true})
180 | ```
181 |
182 | You can see if the cache was hit by checking the `cacheHit` value on the Response object.
183 |
184 | ```
185 | Brightscript Debugger> r = Requests().get("https://httpbin.org/cache/60", {"useCache":true})
186 | Brightscript Debugger> ?r.cachehit
187 | Brightscript Debugger> false
188 | Brightscript Debugger> r = Requests().get("https://httpbin.org/cache/60", {"useCache":true})
189 | Brightscript Debugger> ?r.cachehit
190 | Brightscript Debugger> true
191 | ```
192 |
193 | If the server does not return `cache-control` headers or you want to manually specify the time to cache a request just pass the `cacheSeconds` parameter (int) to Requests.
194 |
195 | ```
196 | Brightscript Debugger> r = Requests().get("https://httpbin.org/get", {"useCache":true, "cacheSeconds":300})
197 | ```
198 |
199 | #### Notes about Cache implementation
200 |
201 | Roku's Cachefs:
202 |
203 | * The cache implementation uses Roku's `cachefs` (https://sdkdocs.roku.com/display/sdkdoc/File+System)
204 | * `cachefs` is available as a Beta feature starting in Roku OS 8.
205 | * `cachefs` exists across channel launches but will evict data when more space is required for another Channel.
206 |
207 | Cache Keys and Storage Location
208 |
209 | * Requests uses an MD5 hash of the URL + Request Headers being passed as the cache key
210 | * Requests stores the cached request as a file in `cachefs:/{MD5_HASH}`. Please be aware of this if your channel is storing things in the `cachefs:/` space as there is a very minute possiibility of name collisions.
211 | * The cache data is stored as a file with the first line as a unix epoch of the time the file was written (time the first request was made). Subsequient requests read the file and compute/compare timestamps to determine if the cached file is still valid or not.
212 |
213 | ## Development
214 |
215 | Roku Requests is an independent open-source project, maintained exclusively by volunteers.
216 |
217 | You might want to help! Get in touch via the slack group, or raise issues.
218 |
--------------------------------------------------------------------------------
/test-project/source/roku_modules/rr/Requests.cat.brs:
--------------------------------------------------------------------------------
1 | ' /**
2 | ' * @module requests
3 | ' */
4 |
5 | ' /**
6 | ' * @memberof module:requests
7 | ' * @name Requests
8 | ' * @function
9 | ' * @description Main Class for Requests
10 | ' * @param {Dynamic} args - Associative array of args to pass into Alacrity
11 |
12 | function rr_Requests() as Object
13 | return {
14 | request : rr_Requests_request
15 | get: rr_Requests_getRequest
16 | post: rr_Requests_postRequest
17 | }
18 | end function
19 |
20 | function rr_Requests_getRequest(url as String, args=invalid)
21 | return m.request("GET", url, args)
22 | end function
23 |
24 | function rr_Requests_postRequest(url as String, args=invalid)
25 | return m.request("POST", url, args)
26 | end function
27 |
28 | function rr_Requests_request(method, url as String, args as Object)
29 |
30 | _params = {}
31 | _headers = {}
32 | ' _cookies = invalid
33 | _data = invalid
34 | _json = invalid
35 | _timeout = 30000
36 | _retryCount = 0
37 | ' _allow_redirects = true
38 | _verify = "common:/certs/ca-bundle.crt"
39 | _useCache = true
40 | _cacheSeconds = invalid
41 |
42 | if args <> invalid and type(args) = "roAssociativeArray"
43 | if args.params <> invalid and type(args.params) = "roAssociativeArray"
44 | _params = args.params
45 | end if
46 | if args.headers <> invalid and type(args.headers) = "roAssociativeArray"
47 | _headers = args.headers
48 | end if
49 | if args.data <> invalid and (type(args.data) = "String" or type(args.data) = "roString")
50 | _data = args.data
51 | end if
52 | if args.json <> invalid and type(args.json) = "roAssociativeArray"
53 | _json = FormatJson(args.json)
54 | end if
55 | if args.timeout <> invalid and (type(args.timeout) = "Integer" or type(args.timeout) = "roInteger")
56 | _timeout = args.timeout
57 | end if
58 | if args.retryCount <> invalid and (type(args.retryCount) = "Integer" or type(args.retryCount) = "roInteger")
59 | _retryCount = args.retryCount
60 | end if
61 | if args.verify <> invalid and (type(args.verify) = "String" or type(args.verify) = "roString")
62 | _verify = args.verify
63 | end if
64 | if args.useCache <> invalid and (type(args.useCache) = "Boolean" or type(args.useCache) = "roBoolean")
65 | _useCache = args.useCache
66 | end if
67 | if args.cacheSeconds <> invalid and (type(args.cacheSeconds) = "Integer" or type(args.cacheSeconds) = "roInteger")
68 | _cacheSeconds = args.cacheSeconds
69 | end if
70 | end if
71 |
72 | ' Constructs and sends a Request.
73 | ' :param method: method for the new Request object.
74 | ' :param url: URL for the new Request object.
75 | ' :param params: (optional) AA to append as a query string to the Request.
76 | ' :param data: (optional) String to send in the body of the Request.
77 | ' :param json: (optional) A JSON serializable object to send in the body of the Request.
78 | ' :param headers: (optional) AA of HTTP Headers to send with the Request.
79 | ' :param cookies: (optional) Dict or CookieJar object to send with the Request.
80 | ' :param timeout: (optional) How many seconds to wait for the server to send data
81 | ' before giving up, as a float
82 | ' :param retryCount: (optional) How many times to retry a failed response.
83 | ' :param allow_redirects: (optional) Boolean. Enable/disable GET/OPTIONS/POST/PUT/PATCH/DELETE/HEAD redirection.
84 | ' :param verify: (optional) String to specify SSL cert bundle.
85 | ' If this is set to empty string `InitClientCertificates` is not called on roUrlTransfer.
86 | ' Defaults to `common:/certs/ca-bundle.crt`
87 | ' :param useCache: (optional) Boolean. Enable/Disable caching. Defaults to true
88 | ' :param cacheSeconds: (optional) Integer. Enable/Disable caching. Defaults to response's Cache-Control if it exists
89 | ' :return: :class:`Requests ` object
90 | ' Usage::
91 | ' req = Requests().get('http://httpbin.org/get')
92 |
93 |
94 | requestHeaders = rr_Requests_headers()
95 | requestHeaders.addHeadersAA(_headers)
96 |
97 | requestQueryString = rr_Requests_queryString()
98 | requestQueryString.addParamsAA(_params)
99 |
100 | ' Setup the data (we overwrite JSON if it's provided)
101 | if _data <> invalid
102 | data = _data
103 | else if _json <> invalid
104 | data = _json
105 | requestHeaders.addHeader("Content-Type", "application/json")
106 | else
107 | data = ""
108 | end if
109 |
110 | 'TODO: Add Cookies Support
111 | 'urlTransfer.EnableCookies()
112 | 'urlTransfer.GetCookies(domain, path)
113 | 'urlTransfer.AddCookies(cookies)
114 | 'urlTransfer.ClearCookies()
115 |
116 | url = requestQueryString.append(url)
117 | headers = requestHeaders._headers
118 |
119 | rc = rr_Requests_cache(method, url, headers)
120 |
121 | response = invalid
122 | if rc <> invalid and _useCache
123 | response = rc.get(_cacheSeconds)
124 | end if
125 |
126 | if response = invalid
127 | response = rr_Requests_run(method, url, headers, data, _timeout, _retryCount, _verify)
128 | if rc <> invalid and _useCache
129 | rc.put(response)
130 | end if
131 | end if
132 |
133 | return response
134 |
135 | end function
136 |
137 | function rr_Requests_run(method, url, headers, data, timeout, retryCount, verify)
138 |
139 | urlTransfer = rr_RequestsUrlTransfer(true, true, verify)
140 | urlTransfer.setUrl(url)
141 | urlTransfer.SetHeaders(headers)
142 |
143 | ? "[http] ------ START HTTP REQUEST ------"
144 |
145 | ? "[http] URL:", urlTransfer.GetURL()
146 |
147 | ? "[http] Timeout= ", timeout
148 |
149 | ? "[http] Headers: ", headers
150 |
151 | cancel_and_return = false
152 |
153 | responseEvent = invalid
154 | requestDetails = {
155 | timesTried : 0,
156 | }
157 | 'while we still have try times
158 | while retryCount >= 0
159 |
160 | 'deincrement the number of retries
161 | retryCount = retryCount - 1
162 | requestDetails.timesTried = requestDetails.timesTried + 1
163 |
164 | ? "[http] Method: ", method
165 | if method="POST"
166 | sent = urlTransfer.AsyncPostFromString(data)
167 | else if method = "GET"
168 | sent = urlTransfer.AsyncGetToString()
169 | else if method = "HEAD"
170 | sent = urlTransfer.AsyncHead()
171 | else
172 | 'PUT, PATCH, DELETE
173 | urlTransfer.SetRequest(method)
174 | sent = urlTransfer.AsyncPostFromString(data)
175 | end if
176 |
177 | if sent = true
178 | clock = CreateObject("roTimespan")
179 | timeout_call = clock.TotalMilliseconds() + timeout
180 |
181 | while true and cancel_and_return = false
182 |
183 | if m.top <> invalid
184 | if m.top.quit <> invalid
185 | cancel_and_return = m.top.quit
186 | end if
187 | end if
188 |
189 | event = urlTransfer.GetPort().GetMessage()
190 |
191 | if type(event) = "roUrlEvent"
192 | exit while
193 | end if
194 |
195 | if clock.TotalMilliseconds() > timeout_call
196 | exit while
197 | end if
198 | end while
199 |
200 | if type(event) = "roUrlEvent"
201 | responseEvent = event
202 | responseCode = event.GetResponseCode()
203 | ? "[http] Response Code", responseCode
204 | if responseCode > 0 and responseCode < 400
205 | 'Response was good, so we break the while
206 | exit while
207 | else
208 | 'We have a bad response
209 | ? "[http] Bad response", responseCode
210 | ? "[http] Will Retry ", retryCount
211 | end if
212 | else
213 | if m.cancel_and_return = true
214 | ? "[http] Killing the Task"
215 | exit while
216 | else
217 | 'We timed out so we should cancel the request
218 | ? "[http] Event Timed Out"
219 | urlTransfer.AsyncCancel()
220 | 'Exponential backoff timeouts
221 | timeout = timeout * 2
222 | ? "[http] Timeout=", timeout
223 | end if
224 | end if
225 | end if
226 | end while
227 | ? "[http] ------ END HTTP REQUEST ------"
228 |
229 | return rr_Requests_response(urlTransfer, responseEvent, requestDetails)
230 |
231 | end function
232 |
233 |
234 |
235 | function rr_Requests_cache(method as String, url as String, headers as Object)
236 |
237 | if method <> "GET"
238 | return invalid
239 | end if
240 |
241 | cacheKey = url
242 | headersString = FormatJson(headers)
243 | if headersString <> invalid
244 | cacheKey = cacheKey + headersString
245 | end if
246 |
247 | ba = CreateObject("roByteArray")
248 | ba.FromAsciiString(cacheKey)
249 | digest = CreateObject("roEVPDigest")
250 | digest.Setup("md5")
251 | md5Key = digest.Process(ba)
252 |
253 | fileLocation = "cachefs:/" + md5Key
254 |
255 | return {
256 | _cacheKey: cacheKey
257 | _md5Key: md5Key
258 |
259 | location: fileLocation
260 |
261 | exists: function() as Boolean
262 | fs = CreateObject("roFileSystem")
263 | return fs.Exists(m.location)
264 | end function
265 |
266 | get: function(expireSeconds) as Object
267 | if m.exists()
268 | fileData = ReadAsciiFile(m.location)
269 | if fileData <> ""
270 | dataSplit = fileData.Split(chr(10))
271 | if dataSplit.count() = 2
272 | fileTimeastamp = dataSplit[0].toInt()
273 | date = CreateObject("roDateTime")
274 | nowTimestamp = date.AsSeconds()
275 | response = ParseJson(dataSplit[1])
276 | if response <> invalid
277 | if expireSeconds = invalid and response.headers <> invalid
278 | cacheControl = response.headers["cache-control"]
279 | if cacheControl <> invalid
280 | cacheControlSplit = cacheControl.split(",")
281 | if cacheControlSplit.count() > 1
282 | cacheControlMaxAgeSplit = cacheControlSplit[1].split("=")
283 | if cacheControlMaxAgeSplit.count() > 1
284 | expireSeconds = val(cacheControlMaxAgeSplit[1])
285 | end if
286 | end if
287 | end if
288 | end if
289 | if expireSeconds <> invalid
290 | if fileTimeastamp + expireSeconds >= nowTimestamp
291 | response.cacheHit = true
292 | return response
293 | end if
294 | end if
295 | end if
296 | end if
297 | end if
298 | end if
299 | return invalid
300 | end function
301 |
302 | put: function(response) as Boolean
303 | date = CreateObject("roDateTime")
304 | timestamp = date.AsSeconds()
305 | putString = stri(timestamp) + chr(10)
306 | jsonResponse = FormatJson(response)
307 |
308 | if jsonResponse <> invalid
309 | putString = putString + jsonResponse
310 | return WriteAsciiFile(m.location, putString)
311 | end if
312 | return false
313 | end function
314 |
315 | delete: function() as Boolean
316 | fs = CreateObject("roFileSystem")
317 | return fs.Delete(m.location)
318 | end function
319 |
320 | }
321 |
322 | end function
323 |
324 | function rr_Requests_headers()
325 |
326 | return {
327 | _headers: {}
328 | addHeader: function(key as String, value as String)
329 | m._headers[key] = value
330 | end function
331 |
332 | addHeadersAA: function(headers as Object)
333 | m._headers.Append(headers)
334 | end function
335 |
336 | build: m.get
337 | get: m._headers
338 | }
339 |
340 | end function
341 |
342 | function rr_Requests_queryString()
343 |
344 | return {
345 | _urlTransfer: CreateObject("roUrlTransfer")
346 | _qs_array: []
347 | addString: function(params as String)
348 | if rr_Requests_Utils_inString("&", params)
349 | split_params = params.split("&")
350 | for each param in split_params
351 | if rr_Requests_Utils_inString("=", param)
352 | split_param = param.split("=")
353 | m.addParamKeyValue(split_param[0], split_param[1])
354 | else
355 | m.addParamKeyValue(param, "")
356 | end if
357 | end for
358 | else if rr_Requests_Utils_inString("=", params)
359 | split_params = params.split("=")
360 | m.addParamKeyValue(split_params[0], split_params[1])
361 | else
362 | m.addParamKeyValue(params, "")
363 | end if
364 | end function
365 | addParamKeyValue: function(param as String, key as String)
366 | m._qs_array.push([param, key])
367 | end function
368 | addParamsAA: function(params as Object)
369 | for each item in params.Items()
370 | m.addParamKeyValue(item.key, item.value)
371 | end for
372 | end function
373 | addParamsArray: function(params as Object)
374 | if params.Count() > 0
375 | for each item in params
376 | if item.Count() > 1
377 | m.addParamKeyValue(item[0], item[1])
378 | else if item.Count() > 0
379 | m.addParamKeyValue(item[0], "")
380 | end if
381 | end for
382 | end if
383 | end function
384 |
385 | build: function() as String
386 | ' "build the QS output from the added params"
387 | output = ""
388 | c = 0
389 | for each qs in m._qs_array
390 | if c = 0
391 | output = qs[0] + "=" + m._urlTransfer.Escape(qs[1])
392 | else
393 | output = output + "&" + qs[0] + "=" + m._urlTransfer.Escape(qs[1])
394 | end if
395 | c += 1
396 | end for
397 | return output
398 | end function
399 |
400 | append: function(url as String) as String
401 | ' "append the QS on a provided URL"
402 | if m._qs_array.Count() > 0
403 | if rr_Requests_Utils_inString("?", url)
404 | if url.right(1) = "?"
405 | return url + m.build()
406 | else
407 | return url + "&" + m.build()
408 | end if
409 | else
410 | return url + "?" + m.build()
411 | end if
412 | end if
413 | return url
414 | end function
415 | }
416 |
417 | end function
418 |
419 | function rr_Requests_response(urlTransfer as Object, responseEvent as Object, requestDetails as Object)
420 |
421 | rr = {}
422 |
423 | rr.timesTried = requestDetails.timesTried
424 | rr.url = urlTransfer.GetUrl()
425 | rr.ok = false
426 | rr.cacheHit = false
427 |
428 | if responseEvent <> invalid
429 | rr.statusCode = responseEvent.GetResponseCode()
430 | rr.text = responseEvent.GetString()
431 | rr.headers = responseEvent.GetResponseHeaders()
432 | rr.headersArray = responseEvent.GetResponseHeadersArray()
433 |
434 | rr.GetSourceIdentity = responseEvent.GetSourceIdentity()
435 | rr.GetFailureReason = responseEvent.GetFailureReason()
436 | rr.target_ip = responseEvent.GetTargetIpAddress()
437 | if rr.statusCode > 0 and rr.statusCode < 400
438 | rr.ok = true
439 | end if
440 | end if
441 |
442 |
443 |
444 | if rr.text <> invalid
445 | rr.json = parseJson(rr.text)
446 | rr.body = rr.text
447 | end if
448 |
449 | return rr
450 |
451 | end function
452 |
453 |
454 | function rr_RequestsUrlTransfer(EnableEncodings as Boolean, retainBodyOnError as Boolean, verify as String)
455 |
456 | _urlTransfer = CreateObject("roUrlTransfer")
457 | _urlTransfer.SetPort(CreateObject("roMessagePort"))
458 |
459 | _urlTransfer.EnableEncodings(enableEncodings)
460 | _urlTransfer.RetainBodyOnError(retainBodyOnError)
461 | if verify <> ""
462 | _urlTransfer.SetCertificatesFile(verify)
463 | _urlTransfer.InitClientCertificates()
464 | end if
465 |
466 | return _urlTransfer
467 |
468 | end function
469 | function rr_Requests_Utils_inString(char as String, strValue as String)
470 |
471 | for each single_char in strValue.split("")
472 | if single_char = char
473 | return true
474 | end if
475 | end for
476 |
477 | return false
478 |
479 | end function
--------------------------------------------------------------------------------
/src/source/Requests.brs:
--------------------------------------------------------------------------------
1 | ' /**
2 | ' * @module requests
3 | ' */
4 |
5 | ' /**
6 | ' * @memberof module:requests
7 | ' * @name Requests
8 | ' * @function
9 | ' * @description Main Class for Requests
10 | ' * @param {Dynamic} args - Associative array of args to pass into Alacrity
11 |
12 | function Requests() as Object
13 | return {
14 | request : Requests_request,
15 | get: Requests_getRequest,
16 | post: Requests_postRequest
17 | }
18 | end function
19 |
20 | function Requests_getRequest(url as String, args=invalid)
21 | return m.request("GET", url, args)
22 | end function
23 |
24 | function Requests_postRequest(url as String, args=invalid)
25 | return m.request("POST", url, args)
26 | end function
27 |
28 | function Requests_request(method, url as String, args as Object)
29 |
30 | _params = {}
31 | _headers = {}
32 | ' _cookies = invalid
33 | _data = invalid
34 | _json = invalid
35 | _timeout = 30000
36 | _retryCount = 0
37 | ' _allow_redirects = true
38 | _verify = "common:/certs/ca-bundle.crt"
39 | _useCache = true
40 | _cacheSeconds = invalid
41 | _parseJson = true
42 | _parseJsonFlags = ""
43 |
44 | if args <> invalid and type(args) = "roAssociativeArray"
45 | if args.params <> invalid and type(args.params) = "roAssociativeArray"
46 | _params = args.params
47 | end if
48 | if args.headers <> invalid and type(args.headers) = "roAssociativeArray"
49 | _headers = args.headers
50 | end if
51 | if args.data <> invalid and (type(args.data) = "String" or type(args.data) = "roString")
52 | _data = args.data
53 | end if
54 | if args.json <> invalid and type(args.json) = "roAssociativeArray"
55 | _json = FormatJson(args.json)
56 | end if
57 | if args.timeout <> invalid and (type(args.timeout) = "Integer" or type(args.timeout) = "roInteger")
58 | _timeout = args.timeout
59 | end if
60 | if args.retryCount <> invalid and (type(args.retryCount) = "Integer" or type(args.retryCount) = "roInteger")
61 | _retryCount = args.retryCount
62 | end if
63 | if args.verify <> invalid and (type(args.verify) = "String" or type(args.verify) = "roString")
64 | _verify = args.verify
65 | end if
66 | if args.useCache <> invalid and (type(args.useCache) = "Boolean" or type(args.useCache) = "roBoolean")
67 | _useCache = args.useCache
68 | end if
69 | if args.cacheSeconds <> invalid and (type(args.cacheSeconds) = "Integer" or type(args.cacheSeconds) = "roInteger")
70 | _cacheSeconds = args.cacheSeconds
71 | end if
72 | if args.parseJson <> invalid and (type(args.parseJson) = "Boolean" or type(args.parseJson) = "roBoolean")
73 | _parseJson = args.parseJson
74 | end if
75 | if args.parseJsonFlags <> invalid and (type(args.parseJsonFlags) = "String" or type(args.parseJsonFlags) = "roString")
76 | _parseJsonFlags = args.parseJsonFlags
77 | end if
78 | end if
79 |
80 | ' Constructs and sends a Request.
81 | ' :param method: method for the new Request object.
82 | ' :param url: URL for the new Request object.
83 | ' :param params: (optional) AA to append as a query string to the Request.
84 | ' :param data: (optional) String to send in the body of the Request.
85 | ' :param json: (optional) A JSON serializable object to send in the body of the Request.
86 | ' :param headers: (optional) AA of HTTP Headers to send with the Request.
87 | ' :param cookies: (optional) Dict or CookieJar object to send with the Request.
88 | ' :param timeout: (optional) How many seconds to wait for the server to send data
89 | ' before giving up, as a float
90 | ' :param retryCount: (optional) How many times to retry a failed response.
91 | ' :param allow_redirects: (optional) Boolean. Enable/disable GET/OPTIONS/POST/PUT/PATCH/DELETE/HEAD redirection.
92 | ' :param verify: (optional) String to specify SSL cert bundle.
93 | ' If this is set to empty string `InitClientCertificates` is not called on roUrlTransfer.
94 | ' Defaults to `common:/certs/ca-bundle.crt`
95 | ' :param useCache: (optional) Boolean. Enable/Disable caching. Defaults to true
96 | ' :param cacheSeconds: (optional) Integer. Enable/Disable caching. Defaults to response's Cache-Control if it exists
97 | ' :param parseJson: (optional) Boolean. Enable/Disable parsing json response. Defaults to true
98 | ' :param parseJsonFlags: (optional) String. Flags passed to ParseJson() function
99 | ' :return: :class:`Requests ` object
100 | ' Usage::
101 | ' req = Requests().get('http://httpbin.org/get')
102 |
103 |
104 | requestHeaders = Requests_headers()
105 | requestHeaders.addHeadersAA(_headers)
106 |
107 | requestQueryString = Requests_queryString()
108 | requestQueryString.addParamsAA(_params)
109 |
110 | ' Setup the data (we overwrite JSON if it's provided)
111 | if _data <> invalid
112 | data = _data
113 | else if _json <> invalid
114 | data = _json
115 | requestHeaders.addHeader("Content-Type", "application/json")
116 | else
117 | data = ""
118 | end if
119 |
120 | 'TODO: Add Cookies Support
121 | 'urlTransfer.EnableCookies()
122 | 'urlTransfer.GetCookies(domain, path)
123 | 'urlTransfer.AddCookies(cookies)
124 | 'urlTransfer.ClearCookies()
125 |
126 | url = requestQueryString.append(url)
127 | headers = requestHeaders._headers
128 |
129 | rc = Requests_cache(method, url, headers)
130 |
131 | response = invalid
132 | if rc <> invalid and _useCache
133 | response = rc.get(_cacheSeconds)
134 | end if
135 |
136 | if response = invalid
137 | response = Requests_run(method, url, headers, data, _timeout, _retryCount, _verify, _parseJson, _parseJsonFlags)
138 | if rc <> invalid and _useCache and response.ok = true
139 | rc.put(response)
140 | end if
141 | end if
142 |
143 | return response
144 |
145 | end function
146 |
147 | function Requests_run(method, url, headers, data, timeout, retryCount, verify, parseJson, parseJsonFlags)
148 |
149 | urlTransfer = RequestsUrlTransfer(true, true, verify)
150 | urlTransfer.setUrl(url)
151 | urlTransfer.SetHeaders(headers)
152 |
153 | ? "[http] ------ START HTTP REQUEST ------"
154 |
155 | ? "[http] URL:", urlTransfer.GetURL()
156 |
157 | ? "[http] Timeout= ", timeout
158 |
159 | ? "[http] Headers: ", headers
160 |
161 | cancel_and_return = false
162 |
163 | responseEvent = invalid
164 | requestDetails = {
165 | timesTried : 0,
166 | parseJson : parseJson,
167 | parseJsonFlags: parseJsonFlags
168 | }
169 | 'while we still have try times
170 | while retryCount >= 0
171 |
172 | 'deincrement the number of retries
173 | retryCount = retryCount - 1
174 | requestDetails.timesTried = requestDetails.timesTried + 1
175 |
176 | sent = invalid
177 |
178 | ? "[http] Method: ", method
179 | if method="POST"
180 | sent = urlTransfer.AsyncPostFromString(data)
181 | else if method = "GET"
182 | sent = urlTransfer.AsyncGetToString()
183 | else if method = "HEAD"
184 | sent = urlTransfer.AsyncHead()
185 | else
186 | 'PUT, PATCH, DELETE
187 | urlTransfer.SetRequest(method)
188 | sent = urlTransfer.AsyncPostFromString(data)
189 | end if
190 |
191 | if sent = true
192 | clock = CreateObject("roTimespan")
193 | timeout_call = clock.TotalMilliseconds() + timeout
194 |
195 | event = invalid
196 |
197 | while true and cancel_and_return = false
198 |
199 | if m.top <> invalid
200 | if m.top.quit <> invalid
201 | cancel_and_return = m.top.quit
202 | end if
203 | end if
204 |
205 | event = urlTransfer.GetPort().GetMessage()
206 |
207 | if type(event) = "roUrlEvent"
208 | exit while
209 | end if
210 |
211 | if clock.TotalMilliseconds() > timeout_call
212 | exit while
213 | end if
214 | end while
215 |
216 | if type(event) = "roUrlEvent"
217 | responseEvent = event
218 | responseCode = event.GetResponseCode()
219 | ? "[http] Response Code", responseCode
220 | if responseCode > 0 and responseCode < 400
221 | 'Response was good, so we break the while
222 | exit while
223 | else
224 | 'We have a bad response
225 | ? "[http] Bad response", responseCode
226 | ? "[http] Will Retry ", retryCount
227 | end if
228 | else
229 | if m.cancel_and_return = true
230 | ? "[http] Killing the Task"
231 | exit while
232 | else
233 | 'We timed out so we should cancel the request
234 | ? "[http] Event Timed Out"
235 | urlTransfer.AsyncCancel()
236 | 'Exponential backoff timeouts
237 | timeout = timeout * 2
238 | ? "[http] Timeout=", timeout
239 | end if
240 | end if
241 | end if
242 | end while
243 | ? "[http] ------ END HTTP REQUEST ------"
244 |
245 | return Requests_response(urlTransfer, responseEvent, requestDetails)
246 |
247 | end function
248 |
249 |
250 |
251 | function Requests_cache(method as String, url as String, headers as Object)
252 |
253 | if method <> "GET"
254 | return invalid
255 | end if
256 |
257 | cacheKey = url
258 | headersString = FormatJson(headers)
259 | if headersString <> invalid
260 | cacheKey = cacheKey + headersString
261 | end if
262 |
263 | ba = CreateObject("roByteArray")
264 | ba.FromAsciiString(cacheKey)
265 | digest = CreateObject("roEVPDigest")
266 | digest.Setup("md5")
267 | md5Key = digest.Process(ba)
268 |
269 | fileLocation = "cachefs:/" + md5Key
270 |
271 | return {
272 | _cacheKey: cacheKey,
273 | _md5Key: md5Key,
274 |
275 | location: fileLocation,
276 |
277 | exists: function() as Boolean
278 | fs = CreateObject("roFileSystem")
279 | if fs <> invalid
280 | return fs.Exists(m.location)
281 | end if
282 | return false
283 | end function,
284 |
285 | get: function(expireSeconds) as Object
286 | if m.exists()
287 | fileData = ReadAsciiFile(m.location)
288 | if fileData <> ""
289 | dataSplit = fileData.Split(chr(10))
290 | if dataSplit.count() = 2
291 | fileTimeastamp = dataSplit[0].toInt()
292 | date = CreateObject("roDateTime")
293 | nowTimestamp = date.AsSeconds()
294 | response = ParseJson(dataSplit[1])
295 | if response <> invalid
296 | if expireSeconds = invalid and response.headers <> invalid
297 | cacheControl = response.headers["cache-control"]
298 | if cacheControl <> invalid
299 | cacheControlSplit = cacheControl.split(",")
300 | if cacheControlSplit.count() > 1
301 | cacheControlMaxAgeSplit = cacheControlSplit[1].split("=")
302 | if cacheControlMaxAgeSplit.count() > 1
303 | expireSeconds = val(cacheControlMaxAgeSplit[1])
304 | end if
305 | end if
306 | end if
307 | end if
308 | if expireSeconds <> invalid
309 | if fileTimeastamp + expireSeconds >= nowTimestamp
310 | response.cacheHit = true
311 | return response
312 | end if
313 | end if
314 | end if
315 | end if
316 | end if
317 | end if
318 | return invalid
319 | end function,
320 |
321 | put: function(response) as Boolean
322 | date = CreateObject("roDateTime")
323 | timestamp = date.AsSeconds()
324 | putString = stri(timestamp) + chr(10)
325 | jsonResponse = FormatJson(response)
326 |
327 | if jsonResponse <> invalid
328 | putString = putString + jsonResponse
329 | return WriteAsciiFile(m.location, putString)
330 | end if
331 | return false
332 | end function,
333 |
334 | delete: function() as Boolean
335 | fs = CreateObject("roFileSystem")
336 | if fs <> invalid
337 | return fs.Delete(m.location)
338 | end if
339 | return false
340 | end function
341 |
342 | }
343 |
344 | end function
345 |
346 | function Requests_headers()
347 |
348 | return {
349 | _headers: {},
350 | addHeader: sub(key as String, value as String)
351 | m._headers[key] = value
352 | end sub,
353 |
354 | addHeadersAA: sub(headers as Object)
355 | m._headers.Append(headers)
356 | end sub,
357 |
358 | build: m.get,
359 | get: m._headers
360 | }
361 |
362 | end function
363 |
364 | function Requests_queryString()
365 |
366 | return {
367 | _urlTransfer: CreateObject("roUrlTransfer"),
368 | _qs_array: [],
369 | addString: sub(params as String)
370 | if Requests_Utils_inString("&", params)
371 | split_params = params.split("&")
372 | for each param in split_params
373 | if Requests_Utils_inString("=", param)
374 | split_param = param.split("=")
375 | m.addParamKeyValue(split_param[0], split_param[1])
376 | else
377 | m.addParamKeyValue(param, "")
378 | end if
379 | end for
380 | else if Requests_Utils_inString("=", params)
381 | split_params = params.split("=")
382 | m.addParamKeyValue(split_params[0], split_params[1])
383 | else
384 | m.addParamKeyValue(params, "")
385 | end if
386 | end sub,
387 | addParamKeyValue: sub(param as String, key as String)
388 | m._qs_array.push([param, key])
389 | end sub,
390 | addParamsAA: sub(params as Object)
391 | for each item in params.Items()
392 | m.addParamKeyValue(item.key, item.value)
393 | end for
394 | end sub,
395 | addParamsArray: sub(params as Object)
396 | if params.Count() > 0
397 | for each item in params
398 | if item.Count() > 1
399 | m.addParamKeyValue(item[0], item[1])
400 | else if item.Count() > 0
401 | m.addParamKeyValue(item[0], "")
402 | end if
403 | end for
404 | end if
405 | end sub,
406 |
407 | build: function() as String
408 | ' "build the QS output from the added params"
409 | output = ""
410 | c = 0
411 | for each qs in m._qs_array
412 | if c = 0
413 | output = qs[0] + "=" + m._urlTransfer.Escape(qs[1])
414 | else
415 | output = output + "&" + qs[0] + "=" + m._urlTransfer.Escape(qs[1])
416 | end if
417 | c += 1
418 | end for
419 | return output
420 | end function,
421 |
422 | append: function(url as String) as String
423 | ' "append the QS on a provided URL"
424 | if m._qs_array.Count() > 0
425 | if Requests_Utils_inString("?", url)
426 | if url.right(1) = "?"
427 | return url + m.build()
428 | else
429 | return url + "&" + m.build()
430 | end if
431 | else
432 | return url + "?" + m.build()
433 | end if
434 | end if
435 | return url
436 | end function
437 | }
438 |
439 | end function
440 |
441 | function Requests_response(urlTransfer as Object, responseEvent as Object, requestDetails as Object)
442 |
443 | rr = {}
444 |
445 | rr.timesTried = requestDetails.timesTried
446 | rr.url = urlTransfer.GetUrl()
447 | rr.ok = false
448 | rr.cacheHit = false
449 |
450 | if responseEvent <> invalid
451 | rr.statusCode = responseEvent.GetResponseCode()
452 | rr.text = responseEvent.GetString()
453 | rr.headers = responseEvent.GetResponseHeaders()
454 | rr.headersArray = responseEvent.GetResponseHeadersArray()
455 |
456 | rr.GetSourceIdentity = responseEvent.GetSourceIdentity()
457 | rr.GetFailureReason = responseEvent.GetFailureReason()
458 | rr.target_ip = responseEvent.GetTargetIpAddress()
459 | if rr.statusCode > 0 and rr.statusCode < 400
460 | rr.ok = true
461 | end if
462 | end if
463 |
464 | if rr.text <> invalid
465 | if requestDetails.parseJson = true
466 | rr.json = parseJson(rr.text, requestDetails.parseJsonFlags)
467 | end if
468 | rr.body = rr.text
469 | end if
470 |
471 | return rr
472 |
473 | end function
474 |
475 |
476 | function RequestsUrlTransfer(enableEncodings as Boolean, retainBodyOnError as Boolean, verify as String)
477 |
478 | _urlTransfer = CreateObject("roUrlTransfer")
479 | _urlTransfer.SetPort(CreateObject("roMessagePort"))
480 |
481 | _urlTransfer.EnableEncodings(enableEncodings)
482 | _urlTransfer.RetainBodyOnError(retainBodyOnError)
483 | if verify <> ""
484 | _urlTransfer.SetCertificatesFile(verify)
485 | _urlTransfer.InitClientCertificates()
486 | end if
487 |
488 | return _urlTransfer
489 |
490 | end function
491 | function Requests_Utils_inString(char as String, strValue as String)
492 |
493 | for each single_char in strValue.split("")
494 | if single_char = char
495 | return true
496 | end if
497 | end for
498 |
499 | return false
500 |
501 | end function
502 |
--------------------------------------------------------------------------------
/test-project/source/rooibos.cat.brs:
--------------------------------------------------------------------------------
1 | ' VERSION: Rooibos 0.1.0
2 | ' LICENSE: MIT License
3 | ' LICENSE:
4 | ' LICENSE: Copyright (c) 2018
5 | ' LICENSE:
6 | ' LICENSE: Permission is hereby granted, free of charge, to any person obtaining a copy
7 | ' LICENSE: of this software and associated documentation files (the "Software"), to deal
8 | ' LICENSE: in the Software without restriction, including without limitation the rights
9 | ' LICENSE: to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | ' LICENSE: copies of the Software, and to permit persons to whom the Software is
11 | ' LICENSE: furnished to do so, subject to the following conditions:
12 | ' LICENSE:
13 | ' LICENSE: The above copyright notice and this permission notice shall be included in all
14 | ' LICENSE: copies or substantial portions of the Software.
15 | ' LICENSE:
16 | ' LICENSE: THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | ' LICENSE: IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | ' LICENSE: FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | ' LICENSE: AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | ' LICENSE: LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | ' LICENSE: OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | ' LICENSE: SOFTWARE.
23 | function Rooibos__Init(args, preTestSetup = invalid, testUtilsDecoratorMethodName = invalid, testSceneName = "TestsScene") as void
24 | if args.RunTests = invalid or args.RunTests <> "true" then
25 | return
26 | end if
27 | args.testUtilsDecoratorMethodName = testUtilsDecoratorMethodName
28 | screen = CreateObject("roSGScreen")
29 | m.port = CreateObject("roMessagePort")
30 | screen.setMessagePort(m.port)
31 | ? "Starting test using test scene with name TestsScene" ; testSceneName
32 | scene = screen.CreateScene(testSceneName)
33 | scene.id = "ROOT"
34 | screen.show()
35 | if (preTestSetup <> invalid)
36 | preTestSetup(screen)
37 | end if
38 | m.global = screen.getGlobalNode()
39 | testId = args.TestId
40 | if (testId = invalid)
41 | testId = "UNDEFINED_TEST_ID"
42 | end if
43 | ? "#########################################################################"
44 | ? "#TEST START : ###" ; testId ; "###"
45 | args.testScene = scene
46 | runner = RBS_TR_TestRunner(args)
47 | runner.Run()
48 | while(true)
49 | msg = wait(0, m.port)
50 | msgType = type(msg)
51 | if msgType = "roSGScreenEvent"
52 | if msg.isScreenClosed()
53 | return
54 | end if
55 | end if
56 | end while
57 | end function
58 | function BaseTestSuite() as Object
59 | this = {}
60 | this.Name = "BaseTestSuite"
61 | this.TestCases = []
62 | this.AddTest = RBS_BTS_AddTest
63 | this.CreateTest = RBS_BTS_CreateTest
64 | this.GetLegacyCompatibleReturnValue = RBS_BTS_GetLegacyCompatibleReturnValue
65 | this.Fail = RBS_BTS_Fail
66 | this.AssertFalse = RBS_BTS_AssertFalse
67 | this.AssertTrue = RBS_BTS_AssertTrue
68 | this.AssertEqual = RBS_BTS_AssertEqual
69 | this.AssertLike = RBS_BTS_AssertLike
70 | this.AssertNotEqual = RBS_BTS_AssertNotEqual
71 | this.AssertInvalid = RBS_BTS_AssertInvalid
72 | this.AssertNotInvalid = RBS_BTS_AssertNotInvalid
73 | this.AssertAAHasKey = RBS_BTS_AssertAAHasKey
74 | this.AssertAANotHasKey = RBS_BTS_AssertAANotHasKey
75 | this.AssertAAHasKeys = RBS_BTS_AssertAAHasKeys
76 | this.AssertAANotHasKeys = RBS_BTS_AssertAANotHasKeys
77 | this.AssertArrayContains = RBS_BTS_AssertArrayContains
78 | this.AssertArrayNotContains = RBS_BTS_AssertArrayNotContains
79 | this.AssertArrayContainsSubset = RBS_BTS_AssertArrayContainsSubset
80 | this.AssertArrayContainsAAs = RBS_BTS_AssertArrayContainsAAs
81 | this.AssertArrayNotContainsSubset = RBS_BTS_AssertArrayNotContainsSubset
82 | this.AssertArrayCount = RBS_BTS_AssertArrayCount
83 | this.AssertArrayNotCount = RBS_BTS_AssertArrayNotCount
84 | this.AssertEmpty = RBS_BTS_AssertEmpty
85 | this.AssertNotEmpty = RBS_BTS_AssertNotEmpty
86 | this.AssertArrayContainsOnly = RBS_BTS_AssertArrayContainsOnly
87 | this.AssertType = RBS_BTS_AssertType
88 | this.AssertSubType = RBS_BTS_AssertSubType
89 | this.AssertNodeCount = RBS_BTS_AssertNodeCount
90 | this.AssertNodeNotCount = RBS_BTS_AssertNodeNotCount
91 | this.AssertNodeEmpty = RBS_BTS_AssertNodeEmpty
92 | this.AssertNodeNotEmpty = RBS_BTS_AssertNodenotEmpty
93 | this.AssertNodeContains = RBS_BTS_AssertNodeContains
94 | this.AssertNodeNotContains = RBS_BTS_AssertNodeNotContains
95 | this.AssertNodeContainsFields = RBS_BTS_AssertNodeContainsFields
96 | this.AssertNodeNotContainsFields = RBS_BTS_AssertNodeNotContainsFields
97 | this.AssertAAContainsSubset = RBS_BTS_AssertAAContainsSubset
98 | this.EqValues = RBS_BTS_EqValues
99 | this.EqAssocArrays = RBS_BTS_EqAssocArray
100 | this.EqArrays = RBS_BTS_EqArray
101 | this.Stub = RBS_BTS_Stub
102 | this.Mock = RBS_BTS_Mock
103 | this.AssertMocks = RBS_BTS_AssertMocks
104 | this.CreateFake = RBS_BTS_CreateFake
105 | this.MockFail = RBS_BTS_MockFail
106 | this.CleanMocks = RBS_BTS_CleanMocks
107 | this.CleanStubs = RBS_BTS_CleanStubs
108 | this.ExpectOnce = RBS_BTS_ExpectOnce
109 | this.ExpectNone = RBS_BTS_ExpectNone
110 | this.Expect = RBS_BTS_Expect
111 | this.MockCallback0 = RBS_BTS_MockCallback0
112 | this.MockCallback1 = RBS_BTS_MockCallback1
113 | this.MockCallback2 = RBS_BTS_MockCallback2
114 | this.MockCallback3 = RBS_BTS_MockCallback3
115 | this.MockCallback4 = RBS_BTS_MockCallback4
116 | this.MockCallback5 = RBS_BTS_MockCallback5
117 | this.StubCallback0 = RBS_BTS_StubCallback0
118 | this.StubCallback1 = RBS_BTS_StubCallback1
119 | this.StubCallback2 = RBS_BTS_StubCallback2
120 | this.StubCallback3 = RBS_BTS_StubCallback3
121 | this.StubCallback4 = RBS_BTS_StubCallback4
122 | this.StubCallback5 = RBS_BTS_StubCallback5
123 | this.pathAsArray_ = RBS_BTS_rodash_pathsAsArray_
124 | this.g = RBS_BTS_rodash_get_
125 | return this
126 | End Function
127 | Sub RBS_BTS_AddTest(name as String, func as Object, funcName as String, setup = invalid as Object, teardown = invalid as Object)
128 | m.testCases.Push(m.createTest(name, func, funcName, setup, teardown))
129 | End Sub
130 | Function RBS_BTS_CreateTest(name as String, func as Object, funcName as String, setup = invalid as Object, teardown = invalid as Object) as Object
131 | if (func = invalid)
132 | res = eval("functionPointer=" + funcName)
133 | if (RBS_CMN_IsInteger(res) and res = 252 and functionPointer <> invalid)
134 | func = functionPointer
135 | else
136 | ? "RBS_BTS_CreateTest could not find test function for " ; name
137 | end if
138 | end if
139 | return {
140 | Name: name
141 | Func: func
142 | FuncName: funcName
143 | SetUp: setup
144 | TearDown: teardown
145 | }
146 | End Function
147 | Function RBS_BTS_Fail(msg = "Error" as string) as dynamic
148 | if (m.currentResult.isFail) then return m.GetLegacyCompatibleReturnValue(false) ' skip test we already failed
149 | m.currentResult.AddResult(msg)
150 | return m.GetLegacyCompatibleReturnValue(false)
151 | End Function
152 | Function RBS_BTS_GetLegacyCompatibleReturnValue(value) as Object
153 | if (value = true)
154 | if (m.isLegacy = true)
155 | return ""
156 | else
157 | return true
158 | end if
159 | else
160 | if (m.isLegacy = true)
161 | return "ERROR"
162 | else
163 | return false
164 | end if
165 | end if
166 | End Function
167 | Function RBS_BTS_AssertFalse(expr as dynamic, msg = "Expression evaluates to true" as string) as dynamic
168 | if (m.currentResult.isFail) then return m.GetLegacyCompatibleReturnValue(false) ' skip test we already failed
169 | if not RBS_CMN_IsBoolean(expr) or expr
170 | m.currentResult.AddResult(msg)
171 | return m.fail(msg)
172 | end if
173 | m.currentResult.AddResult("")
174 | return m.GetLegacyCompatibleReturnValue(true)
175 | End Function
176 | Function RBS_BTS_AssertTrue(expr as dynamic, msg = "Expression evaluates to false" as string) as dynamic
177 | if (m.currentResult.isFail) then return m.GetLegacyCompatibleReturnValue(false) ' skip test we already failed
178 | if not RBS_CMN_IsBoolean(expr) or not expr then
179 | m.currentResult.AddResult(msg)
180 | return m.GetLegacyCompatibleReturnValue(false)
181 | End if
182 | m.currentResult.AddResult("")
183 | return m.GetLegacyCompatibleReturnValue(true)
184 | End Function
185 | Function RBS_BTS_AssertEqual(first as dynamic, second as dynamic, msg = "" as string) as dynamic
186 | if (m.currentResult.isFail) then return m.GetLegacyCompatibleReturnValue(false) ' skip test we already failed
187 | if not m.eqValues(first, second)
188 | if msg = ""
189 | first_as_string = RBS_CMN_AsString(first)
190 | second_as_string = RBS_CMN_AsString(second)
191 | msg = first_as_string + " != " + second_as_string
192 | end if
193 | m.currentResult.AddResult(msg)
194 | return m.GetLegacyCompatibleReturnValue(false)
195 | end if
196 | m.currentResult.AddResult("")
197 | return m.GetLegacyCompatibleReturnValue(true)
198 | End Function
199 | Function RBS_BTS_AssertLike(first as dynamic, second as dynamic, msg = "" as string) as dynamic
200 | if (m.currentResult.isFail) then return m.GetLegacyCompatibleReturnValue(false) ' skip test we already failed
201 | if first <> second
202 | if msg = ""
203 | first_as_string = RBS_CMN_AsString(first)
204 | second_as_string = RBS_CMN_AsString(second)
205 | msg = first_as_string + " != " + second_as_string
206 | end if
207 | m.currentResult.AddResult(msg)
208 | return m.GetLegacyCompatibleReturnValue(false)
209 | end if
210 | m.currentResult.AddResult("")
211 | return m.GetLegacyCompatibleReturnValue(true)
212 | End Function
213 | Function RBS_BTS_AssertNotEqual(first as dynamic, second as dynamic, msg = "" as string) as dynamic
214 | if (m.currentResult.isFail) then return m.GetLegacyCompatibleReturnValue(false) ' skip test we already failed
215 | if m.eqValues(first, second)
216 | if msg = ""
217 | first_as_string = RBS_CMN_AsString(first)
218 | second_as_string = RBS_CMN_AsString(second)
219 | msg = first_as_string + " == " + second_as_string
220 | end if
221 | m.currentResult.AddResult(msg)
222 | return m.GetLegacyCompatibleReturnValue(false)
223 | end if
224 | m.currentResult.AddResult("")
225 | return m.GetLegacyCompatibleReturnValue(true)
226 | End Function
227 | Function RBS_BTS_AssertInvalid(value as dynamic, msg = "" as string) as dynamic
228 | if (m.currentResult.isFail) then return m.GetLegacyCompatibleReturnValue(false) ' skip test we already failed
229 | if value <> Invalid
230 | if msg = ""
231 | expr_as_string = RBS_CMN_AsString(value)
232 | msg = expr_as_string + " <> Invalid"
233 | end if
234 | m.currentResult.AddResult(msg)
235 | return m.GetLegacyCompatibleReturnValue(false)
236 | end if
237 | m.currentResult.AddResult("")
238 | return m.GetLegacyCompatibleReturnValue(true)
239 | End Function
240 | Function RBS_BTS_AssertNotInvalid(value as dynamic, msg = "" as string) as dynamic
241 | if (m.currentResult.isFail) then return m.GetLegacyCompatibleReturnValue(false) ' skip test we already failed
242 | if value = Invalid
243 | if msg = ""
244 | expr_as_string = RBS_CMN_AsString(value)
245 | msg = expr_as_string + " = Invalid"
246 | end if
247 | m.currentResult.AddResult(msg)
248 | return m.GetLegacyCompatibleReturnValue(false)
249 | end if
250 | m.currentResult.AddResult("")
251 | return m.GetLegacyCompatibleReturnValue(true)
252 | End Function
253 | Function RBS_BTS_AssertAAHasKey(array as dynamic, key as string, msg = "" as string) as dynamic
254 | if (m.currentResult.isFail) then return m.GetLegacyCompatibleReturnValue(false) ' skip test we already failed
255 | if RBS_CMN_IsAssociativeArray(array)
256 | if not array.DoesExist(key)
257 | if msg = ""
258 | msg = "Array doesn't have the '" + key + "' key."
259 | end if
260 | m.currentResult.AddResult(msg)
261 | return m.GetLegacyCompatibleReturnValue(false)
262 | end if
263 | else
264 | msg = "Input value is not an Associative Array."
265 | m.currentResult.AddResult(msg)
266 | return m.GetLegacyCompatibleReturnValue(false)
267 | end if
268 | m.currentResult.AddResult("")
269 | return m.GetLegacyCompatibleReturnValue(true)
270 | End Function
271 | Function RBS_BTS_AssertAANotHasKey(array as dynamic, key as string, msg = "" as string) as dynamic
272 | if (m.currentResult.isFail) then return m.GetLegacyCompatibleReturnValue(false) ' skip test we already failed
273 | if RBS_CMN_IsAssociativeArray(array)
274 | if array.DoesExist(key)
275 | if msg = ""
276 | msg = "Array has the '" + key + "' key."
277 | end if
278 | m.currentResult.AddResult(msg)
279 | return m.GetLegacyCompatibleReturnValue(false)
280 | end if
281 | else
282 | msg = "Input value is not an Associative Array."
283 | m.currentResult.AddResult(msg)
284 | return m.GetLegacyCompatibleReturnValue(false)
285 | end if
286 | m.currentResult.AddResult("")
287 | return m.GetLegacyCompatibleReturnValue(true)
288 | End Function
289 | Function RBS_BTS_AssertAAHasKeys(array as dynamic, keys as object, msg = "" as string) as dynamic
290 | if (m.currentResult.isFail) then return m.GetLegacyCompatibleReturnValue(false) ' skip test we already failed
291 | if RBS_CMN_IsAssociativeArray(array) and RBS_CMN_IsArray(keys)
292 | for each key in keys
293 | if not array.DoesExist(key)
294 | if msg = ""
295 | msg = "Array doesn't have the '" + key + "' key."
296 | end if
297 | m.currentResult.AddResult(msg)
298 | return m.GetLegacyCompatibleReturnValue(false)
299 | end if
300 | end for
301 | else
302 | msg = "Input value is not an Associative Array."
303 | m.currentResult.AddResult(msg)
304 | return m.GetLegacyCompatibleReturnValue(false)
305 | end if
306 | m.currentResult.AddResult("")
307 | return m.GetLegacyCompatibleReturnValue(true)
308 | End Function
309 | Function RBS_BTS_AssertAANotHasKeys(array as dynamic, keys as object, msg = "" as string) as dynamic
310 | if (m.currentResult.isFail) then return m.GetLegacyCompatibleReturnValue(false) ' skip test we already failed
311 | if RBS_CMN_IsAssociativeArray(array) and RBS_CMN_IsArray(keys)
312 | for each key in keys
313 | if array.DoesExist(key)
314 | if msg = ""
315 | msg = "Array has the '" + key + "' key."
316 | end if
317 | m.currentResult.AddResult(msg)
318 | return m.GetLegacyCompatibleReturnValue(false)
319 | end if
320 | end for
321 | else
322 | msg = "Input value is not an Associative Array."
323 | m.currentResult.AddResult(msg)
324 | return m.GetLegacyCompatibleReturnValue(false)
325 | end if
326 | m.currentResult.AddResult("")
327 | return m.GetLegacyCompatibleReturnValue(true)
328 | End Function
329 | Function RBS_BTS_AssertArrayContains(array as dynamic, value as dynamic, key = invalid as string, msg = "" as string) as dynamic
330 | if (m.currentResult.isFail) then return m.GetLegacyCompatibleReturnValue(false) ' skip test we already failed
331 | if RBS_CMN_IsAssociativeArray(array) or RBS_CMN_IsArray(array)
332 | if not RBS_CMN_ArrayContains(array, value, key)
333 | msg = "Array doesn't have the '" + RBS_CMN_AsString(value) + "' value."
334 | m.currentResult.AddResult(msg)
335 | return m.GetLegacyCompatibleReturnValue(false)
336 | end if
337 | else
338 | msg = "Input value is not an Array."
339 | m.currentResult.AddResult(msg)
340 | return m.GetLegacyCompatibleReturnValue(false)
341 | end if
342 | m.currentResult.AddResult("")
343 | return m.GetLegacyCompatibleReturnValue(true)
344 | End Function
345 | Function RBS_BTS_AssertArrayContainsAAs(array as dynamic, values as dynamic, msg = "" as string) as dynamic
346 | if (m.currentResult.isFail) then return m.GetLegacyCompatibleReturnValue(false) ' skip test we already failed
347 | if not RBS_CMN_IsArray(values)
348 | msg = "values to search for are not an Array."
349 | m.currentResult.AddResult(msg)
350 | return m.GetLegacyCompatibleReturnValue(false)
351 | end if
352 | if RBS_CMN_IsArray(array)
353 | for each value in values
354 | isMatched = false
355 | if not RBS_CMN_IsAssociativeArray(value)
356 | msg = "Value to search for was not associativeArray "+ RBS_CMN_AsString(value)
357 | m.currentResult.AddResult(msg)
358 | return m.GetLegacyCompatibleReturnValue(false)
359 | end if
360 | for each item in array
361 | if (RBS_CMN_IsAssociativeArray(item))
362 | isValueMatched = true
363 | for each key in value
364 | fieldValue = value[key]
365 | itemValue = item[key]
366 | if (not m.EqValues(fieldValue, itemValue))
367 | isValueMatched = false
368 | exit for
369 | end if
370 | end for
371 | if (isValueMatched)
372 | isMatched = true
373 | exit for
374 | end if
375 | end if
376 | end for ' items in array
377 | if not isMatched
378 | msg = "array missing value: "+ RBS_CMN_AsString(value)
379 | m.currentResult.AddResult(msg)
380 | return m.GetLegacyCompatibleReturnValue(false)
381 | end if
382 | end for 'values to match
383 | else
384 | msg = "Input value is not an Array."
385 | m.currentResult.AddResult(msg)
386 | return m.GetLegacyCompatibleReturnValue(false)
387 | end if
388 | m.currentResult.AddResult("")
389 | return m.GetLegacyCompatibleReturnValue(true)
390 | End Function
391 | Function RBS_BTS_AssertArrayNotContains(array as dynamic, value as dynamic, key = invalid as string, msg = "" as string) as dynamic
392 | if (m.currentResult.isFail) then return m.GetLegacyCompatibleReturnValue(false) ' skip test we already failed
393 | if RBS_CMN_IsAssociativeArray(array) or RBS_CMN_IsArray(array)
394 | if RBS_CMN_ArrayContains(array, value, key)
395 | msg = "Array has the '" + RBS_CMN_AsString(value) + "' value."
396 | m.currentResult.AddResult(msg)
397 | return m.GetLegacyCompatibleReturnValue(false)
398 | end if
399 | else
400 | msg = "Input value is not an Array."
401 | m.currentResult.AddResult(msg)
402 | return m.GetLegacyCompatibleReturnValue(false)
403 | end if
404 | m.currentResult.AddResult("")
405 | return m.GetLegacyCompatibleReturnValue(true)
406 | End Function
407 | Function RBS_BTS_AssertArrayContainsSubset(array as dynamic, subset as dynamic, msg = "" as string) as dynamic
408 | if (m.currentResult.isFail) then return m.GetLegacyCompatibleReturnValue(false) ' skip test we already failed
409 | if (RBS_CMN_IsAssociativeArray(array) and RBS_CMN_IsAssociativeArray(subset)) or (RBS_CMN_IsArray(array) and RBS_CMN_IsArray(subset))
410 | isAA = RBS_CMN_IsAssociativeArray(subset)
411 | for each item in subset
412 | key = invalid
413 | value = item
414 | if isAA
415 | key = item
416 | value = subset[key]
417 | end if
418 | if not RBS_CMN_ArrayContains(array, value, key)
419 | msg = "Array doesn't have the '" + RBS_CMN_AsString(value) + "' value."
420 | m.currentResult.AddResult(msg)
421 | return m.GetLegacyCompatibleReturnValue(false)
422 | end if
423 | end for
424 | else
425 | msg = "Input value is not an Array."
426 | m.currentResult.AddResult(msg)
427 | return m.GetLegacyCompatibleReturnValue(false)
428 | end if
429 | m.currentResult.AddResult("")
430 | return m.GetLegacyCompatibleReturnValue(true)
431 | End Function
432 | Function RBS_BTS_AssertArrayNotContainsSubset(array as dynamic, subset as dynamic, msg = "" as string) as dynamic
433 | if (m.currentResult.isFail) then return m.GetLegacyCompatibleReturnValue(false) ' skip test we already failed
434 | if (RBS_CMN_IsAssociativeArray(array) and RBS_CMN_IsAssociativeArray(subset)) or (RBS_CMN_IsArray(array) and RBS_CMN_IsArray(subset))
435 | isAA = RBS_CMN_IsAssociativeArray(subset)
436 | for each item in subset
437 | key = invalid
438 | value = item
439 | if isAA
440 | key = item
441 | value = item[key]
442 | end if
443 | if RBS_CMN_ArrayContains(array, value, key)
444 | msg = "Array has the '" + RBS_CMN_AsString(value) + "' value."
445 | m.currentResult.AddResult(msg)
446 | return m.GetLegacyCompatibleReturnValue(false)
447 | end if
448 | end for
449 | else
450 | msg = "Input value is not an Array."
451 | m.currentResult.AddResult(msg)
452 | return m.GetLegacyCompatibleReturnValue(false)
453 | end if
454 | m.currentResult.AddResult("")
455 | return m.GetLegacyCompatibleReturnValue(true)
456 | End Function
457 | Function RBS_BTS_AssertArrayCount(array as dynamic, count as integer, msg = "" as string) as dynamic
458 | if (m.currentResult.isFail) then return m.GetLegacyCompatibleReturnValue(false) ' skip test we already failed
459 | if RBS_CMN_IsAssociativeArray(array) or RBS_CMN_IsArray(array)
460 | if array.Count() <> count
461 | msg = "Array items count " + RBS_CMN_AsString(array.Count()) + " <> " + RBS_CMN_AsString(count) + "."
462 | m.currentResult.AddResult(msg)
463 | return m.GetLegacyCompatibleReturnValue(false)
464 | end if
465 | else
466 | msg = "Input value is not an Array."
467 | m.currentResult.AddResult(msg)
468 | return m.GetLegacyCompatibleReturnValue(false)
469 | end if
470 | m.currentResult.AddResult("")
471 | return m.GetLegacyCompatibleReturnValue(true)
472 | End Function
473 | Function RBS_BTS_AssertArrayNotCount(array as dynamic, count as integer, msg = "" as string) as dynamic
474 | if (m.currentResult.isFail) then return m.GetLegacyCompatibleReturnValue(false) ' skip test we already failed
475 | if RBS_CMN_IsAssociativeArray(array) or RBS_CMN_IsArray(array)
476 | if array.Count() = count
477 | msg = "Array items count = " + RBS_CMN_AsString(count) + "."
478 | m.currentResult.AddResult(msg)
479 | return m.GetLegacyCompatibleReturnValue(false)
480 | end if
481 | else
482 | msg = "Input value is not an Array."
483 | m.currentResult.AddResult(msg)
484 | return m.GetLegacyCompatibleReturnValue(false)
485 | end if
486 | m.currentResult.AddResult("")
487 | return m.GetLegacyCompatibleReturnValue(true)
488 | End Function
489 | Function RBS_BTS_AssertEmpty(item as dynamic, msg = "" as string) as dynamic
490 | if (m.currentResult.isFail) then return m.GetLegacyCompatibleReturnValue(false) ' skip test we already failed
491 | if RBS_CMN_IsAssociativeArray(item) or RBS_CMN_IsArray(item)
492 | if item.Count() > 0
493 | msg = "Array is not empty."
494 | m.currentResult.AddResult(msg)
495 | return m.GetLegacyCompatibleReturnValue(false)
496 | end if
497 | else if (RBS_CMN_IsString(item))
498 | if (RBS_CMN_AsString(item) <> "")
499 | msg = "Input value is not empty."
500 | m.currentResult.AddResult(msg)
501 | return m.GetLegacyCompatibleReturnValue(false)
502 | end if
503 | else
504 | msg = "AssertEmpty: Input value was not an array or a string"
505 | m.currentResult.AddResult(msg)
506 | return m.GetLegacyCompatibleReturnValue(false)
507 | end if
508 | m.currentResult.AddResult("")
509 | return m.GetLegacyCompatibleReturnValue(true)
510 | End Function
511 | Function RBS_BTS_AssertNotEmpty(item as dynamic, msg = "" as string) as dynamic
512 | if (m.currentResult.isFail) then return m.GetLegacyCompatibleReturnValue(false) ' skip test we already failed
513 | if RBS_CMN_IsAssociativeArray(item) or RBS_CMN_IsArray(item)
514 | if item.Count() = 0
515 | msg = "Array is empty."
516 | m.currentResult.AddResult(msg)
517 | return m.GetLegacyCompatibleReturnValue(false)
518 | end if
519 | else if RBS_CMN_IsString(item)
520 | if (item = "")
521 | msg = "Input value is empty."
522 | m.currentResult.AddResult(msg)
523 | return m.GetLegacyCompatibleReturnValue(false)
524 | end if
525 | end if
526 | m.currentResult.AddResult("")
527 | return m.GetLegacyCompatibleReturnValue(true)
528 | End Function
529 | Function RBS_BTS_AssertArratNotEmpty(item as dynamic, msg = "" as string) as dynamic
530 | if (m.currentResult.isFail) then return m.GetLegacyCompatibleReturnValue(false) ' skip test we already failed
531 | if RBS_CMN_IsAssociativeArray(item) or RBS_CMN_IsArray(item)
532 | if item.Count() = 0
533 | msg = "Array is empty."
534 | m.currentResult.AddResult(msg)
535 | return m.GetLegacyCompatibleReturnValue(false)
536 | end if
537 | else if RBS_CMN_AsString(item) = ""
538 | msg = "Input value is empty."
539 | m.currentResult.AddResult(msg)
540 | return m.GetLegacyCompatibleReturnValue(false)
541 | end if
542 | m.currentResult.AddResult("")
543 | return m.GetLegacyCompatibleReturnValue(true)
544 | End Function
545 | Function RBS_BTS_AssertArrayContainsOnly(array as dynamic, typeStr as string, msg = "" as string) as dynamic
546 | if (m.currentResult.isFail) then return m.GetLegacyCompatibleReturnValue(false) ' skip test we already failed
547 | if RBS_CMN_IsAssociativeArray(array) or RBS_CMN_IsArray(array)
548 | methodName = "RBS_CMN_Is" + typeStr
549 | for each item in array
550 | if not methodName(item)
551 | msg = RBS_CMN_AsString(item) + "is not a '" + typeStr + "' type."
552 | m.currentResult.AddResult(msg)
553 | return m.GetLegacyCompatibleReturnValue(false)
554 | end if
555 | end for
556 | else
557 | msg = "Input value is not an Array."
558 | m.currentResult.AddResult(msg)
559 | return m.GetLegacyCompatibleReturnValue(false)
560 | end if
561 | m.currentResult.AddResult("")
562 | return m.GetLegacyCompatibleReturnValue(true)
563 | End Function
564 | function RBS_BTS_AssertType(value as dynamic, typeStr as string, msg ="" as string) as dynamic
565 | if (m.currentResult.isFail) then return m.GetLegacyCompatibleReturnValue(false) ' skip test we already failed
566 | if type(value) <> typeStr
567 | if msg = ""
568 | expr_as_string = RBS_CMN_AsString(value)
569 | msg = expr_as_string + " was not expected type " + typeStr
570 | end if
571 | m.currentResult.AddResult(msg)
572 | return m.GetLegacyCompatibleReturnValue(false)
573 | end if
574 | m.currentResult.AddResult("")
575 | return m.GetLegacyCompatibleReturnValue(true)
576 | end function
577 | function RBS_BTS_AssertSubType(value as dynamic, typeStr as string, msg ="" as string) as dynamic
578 | if (m.currentResult.isFail) then return m.GetLegacyCompatibleReturnValue(false) ' skip test we already failed
579 | if type(value) <> "roSGNode"
580 | if msg = ""
581 | expr_as_string = RBS_CMN_AsString(value)
582 | msg = expr_as_string + " was not a node, so could not match subtype " + typeStr
583 | end if
584 | m.currentResult.AddResult(msg)
585 | return m.GetLegacyCompatibleReturnValue(false)
586 | else if (value.subType() <> typeStr)
587 | if msg = ""
588 | expr_as_string = RBS_CMN_AsString(value)
589 | msg = expr_as_string + "( type : " + value.subType() +") was not of subType " + typeStr
590 | end if
591 | m.currentResult.AddResult(msg)
592 | return m.GetLegacyCompatibleReturnValue(false)
593 | end if
594 | m.currentResult.AddResult("")
595 | return m.GetLegacyCompatibleReturnValue(true)
596 | end function
597 | Function RBS_BTS_EqValues(Value1 as dynamic, Value2 as dynamic) as dynamic
598 | val1Type = type(Value1)
599 | val2Type = type(Value2)
600 | if val1Type = "" or val2Type = ""
601 | ? "ERROR!!!! - undefined value passed"
602 | return false
603 | end if
604 | if val1Type = "roString" or val1Type = "String"
605 | Value1 = RBS_CMN_AsString(Value1)
606 | else
607 | Value1 = box(Value1)
608 | end if
609 | if val2Type = "roString" or val2Type = "String"
610 | Value2 = RBS_CMN_AsString(Value2)
611 | else
612 | Value2 = box(Value2)
613 | end if
614 | val1Type = type(Value1)
615 | val2Type = type(Value2)
616 | if val1Type = "roFloat" and val2Type = "roInt"
617 | Value2 = box(Cdbl(Value2))
618 | else if val2Type = "roFloat" and val1Type = "roInt"
619 | Value1 = box(Cdbl(Value1))
620 | end if
621 | if val1Type <> val2Type
622 | return false
623 | else
624 | valtype = val1Type
625 | if valtype = ""
626 | return false
627 | else if valtype = "roList"
628 | return m.eqArrays(Value1, Value2)
629 | else if valtype = "roAssociativeArray"
630 | return m.eqAssocArrays(Value1, Value2)
631 | else if valtype = "roArray"
632 | return m.eqArrays(Value1, Value2)
633 | else if (valtype = "roSGNode")
634 | if (val2Type <> "roSGNode")
635 | return false
636 | else
637 | return Value1.isSameNode(Value2)
638 | end if
639 | else
640 | return Value1 = Value2
641 | end if
642 | end if
643 | End Function
644 | Function RBS_BTS_EqAssocArray(Value1 as Object, Value2 as Object) as dynamic
645 | l1 = Value1.Count()
646 | l2 = Value2.Count()
647 | if not l1 = l2
648 | return false
649 | else
650 | for each k in Value1
651 | if not Value2.DoesExist(k)
652 | return false
653 | else
654 | v1 = Value1[k]
655 | v2 = Value2[k]
656 | if not m.eqValues(v1, v2)
657 | return false
658 | end if
659 | end if
660 | end for
661 | return true
662 | end if
663 | End Function
664 | Function RBS_BTS_EqArray(Value1 as Object, Value2 as Object) as dynamic
665 | l1 = Value1.Count()
666 | l2 = Value2.Count()
667 | if not l1 = l2
668 | return false
669 | else
670 | for i = 0 to l1 - 1
671 | v1 = Value1[i]
672 | v2 = Value2[i]
673 | if not m.eqValues(v1, v2) then
674 | return false
675 | end if
676 | end for
677 | return true
678 | end if
679 | End Function
680 | Function RBS_BTS_AssertNodeCount(node as dynamic, count as integer, msg = "" as string) as dynamic
681 | if (m.currentResult.isFail) then return m.GetLegacyCompatibleReturnValue(false) ' skip test we already failed
682 | if type(node) = "roSGNode"
683 | if node.getChildCount() <> count
684 | msg = "node items count <> " + RBS_CMN_AsString(count) + ". Received " + RBS_CMN_AsString(node.getChildCount())
685 | m.currentResult.AddResult(msg)
686 | return m.GetLegacyCompatibleReturnValue(false)
687 | end if
688 | else
689 | msg = "Input value is not an node."
690 | m.currentResult.AddResult(msg)
691 | return m.GetLegacyCompatibleReturnValue(false)
692 | end if
693 | m.currentResult.AddResult("")
694 | return m.GetLegacyCompatibleReturnValue(true)
695 | End Function
696 | Function RBS_BTS_AssertNodeNotCount(node as dynamic, count as integer, msg = "" as string) as dynamic
697 | if (m.currentResult.isFail) then return m.GetLegacyCompatibleReturnValue(false) ' skip test we already failed
698 | if type(node) = "roSGNode"
699 | if node.getChildCount() = count
700 | msg = "node items count = " + RBS_CMN_AsString(count) + "."
701 | m.currentResult.AddResult(msg)
702 | return m.GetLegacyCompatibleReturnValue(false)
703 | end if
704 | else
705 | msg = "Input value is not an node."
706 | m.currentResult.AddResult(msg)
707 | return m.GetLegacyCompatibleReturnValue(false)
708 | end if
709 | m.currentResult.AddResult("")
710 | return m.GetLegacyCompatibleReturnValue(true)
711 | End Function
712 | Function RBS_BTS_AssertNodeEmpty(node as dynamic, msg = "" as string) as dynamic
713 | if (m.currentResult.isFail) then return m.GetLegacyCompatibleReturnValue(false) ' skip test we already failed
714 | if type(node) = "roSGNode"
715 | if node.getChildCount() > 0
716 | msg = "node is not empty."
717 | m.currentResult.AddResult(msg)
718 | return m.GetLegacyCompatibleReturnValue(false)
719 | end if
720 | end if
721 | m.currentResult.AddResult("")
722 | return m.GetLegacyCompatibleReturnValue(true)
723 | End Function
724 | Function RBS_BTS_AssertNodeNotEmpty(node as dynamic, msg = "" as string) as dynamic
725 | if (m.currentResult.isFail) then return m.GetLegacyCompatibleReturnValue(false) ' skip test we already failed
726 | if type(node) = "roSGNode"
727 | if node.Count() = 0
728 | msg = "Array is empty."
729 | m.currentResult.AddResult(msg)
730 | return m.GetLegacyCompatibleReturnValue(false)
731 | end if
732 | end if
733 | m.currentResult.AddResult("")
734 | return m.GetLegacyCompatibleReturnValue(true)
735 | End Function
736 | Function RBS_BTS_AssertNodeContains(node as dynamic, value as dynamic, msg = "" as string) as dynamic
737 | if (m.currentResult.isFail) then return m.GetLegacyCompatibleReturnValue(false) ' skip test we already failed
738 | if type(node) = "roSGNode"
739 | if not RBS_CMN_NodeContains(node, value)
740 | msg = "Node doesn't have the '" + RBS_CMN_AsString(value) + "' value."
741 | m.currentResult.AddResult(msg)
742 | return m.GetLegacyCompatibleReturnValue(false)
743 | end if
744 | else
745 | msg = "Input value is not an Node."
746 | m.currentResult.AddResult(msg)
747 | return m.GetLegacyCompatibleReturnValue(false)
748 | end if
749 | m.currentResult.AddResult("")
750 | return m.GetLegacyCompatibleReturnValue(true)
751 | End Function
752 | Function RBS_BTS_AssertNodeContainsOnly(node as dynamic, msg = "" as string) as dynamic
753 | if (m.currentResult.isFail) then return m.GetLegacyCompatibleReturnValue(false) ' skip test we already failed
754 | if type(node) = "roSGNode"
755 | if not RBS_CMN_NodeContains(node, value)
756 | msg = "Node doesn't have the '" + RBS_CMN_AsString(value) + "' value."
757 | m.currentResult.AddResult(msg)
758 | return m.GetLegacyCompatibleReturnValue(false)
759 | else if node.getChildCount() <> 1
760 | msg = "Node Contains speicified value; but other values as well"
761 | m.currentResult.AddResult(msg)
762 | return m.GetLegacyCompatibleReturnValue(false)
763 | end if
764 | else
765 | msg = "Input value is not an Node."
766 | m.currentResult.AddResult(msg)
767 | return m.GetLegacyCompatibleReturnValue(false)
768 | end if
769 | m.currentResult.AddResult("")
770 | return m.GetLegacyCompatibleReturnValue(true)
771 | End Function
772 | Function RBS_BTS_AssertNodeNotContains(node as dynamic, value as dynamic, msg = "" as string) as dynamic
773 | if (m.currentResult.isFail) then return m.GetLegacyCompatibleReturnValue(false) ' skip test we already failed
774 | if type(node) = "roSGNode"
775 | if RBS_CMN_NodeContains(node, value)
776 | msg = "Node has the '" + RBS_CMN_AsString(value) + "' value."
777 | m.currentResult.AddResult(msg)
778 | return m.GetLegacyCompatibleReturnValue(false)
779 | end if
780 | else
781 | msg = "Input value is not an Node."
782 | m.currentResult.AddResult(msg)
783 | return m.GetLegacyCompatibleReturnValue(false)
784 | end if
785 | m.currentResult.AddResult("")
786 | return m.GetLegacyCompatibleReturnValue(true)
787 | End Function
788 | Function RBS_BTS_AssertNodeContainsFields(node as dynamic, subset as dynamic, ignoredFields=invalid, msg = "" as string) as dynamic
789 | if (m.currentResult.isFail) then return m.GetLegacyCompatibleReturnValue(false) ' skip test we already failed
790 | if ( type(node) = "roSGNode" and RBS_CMN_IsAssociativeArray(subset)) or ( type(node) = "roSGNode" and RBS_CMN_IsArray(subset))
791 | isAA = RBS_CMN_IsAssociativeArray(subset)
792 | isIgnoredFields = RBS_CMN_IsArray(ignoredFields)
793 | for each key in subset
794 | if (key <> "")
795 | if (not isIgnoredFields or not RBS_CMN_ArrayContains(ignoredFields, key))
796 | subsetValue = subset[key]
797 | nodeValue = node[key]
798 | if not m.eqValues(nodeValue, subsetValue)
799 | msg = key + ": Expected '" + RBS_CMN_AsString(subsetValue) + "', got '" + RBS_CMN_AsString(nodeValue) + "'"
800 | m.currentResult.AddResult(msg)
801 | return m.GetLegacyCompatibleReturnValue(false)
802 | end if
803 | end if
804 | else
805 | ? "Found empty key!"
806 | end if
807 | end for
808 | else
809 | msg = "Input value is not an Node."
810 | m.currentResult.AddResult(msg)
811 | return m.GetLegacyCompatibleReturnValue(false)
812 | end if
813 | m.currentResult.AddResult("")
814 | return m.GetLegacyCompatibleReturnValue(true)
815 | End Function
816 | Function RBS_BTS_AssertNodeNotContainsFields(node as dynamic, subset as dynamic, msg = "" as string) as dynamic
817 | if (m.currentResult.isFail) then return m.GetLegacyCompatibleReturnValue(false) ' skip test we already failed
818 | if ( type(node) = "roSGNode" and RBS_CMN_IsAssociativeArray(subset)) or ( type(node) = "roSGNode" and RBS_CMN_IsArray(subset))
819 | isAA = RBS_CMN_IsAssociativeArray(subset)
820 | for each item in subset
821 | key = invalid
822 | value = item
823 | if isAA
824 | key = item
825 | value = item[key]
826 | end if
827 | if RBS_CMN_NodeContains(node, value, key)
828 | msg = "Node has the '" + RBS_CMN_AsString(value) + "' value."
829 | m.currentResult.AddResult(msg)
830 | return m.GetLegacyCompatibleReturnValue(false)
831 | end if
832 | end for
833 | else
834 | msg = "Input value is not an Node."
835 | m.currentResult.AddResult(msg)
836 | return m.GetLegacyCompatibleReturnValue(false)
837 | end if
838 | m.currentResult.AddResult("")
839 | return m.GetLegacyCompatibleReturnValue(true)
840 | End Function
841 | Function RBS_BTS_AssertAAContainsSubset(array as dynamic, subset as dynamic, ignoredFields = invalid, msg = "" as string) as dynamic
842 | if (m.currentResult.isFail) then return m.GetLegacyCompatibleReturnValue(false) ' skip test we already failed
843 | if (RBS_CMN_IsAssociativeArray(array) and RBS_CMN_IsAssociativeArray(subset))
844 | isAA = RBS_CMN_IsAssociativeArray(subset)
845 | isIgnoredFields = RBS_CMN_IsArray(ignoredFields)
846 | for each key in subset
847 | if (key <> "")
848 | if (not isIgnoredFields or not RBS_CMN_ArrayContains(ignoredFields, key))
849 | subsetValue = subset[key]
850 | arrayValue = array[key]
851 | if not m.eqValues(arrayValue, subsetValue)
852 | msg = key + ": Expected '" + RBS_CMN_AsString(subsetValue) + "', got '" + RBS_CMN_AsString(arrayValue) + "'"
853 | m.currentResult.AddResult(msg)
854 | return m.GetLegacyCompatibleReturnValue(false)
855 | end if
856 | end if
857 | else
858 | ? "Found empty key!"
859 | end if
860 | end for
861 | else
862 | msg = "Input values are not an Associative Array."
863 | return m.GetLegacyCompatibleReturnValue(false)
864 | end if
865 | m.currentResult.AddResult("")
866 | return m.GetLegacyCompatibleReturnValue(true)
867 | End Function
868 | function RBS_BTS_Stub(target, methodName, returnValue = invalid) as object
869 | if (type(target) <> "roAssociativeArray")
870 | m.Fail("could not create Stub provided target was null")
871 | return {}
872 | end if
873 | if (m.stubs =invalid)
874 | m.__stubId = -1
875 | m.stubs = {}
876 | end if
877 | m.__stubId++
878 | if (m.__stubId > 5)
879 | ? "ERROR ONLY 6 STUBS PER TEST ARE SUPPORTED!!"
880 | return invalid
881 | end if
882 | id = stri(m.__stubId).trim()
883 | fake = m.CreateFake(id, target, methodName, 1, invalid, returnValue)
884 | m.stubs[id] = fake
885 | if (type(target[methodName]) = "Function" or type(target[methodName]) = "roFunction")
886 | target[methodName] = m["StubCallback" + id]
887 | target.__stubs = m.stubs
888 | else
889 | ? "ERROR - could not create Stub : method not found "; target ; "." ; methodName
890 | end if
891 | return fake
892 | end function
893 | function RBS_BTS_ExpectOnce(target, methodName, expectedArgs = invalid, returnValue = invalid) as object
894 | return m.Mock(target, methodName, 1, expectedArgs, returnValue)
895 | end function
896 | function RBS_BTS_ExpectNone(target, methodName, expectedArgs = invalid, returnValue = invalid) as object
897 | return m.Mock(target, methodName, 0, expectedArgs, returnValue)
898 | end function
899 | function RBS_BTS_Expect(target, methodName, expectedInvocations = 1, expectedArgs = invalid, returnValue = invalid) as object
900 | return m.Mock(target, methodName, expectedInvocations, expectedArgs, returnValue)
901 | end function
902 | function RBS_BTS_Mock(target, methodName, expectedInvocations = 1, expectedArgs = invalid, returnValue = invalid) as object
903 | if (type(target) <> "roAssociativeArray")
904 | m.Fail("could not create Stub provided target was null")
905 | return {}
906 | end if
907 | if (m.mocks = invalid)
908 | m.__mockId = -1
909 | m.mocks = {}
910 | end if
911 | m.__mockId++
912 | if (m.__mockId > 5)
913 | ? "ERROR ONLY 6 MOCKS PER TEST ARE SUPPORTED!! you're on # " ; m.__mockId
914 | ? " Method was " ; methodName
915 | return invalid
916 | end if
917 | id = stri(m.__mockId).trim()
918 | fake = m.CreateFake(id, target, methodName, expectedInvocations, expectedArgs, returnValue)
919 | m.mocks[id] = fake 'this will bind it to m
920 | if (type(target[methodName]) = "Function" or type(target[methodName]) = "roFunction")
921 | target[methodName] = m["MockCallback" + id]
922 | target.__mocks = m.mocks
923 | else
924 | ? "ERROR - could not create Mock : method not found "; target ; "." ; methodName
925 | end if
926 | return fake
927 | end function
928 | function RBS_BTS_CreateFake(id, target, methodName, expectedInvocations = 1, expectedArgs =invalid, returnValue=invalid ) as object
929 | fake = {
930 | id : id,
931 | target: target,
932 | methodName: methodName,
933 | returnValue: returnValue,
934 | isCalled: false,
935 | invocations: 0,
936 | invokedArgs: [invalid, invalid, invalid, invalid, invalid, invalid, invalid, invalid, invalid],
937 | expectedArgs: expectedArgs,
938 | expectedInvocations: expectedInvocations,
939 | callback: function (arg1=invalid, arg2=invalid, arg3=invalid, arg4=invalid, arg5=invalid, arg6=invalid, arg7=invalid, arg8=invalid, arg9 =invalid)as dynamic
940 | ? "FAKE CALLBACK CALLED FOR " ; m.methodName
941 | if (m.allInvokedArgs = invalid)
942 | m.allInvokedArgs = []
943 | end if
944 | m.invokedArgs = [arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9 ]
945 | m.allInvokedArgs.push ([arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9 ])
946 | m.isCalled = true
947 | m.invocations++
948 | return m.returnValue
949 | end function
950 | }
951 | return fake
952 | end function
953 | function RBS_BTS_AssertMocks() as void
954 | if (m.__mockId = invalid ) return
955 | lastId = int(m.__mockId)
956 | for each id in m.mocks
957 | mock = m.mocks[id]
958 | methodName = mock.methodName
959 | if (mock.expectedInvocations <> mock.invocations)
960 | m.MockFail(methodName, "Wrong number of calls. (" + stri(mock.invocations).trim() + " / " + stri(mock.expectedInvocations).trim() + ")")
961 | return
962 | else if (RBS_CMN_IsArray(mock.expectedArgs))
963 | for i = 0 to mock.expectedargs.count() -1
964 | value = mock.invokedArgs[i]
965 | expected = mock.expectedargs[i]
966 | if (not m.eqValues(value,expected))
967 | m.MockFail(methodName, "Expected arg #" + stri(i).trim() + " to be '" + RBS_CMN_AsString(expected) + "' got '" + RBS_CMN_AsString(value) + "')")
968 | return
969 | end if
970 | end for
971 | end if
972 | end for
973 | m.CleanMocks()
974 | end function
975 | function RBS_BTS_CleanMocks() as void
976 | if m.mocks = invalid return
977 | for each id in m.mocks
978 | mock = m.mocks[id]
979 | mock.target.__mocks = invalid
980 | end for
981 | m.mocks = invalid
982 | end function
983 | function RBS_BTS_CleanStubs() as void
984 | if m.stubs = invalid return
985 | for each id in m.stubs
986 | stub = m.stubs[id]
987 | stub.target.__stubs = invalid
988 | end for
989 | m.stubs = invalid
990 | end function
991 | Function RBS_BTS_MockFail(methodName, message) as dynamic
992 | if (m.currentResult.isFail) then return m.GetLegacyCompatibleReturnValue(false) ' skip test we already failed
993 | m.currentResult.AddResult("mock failure on '" + methodName + "' : " + message)
994 | return m.GetLegacyCompatibleReturnValue(false)
995 | End Function
996 | function RBS_BTS_StubCallback0(arg1=invalid, arg2=invalid, arg3=invalid, arg4=invalid, arg5=invalid, arg6=invalid, arg7=invalid, arg8=invalid, arg9 =invalid)as dynamic
997 | fake = m.__Stubs["0"]
998 | return fake.callback(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9)
999 | end function
1000 | function RBS_BTS_StubCallback1(arg1=invalid, arg2=invalid, arg3=invalid, arg4=invalid, arg5=invalid, arg6=invalid, arg7=invalid, arg8=invalid, arg9 =invalid)as dynamic
1001 | fake = m.__Stubs["1"]
1002 | return fake.callback(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9)
1003 | end function
1004 | function RBS_BTS_StubCallback2(arg1=invalid, arg2=invalid, arg3=invalid, arg4=invalid, arg5=invalid, arg6=invalid, arg7=invalid, arg8=invalid, arg9 =invalid)as dynamic
1005 | fake = m.__Stubs["2"]
1006 | return fake.callback(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9)
1007 | end function
1008 | function RBS_BTS_StubCallback3(arg1=invalid, arg2=invalid, arg3=invalid, arg4=invalid, arg5=invalid, arg6=invalid, arg7=invalid, arg8=invalid, arg9 =invalid)as dynamic
1009 | fake = m.__Stubs["3"]
1010 | return fake.callback(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9)
1011 | end function
1012 | function RBS_BTS_StubCallback4(arg1=invalid, arg2=invalid, arg3=invalid, arg4=invalid, arg5=invalid, arg6=invalid, arg7=invalid, arg8=invalid, arg9 =invalid)as dynamic
1013 | fake = m.__Stubs["4"]
1014 | return fake.callback(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9)
1015 | end function
1016 | function RBS_BTS_StubCallback5(arg1=invalid, arg2=invalid, arg3=invalid, arg4=invalid, arg5=invalid, arg6=invalid, arg7=invalid, arg8=invalid, arg9 =invalid)as dynamic
1017 | fake = m.__Stubs["5"]
1018 | return fake.callback(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9)
1019 | end function
1020 | function RBS_BTS_MockCallback0(arg1=invalid, arg2=invalid, arg3=invalid, arg4=invalid, arg5=invalid, arg6=invalid, arg7=invalid, arg8=invalid, arg9 =invalid)as dynamic
1021 | fake = m.__mocks["0"]
1022 | return fake.callback(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9)
1023 | end function
1024 | function RBS_BTS_MockCallback1(arg1=invalid, arg2=invalid, arg3=invalid, arg4=invalid, arg5=invalid, arg6=invalid, arg7=invalid, arg8=invalid, arg9 =invalid)as dynamic
1025 | fake = m.__mocks["1"]
1026 | return fake.callback(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9)
1027 | end function
1028 | function RBS_BTS_MockCallback2(arg1=invalid, arg2=invalid, arg3=invalid, arg4=invalid, arg5=invalid, arg6=invalid, arg7=invalid, arg8=invalid, arg9 =invalid)as dynamic
1029 | fake = m.__mocks["2"]
1030 | return fake.callback(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9)
1031 | end function
1032 | function RBS_BTS_MockCallback3(arg1=invalid, arg2=invalid, arg3=invalid, arg4=invalid, arg5=invalid, arg6=invalid, arg7=invalid, arg8=invalid, arg9 =invalid)as dynamic
1033 | fake = m.__mocks["3"]
1034 | return fake.callback(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9)
1035 | end function
1036 | function RBS_BTS_MockCallback4(arg1=invalid, arg2=invalid, arg3=invalid, arg4=invalid, arg5=invalid, arg6=invalid, arg7=invalid, arg8=invalid, arg9 =invalid)as dynamic
1037 | fake = m.__mocks["4"]
1038 | return fake.callback(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9)
1039 | end function
1040 | function RBS_BTS_MockCallback5(arg1=invalid, arg2=invalid, arg3=invalid, arg4=invalid, arg5=invalid, arg6=invalid, arg7=invalid, arg8=invalid, arg9 =invalid)as dynamic
1041 | fake = m.__mocks["5"]
1042 | return fake.callback(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9)
1043 | end function
1044 | Function RBS_BTS_rodash_pathsAsArray_(path)
1045 | pathRE = CreateObject("roRegex", "\[([0-9]+)\]", "i")
1046 | segments = []
1047 | if type(path) = "String" or type(path) = "roString"
1048 | dottedPath = pathRE.replaceAll(path, ".\1")
1049 | stringSegments = dottedPath.tokenize(".")
1050 | for each s in stringSegments
1051 | if (Asc(s) >= 48) and (Asc(s) <= 57)
1052 | segments.push(s.toInt())
1053 | else
1054 | segments.push(s)
1055 | end if
1056 | end for
1057 | else if type(path) = "roList" or type(path) = "roArray"
1058 | stringPath = ""
1059 | for each s in path
1060 | stringPath = stringPath + "." + Box(s).toStr()
1061 | end for
1062 | segments = m.pathAsArray_(stringPath)
1063 | else
1064 | segments = invalid
1065 | end if
1066 | return segments
1067 | End Function
1068 | Function RBS_BTS_rodash_get_(aa, path, default=invalid)
1069 | if type(aa) <> "roAssociativeArray" and type(aa) <> "roArray" and type(aa) <> "roSGNode" then return default
1070 | segments = m.pathAsArray_(path)
1071 | if (Type(path) = "roInt" or Type(path) = "roInteger" or Type(path) = "Integer")
1072 | path = stri(path).trim()
1073 | end if
1074 | if segments = invalid then return default
1075 | result = invalid
1076 | while segments.count() > 0
1077 | key = segments.shift()
1078 | if (type(key) = "roInteger") 'it's a valid index
1079 | if (aa <> invalid and GetInterface(aa, "ifArray") <> invalid)
1080 | value = aa[key]
1081 | else if (aa <> invalid and GetInterface(aa, "ifSGNodeChildren") <> invalid)
1082 | value = aa.getChild(key)
1083 | else if (aa <> invalid and GetInterface(aa, "ifAssociativeArray") <> invalid)
1084 | key = tostr(key)
1085 | if not aa.doesExist(key)
1086 | exit while
1087 | end if
1088 | value = aa.lookup(key)
1089 | else
1090 | value = invalid
1091 | end if
1092 | else
1093 | if not aa.doesExist(key)
1094 | exit while
1095 | end if
1096 | value = aa.lookup(key)
1097 | end if
1098 | if segments.count() = 0
1099 | result = value
1100 | exit while
1101 | end if
1102 | if type(value) <> "roAssociativeArray" and type(value) <> "roArray" and type(value) <> "roSGNode"
1103 | exit while
1104 | end if
1105 | aa = value
1106 | end while
1107 | if result = invalid then return default
1108 | return result
1109 | End Function
1110 | function RBS_CMN_IsXmlElement(value as Dynamic) as Boolean
1111 | return RBS_CMN_IsValid(value) and GetInterface(value, "ifXMLElement") <> invalid
1112 | end function
1113 | function RBS_CMN_IsFunction(value as Dynamic) as Boolean
1114 | return RBS_CMN_IsValid(value) and GetInterface(value, "ifFunction") <> invalid
1115 | end function
1116 | function RBS_CMN_GetFunction(func, name) as Object
1117 | if (RBS_CMN_IsFunction(func)) then return func
1118 | if (not RBS_CMN_IsNotEmptyString(name)) then return invalid
1119 | res = eval("functionPointer=" + name)
1120 | if (RBS_CMN_IsInteger(res) and RBS_CMN_IsFunction(functionPointer))
1121 | return functionPointer
1122 | else
1123 | return invalid
1124 | end if
1125 | end function
1126 | function RBS_CMN_IsBoolean(value as Dynamic) as Boolean
1127 | return RBS_CMN_IsValid(value) and GetInterface(value, "ifBoolean") <> invalid
1128 | end function
1129 | function RBS_CMN_IsInteger(value as Dynamic) as Boolean
1130 | return RBS_CMN_IsValid(value) and GetInterface(value, "ifInt") <> invalid and (Type(value) = "roInt" or Type(value) = "roInteger" or Type(value) = "Integer")
1131 | end function
1132 | function RBS_CMN_IsFloat(value as Dynamic) as Boolean
1133 | return RBS_CMN_IsValid(value) and GetInterface(value, "ifFloat") <> invalid
1134 | end function
1135 | function RBS_CMN_IsDouble(value as Dynamic) as Boolean
1136 | return RBS_CMN_IsValid(value) and GetInterface(value, "ifDouble") <> invalid
1137 | end function
1138 | function RBS_CMN_IsLongInteger(value as Dynamic) as Boolean
1139 | return RBS_CMN_IsValid(value) and GetInterface(value, "ifLongInt") <> invalid
1140 | end function
1141 | function RBS_CMN_IsNumber(value as Dynamic) as Boolean
1142 | return RBS_CMN_IsLongInteger(value) or RBS_CMN_IsDouble(value) or RBS_CMN_IsInteger(value) or RBS_CMN_IsFloat(value)
1143 | end function
1144 | function RBS_CMN_IsList(value as Dynamic) as Boolean
1145 | return RBS_CMN_IsValid(value) and GetInterface(value, "ifList") <> invalid
1146 | end function
1147 | function RBS_CMN_IsArray(value as Dynamic) as Boolean
1148 | return RBS_CMN_IsValid(value) and GetInterface(value, "ifArray") <> invalid
1149 | end function
1150 | function RBS_CMN_IsAssociativeArray(value as Dynamic) as Boolean
1151 | return RBS_CMN_IsValid(value) and GetInterface(value, "ifAssociativeArray") <> invalid
1152 | end function
1153 | function RBS_CMN_IsSGNode(value as Dynamic) as Boolean
1154 | return RBS_CMN_IsValid(value) and GetInterface(value, "ifSGNodeChildren") <> invalid
1155 | end function
1156 | function RBS_CMN_IsString(value as Dynamic) as Boolean
1157 | return RBS_CMN_IsValid(value) and GetInterface(value, "ifString") <> invalid
1158 | end function
1159 | function RBS_CMN_IsNotEmptyString(value as Dynamic) as Boolean
1160 | return RBS_CMN_IsString(value) and len(value) > 0
1161 | end function
1162 | function RBS_CMN_IsDateTime(value as Dynamic) as Boolean
1163 | return RBS_CMN_IsValid(value) and (GetInterface(value, "ifDateTime") <> invalid or Type(value) = "roDateTime")
1164 | end function
1165 | function RBS_CMN_IsValid(value as Dynamic) as Boolean
1166 | return Type(value) <> "" and value <> invalid
1167 | end function
1168 | function RBS_CMN_ValidStr(obj as Object) as String
1169 | if obj <> invalid and GetInterface(obj, "ifString") <> invalid
1170 | return obj
1171 | else
1172 | return ""
1173 | end if
1174 | end function
1175 | function RBS_CMN_AsString(input as Dynamic) as String
1176 | if RBS_CMN_IsValid(input) = false
1177 | return ""
1178 | else if RBS_CMN_IsString(input)
1179 | return input
1180 | else if RBS_CMN_IsInteger(input) or RBS_CMN_IsLongInteger(input) or RBS_CMN_IsBoolean(input)
1181 | return input.ToStr()
1182 | else if RBS_CMN_IsFloat(input) or RBS_CMN_IsDouble(input)
1183 | return Str(input).Trim()
1184 | else if type(input) = "roSGNode"
1185 | return "Node(" + input.subType() +")"
1186 | else if type(input) = "roAssociativeArray"
1187 | isFirst = true
1188 | text = "{"
1189 | if (not isFirst)
1190 | text += ","
1191 | isFirst = false
1192 | end if
1193 | for each key in input
1194 | text += key + ":" + RBS_CMN_AsString(input[key])
1195 | end for
1196 | text += "}"
1197 | return text
1198 | else
1199 | return ""
1200 | end If
1201 | end function
1202 | function RBS_CMN_AsInteger(input as Dynamic) as Integer
1203 | if RBS_CMN_IsValid(input) = false
1204 | return 0
1205 | else if RBS_CMN_IsString(input)
1206 | return input.ToInt()
1207 | else if RBS_CMN_IsInteger(input)
1208 | return input
1209 | else if RBS_CMN_IsFloat(input) or RBS_CMN_IsDouble(input) or RBS_CMN_IsLongInteger(input)
1210 | return Int(input)
1211 | else
1212 | return 0
1213 | end if
1214 | end function
1215 | function RBS_CMN_AsLongInteger(input as Dynamic) as LongInteger
1216 | if RBS_CMN_IsValid(input) = false
1217 | return 0
1218 | else if RBS_CMN_IsString(input)
1219 | return RBS_CMN_AsInteger(input)
1220 | else if RBS_CMN_IsLongInteger(input) or RBS_CMN_IsFloat(input) or RBS_CMN_IsDouble(input) or RBS_CMN_IsInteger(input)
1221 | return input
1222 | else
1223 | return 0
1224 | end if
1225 | end function
1226 | function RBS_CMN_AsFloat(input as Dynamic) as Float
1227 | if RBS_CMN_IsValid(input) = false
1228 | return 0.0
1229 | else if RBS_CMN_IsString(input)
1230 | return input.ToFloat()
1231 | else if RBS_CMN_IsInteger(input)
1232 | return (input / 1)
1233 | else if RBS_CMN_IsFloat(input) or RBS_CMN_IsDouble(input) or RBS_CMN_IsLongInteger(input)
1234 | return input
1235 | else
1236 | return 0.0
1237 | end if
1238 | end function
1239 | function RBS_CMN_AsDouble(input as Dynamic) as Double
1240 | if RBS_CMN_IsValid(input) = false
1241 | return 0.0
1242 | else if RBS_CMN_IsString(input)
1243 | return RBS_CMN_AsFloat(input)
1244 | else if RBS_CMN_IsInteger(input) or RBS_CMN_IsLongInteger(input) or RBS_CMN_IsFloat(input) or RBS_CMN_IsDouble(input)
1245 | return input
1246 | else
1247 | return 0.0
1248 | end if
1249 | end function
1250 | function RBS_CMN_AsBoolean(input as Dynamic) as Boolean
1251 | if RBS_CMN_IsValid(input) = false
1252 | return false
1253 | else if RBS_CMN_IsString(input)
1254 | return LCase(input) = "true"
1255 | else if RBS_CMN_IsInteger(input) or RBS_CMN_IsFloat(input)
1256 | return input <> 0
1257 | else if RBS_CMN_IsBoolean(input)
1258 | return input
1259 | else
1260 | return false
1261 | end if
1262 | end function
1263 | function RBS_CMN_AsArray(value as Object) as Object
1264 | if RBS_CMN_IsValid(value)
1265 | if not RBS_CMN_IsArray(value)
1266 | return [value]
1267 | else
1268 | return value
1269 | end if
1270 | end if
1271 | return []
1272 | end function
1273 | function RBS_CMN_IsNullOrEmpty(value as Dynamic) as Boolean
1274 | if RBS_CMN_IsString(value)
1275 | return Len(value) = 0
1276 | else
1277 | return not RBS_CMN_IsValid(value)
1278 | end if
1279 | end function
1280 | function RBS_CMN_FindElementIndexInArray(array as Object, value as Object, compareAttribute = invalid as Dynamic, caseSensitive = false as Boolean) as Integer
1281 | if RBS_CMN_IsArray(array)
1282 | for i = 0 to RBS_CMN_AsArray(array).Count() - 1
1283 | compareValue = array[i]
1284 | if compareAttribute <> invalid and RBS_CMN_IsAssociativeArray(compareValue)
1285 | compareValue = compareValue.LookupCI(compareAttribute)
1286 | end If
1287 | if RBS_CMN_IsString(compareValue) and RBS_CMN_IsString(value) and not caseSensitive
1288 | if LCase(compareValue) = LCase(value)
1289 | return i
1290 | end If
1291 | else if compareValue = value
1292 | return i
1293 | end if
1294 | item = array[i]
1295 | next
1296 | end if
1297 | return -1
1298 | end function
1299 | function RBS_CMN_ArrayContains(array as Object, value as Object, compareAttribute = invalid as Dynamic) as Boolean
1300 | return (RBS_CMN_FindElementIndexInArray(array, value, compareAttribute) > -1)
1301 | end function
1302 | function RBS_CMN_FindElementIndexInNode(node as Object, value as Object) as Integer
1303 | if type(node) = "roSGNode"
1304 | for i = 0 to node.getChildCount() - 1
1305 | compareValue = node.getChild(i)
1306 | if type(compareValue) = "roSGNode" and compareValue.isSameNode(value)
1307 | return i
1308 | end if
1309 | next
1310 | end if
1311 | return -1
1312 | end function
1313 | function RBS_CMN_NodeContains(node as Object, value as Object) as Boolean
1314 | return (RBS_CMN_FindElementIndexInNode(node, value) > -1)
1315 | end function
1316 | function UnitTestItGroup(name as string, isSolo as boolean, isIgnored as boolean)
1317 | this = {}
1318 | this.testCases = createObject("roArray", 0, true)
1319 | this.ignoredTestCases = CreateObject("roArray",0, true)
1320 | this.soloTestCases = CreateObject("roArray",0, true)
1321 | this.testCaseLookup = {}
1322 | this.setupFunction = invalid
1323 | this.setupFunctionName = ""
1324 | this.tearDownFunction = invalid
1325 | this.tearDownFunctionName = ""
1326 | this.tearDownFunctionName = ""
1327 | this.beforeEachFunction = invalid
1328 | this.beforeEachFunctionName = ""
1329 | this.afterEachFunction = invalid
1330 | this.afterEachFunctionName = ""
1331 | this.isSolo = isSolo
1332 | this.isIgnored = isIgnored
1333 | this.hasSoloTests = false
1334 | this.name = name
1335 | this.AddTestCase = RBS_ItG_AddTestCase
1336 | return this
1337 | end function
1338 | function RBS_ItG_AddTestCase(testCase)
1339 | if (testCase.isSolo)
1340 | m.hasSoloTestCases = true
1341 | m.soloTestCases.push(testCase)
1342 | m.hasSoloTests = true
1343 | else if (testCase.isIgnored)
1344 | m.ignoredTestCases.push(testCase)
1345 | else
1346 | m.testCases.push(testCase)
1347 | end if
1348 | end function
1349 | function RBS_ItG_GetTestCases(group) as object
1350 | if (group.hasSoloTests)
1351 | return group.soloTestCases
1352 | else
1353 | return group.testCases
1354 | end if
1355 | end function
1356 | function RBS_ItG_GetRunnableTestSuite(group) as object
1357 | testCases = RBS_ItG_GetTestCases(group)
1358 | runnableSuite = BaseTestSuite()
1359 | runnableSuite.name = group.name
1360 | runnableSuite.isLegacy = group.isLegacy = true
1361 | for each testCase in testCases
1362 | name = testCase.name
1363 | if (testCase.isSolo)
1364 | name += " [SOLO] "
1365 | end if
1366 | runnableSuite.addTest(name, testCase.func, testCase.funcName)
1367 | group.testCaseLookup[name] = testCase
1368 | end for
1369 | runnableSuite.SetUp = RBS_CMN_GetFunction(group.setupFunction, group.setupFunctionName)
1370 | runnableSuite.TearDown = RBS_CMN_GetFunction(group.teardownFunction, group.teardownFunctionName)
1371 | runnableSuite.BeforeEach = RBS_CMN_GetFunction(group.beforeEachFunction, group.beforeEachFunctionName)
1372 | runnableSuite.AftrEach = RBS_CMN_GetFunction(group.afterEachFunction, group.afterEachFunctionName)
1373 | return runnableSuite
1374 | end function
1375 | Function ItemGenerator(scheme as object) as Object
1376 | this = {}
1377 | this.getItem = RBS_IG_GetItem
1378 | this.getAssocArray = RBS_IG_GetAssocArray
1379 | this.getArray = RBS_IG_GetArray
1380 | this.getSimpleType = RBS_IG_GetSimpleType
1381 | this.getInteger = RBS_IG_GetInteger
1382 | this.getFloat = RBS_IG_GetFloat
1383 | this.getString = RBS_IG_GetString
1384 | this.getBoolean = RBS_IG_GetBoolean
1385 | if not RBS_CMN_IsValid(scheme)
1386 | return invalid
1387 | end if
1388 | return this.getItem(scheme)
1389 | End Function
1390 | Function RBS_IG_GetItem(scheme as object) as object
1391 | item = invalid
1392 | if RBS_CMN_IsAssociativeArray(scheme)
1393 | item = m.getAssocArray(scheme)
1394 | else if RBS_CMN_IsArray(scheme)
1395 | item = m.getArray(scheme)
1396 | else if RBS_CMN_IsString(scheme)
1397 | item = m.getSimpleType(lCase(scheme))
1398 | end if
1399 | return item
1400 | End Function
1401 | Function RBS_IG_GetAssocArray(scheme as object) as object
1402 | item = {}
1403 | for each key in scheme
1404 | if not item.DoesExist(key)
1405 | item[key] = m.getItem(scheme[key])
1406 | end if
1407 | end for
1408 | return item
1409 | End Function
1410 | Function RBS_IG_GetArray(scheme as object) as object
1411 | item = []
1412 | for each key in scheme
1413 | item.Push(m.getItem(key))
1414 | end for
1415 | return item
1416 | End Function
1417 | Function RBS_IG_GetSimpleType(typeStr as string) as object
1418 | item = invalid
1419 | if typeStr = "integer" or typeStr = "int" or typeStr = "roint"
1420 | item = m.getInteger()
1421 | else if typeStr = "float" or typeStr = "rofloat"
1422 | item = m.getFloat()
1423 | else if typeStr = "string" or typeStr = "rostring"
1424 | item = m.getString(10)
1425 | else if typeStr = "boolean" or typeStr = "roboolean"
1426 | item = m.getBoolean()
1427 | end if
1428 | return item
1429 | End Function
1430 | Function RBS_IG_GetBoolean() as boolean
1431 | return RBS_CMN_AsBoolean(Rnd(2) \ Rnd(2))
1432 | End Function
1433 | Function RBS_IG_GetInteger(seed = 100 as integer) as integer
1434 | return Rnd(seed)
1435 | End Function
1436 | Function RBS_IG_GetFloat() as float
1437 | return Rnd(0)
1438 | End Function
1439 | Function RBS_IG_GetString(seed as integer) as string
1440 | item = ""
1441 | if seed > 0
1442 | stringLength = Rnd(seed)
1443 | for i = 0 to stringLength
1444 | chType = Rnd(3)
1445 | if chType = 1 'Chr(48-57) - numbers
1446 | chNumber = 47 + Rnd(10)
1447 | else if chType = 2 'Chr(65-90) - Uppercase Letters
1448 | chNumber = 64 + Rnd(26)
1449 | else 'Chr(97-122) - Lowercase Letters
1450 | chNumber = 96 + Rnd(26)
1451 | end if
1452 | item = item + Chr(chNumber)
1453 | end for
1454 | end if
1455 | return item
1456 | End Function
1457 | function UnitTestRuntimeConfig(testsDirectory, maxLinesWithoutSuiteDirective, supportLegacyTests = false)
1458 | this = {}
1459 | this.testsDirectory = testsDirectory
1460 | this.CreateSuites = RBS_CreateSuites
1461 | this.hasSoloSuites = false
1462 | this.hasSoloGroups = false
1463 | this.hasSoloTests = false
1464 | this.suites = this.CreateSuites(this.testsDirectory, maxLinesWithoutSuiteDirective, supportLegacyTests)
1465 | return this
1466 | end function
1467 | function RBS_CreateSuites(testsDirectory, maxLinesWithoutSuiteDirective, supportLegacyTests )
1468 | result = CreateObject("roArray", 0, true)
1469 | testsFileRegex = CreateObject("roRegex", "^[0-9a-z\_]*\.brs$", "i")
1470 | if testsDirectory <> ""
1471 | fileSystem = CreateObject("roFileSystem")
1472 | listing = fileSystem.GetDirectoryListing(testsDirectory)
1473 | for each item in listing
1474 | itemPath = testsDirectory + "/" + item
1475 | itemStat = fileSystem.Stat(itemPath)
1476 | if itemStat.type = "directory" then
1477 | result.append(m.CreateSuites(itemPath, maxLinesWithoutSuiteDirective, supportLegacyTests ))
1478 | else if testsFileRegex.IsMatch(item) then
1479 | suite = UnitTestSuite(itemPath, maxLinesWithoutSuiteDirective, supportLegacyTests)
1480 | if (suite.isValid)
1481 | if (suite.isSolo)
1482 | m.hasSoloSuites = true
1483 | end if
1484 | if (suite.hasSoloTests)
1485 | m.hasSoloTests = true
1486 | end if
1487 | if (suite.hasSoloGroups)
1488 | m.hasSoloGroups = true
1489 | end if
1490 | result.Push(suite)
1491 | else
1492 | end if
1493 | end if
1494 | end for
1495 | end if
1496 | return result
1497 | end function
1498 | function RBS_STATS_CreateTotalStatistic() as Object
1499 | statTotalItem = {
1500 | Suites : []
1501 | Time : 0
1502 | Total : 0
1503 | Correct : 0
1504 | Fail : 0
1505 | Ignored : 0
1506 | Crash : 0
1507 | IgnoredTestNames: []
1508 | }
1509 | return statTotalItem
1510 | end function
1511 | function RBS_STATS_MergeTotalStatistic(stat1, stat2) as void
1512 | for each suite in stat2.Suites
1513 | stat1.Suites.push(suite)
1514 | end for
1515 | stat1.Time += stat2.Time
1516 | stat1.Total += stat2.Total
1517 | stat1.Correct += stat2.Correct
1518 | stat1.Fail += stat2.Fail
1519 | stat1.Crash += stat2.Crash
1520 | stat1.Ignored += stat2.Ignored
1521 | stat1.IgnoredTestNames.append(stat2.IgnoredTestNames)
1522 | end function
1523 | function RBS_STATS_CreateSuiteStatistic(name as String) as Object
1524 | statSuiteItem = {
1525 | Name : name
1526 | Tests : []
1527 | Time : 0
1528 | Total : 0
1529 | Correct : 0
1530 | Fail : 0
1531 | Crash : 0
1532 | Ignored : 0
1533 | IgnoredTestNames:[]
1534 | }
1535 | return statSuiteItem
1536 | end function
1537 | function RBS_STATS_CreateTestStatistic(name as String, result = "Success" as String, time = 0 as Integer, errorCode = 0 as Integer, errorMessage = "" as String) as Object
1538 | statTestItem = {
1539 | Name : name
1540 | Result : result
1541 | Time : time
1542 | Error : {
1543 | Code : errorCode
1544 | Message : errorMessage
1545 | }
1546 | }
1547 | return statTestItem
1548 | end function
1549 | sub RBS_STATS_AppendTestStatistic(statSuiteObj as Object, statTestObj as Object)
1550 | if RBS_CMN_IsAssociativeArray(statSuiteObj) and RBS_CMN_IsAssociativeArray(statTestObj)
1551 | statSuiteObj.Tests.Push(statTestObj)
1552 | if RBS_CMN_IsInteger(statTestObj.time)
1553 | statSuiteObj.Time = statSuiteObj.Time + statTestObj.Time
1554 | end if
1555 | statSuiteObj.Total = statSuiteObj.Total + 1
1556 | if lCase(statTestObj.Result) = "success"
1557 | statSuiteObj.Correct = statSuiteObj.Correct + 1
1558 | else if lCase(statTestObj.result) = "fail"
1559 | statSuiteObj.Fail = statSuiteObj.Fail + 1
1560 | else
1561 | statSuiteObj.crash = statSuiteObj.crash + 1
1562 | end if
1563 | end if
1564 | end sub
1565 | sub RBS_STATS_AppendSuiteStatistic(statTotalObj as Object, statSuiteObj as Object)
1566 | if RBS_CMN_IsAssociativeArray(statTotalObj) and RBS_CMN_IsAssociativeArray(statSuiteObj)
1567 | statTotalObj.Suites.Push(statSuiteObj)
1568 | statTotalObj.Time = statTotalObj.Time + statSuiteObj.Time
1569 | if RBS_CMN_IsInteger(statSuiteObj.Total)
1570 | statTotalObj.Total = statTotalObj.Total + statSuiteObj.Total
1571 | end if
1572 | if RBS_CMN_IsInteger(statSuiteObj.Correct)
1573 | statTotalObj.Correct = statTotalObj.Correct + statSuiteObj.Correct
1574 | end if
1575 | if RBS_CMN_IsInteger(statSuiteObj.Fail)
1576 | statTotalObj.Fail = statTotalObj.Fail + statSuiteObj.Fail
1577 | end if
1578 | if RBS_CMN_IsInteger(statSuiteObj.Crash)
1579 | statTotalObj.Crash = statTotalObj.Crash + statSuiteObj.Crash
1580 | end if
1581 | end if
1582 | end sub
1583 | function UnitTestCase(name as string, func as dynamic, funcName as string, isSolo as boolean, isIgnored as boolean, lineNumber as integer, params = invalid, paramTestIndex =0)
1584 | this = {}
1585 | this.isSolo = isSolo
1586 | this.func = func
1587 | this.funcName = funcName
1588 | this.isIgnored = isIgnored
1589 | this.name = name
1590 | this.lineNumber = lineNumber
1591 | this.assertIndex = 0
1592 | this.assertLineNumberMap = {}
1593 | this.AddAssertLine = RBS_TC_AddAssertLine
1594 | this.getTestLineIndex = 0
1595 | this.rawParams = params
1596 | this.paramTestIndex = paramTestIndex
1597 | this.isParamTest = false
1598 | if (params <> invalid)
1599 | this.name += stri(this.paramTestIndex)
1600 | end if
1601 | return this
1602 | end function
1603 | function RBS_TC_AddAssertLine(lineNumber as integer)
1604 | m.assertLineNumberMap[stri(m.assertIndex).trim()] = lineNumber
1605 | m.assertIndex++
1606 | end function
1607 | function RBS_TC_GetAssertLine(testCase, index)
1608 | if (testCase.assertLineNumberMap.doesExist(stri(index).trim()))
1609 | return testCase.assertLineNumberMap[stri(index).trim()]
1610 | else
1611 | return testCase.lineNumber
1612 | end if
1613 | end function
1614 | function Logger(config) as Object
1615 | this = {}
1616 | this.config = config
1617 | this.verbosityLevel = {
1618 | basic : 0
1619 | normal : 1
1620 | verbose : 2
1621 | }
1622 | this.verbosity = this.config.logLevel
1623 | this.PrintStatistic = RBS_LOGGER_PrintStatistic
1624 | this.PrintMetaSuiteStart = RBS_LOGGER_PrintMetaSuiteStart
1625 | this.PrintSuiteStatistic = RBS_LOGGER_PrintSuiteStatistic
1626 | this.PrintTestStatistic = RBS_LOGGER_PrintTestStatistic
1627 | this.PrintStart = RBS_LOGGER_PrintStart
1628 | this.PrintEnd = RBS_LOGGER_PrintEnd
1629 | this.PrintSuiteStart = RBS_LOGGER_PrintSuiteStart
1630 | return this
1631 | end function
1632 | sub RBS_LOGGER_PrintStatistic(statObj as Object)
1633 | m.PrintStart()
1634 | previousfile = invalid
1635 | for each testSuite in statObj.Suites
1636 | if (not statObj.testRunHasFailures or ((not m.config.showOnlyFailures) OR testSuite.fail > 0 or testSuite.crash > 0))
1637 | if (testSuite.metaTestSuite.filePath <> previousfile)
1638 | m.PrintMetaSuiteStart(testSuite.metaTestSuite)
1639 | previousfile = testSuite.metaTestSuite.filePath
1640 | end if
1641 | m.PrintSuiteStatistic(testSuite, statObj.testRunHasFailures)
1642 | end if
1643 | end for
1644 | ? ""
1645 | m.PrintEnd()
1646 | ? "Total = "; RBS_CMN_AsString(statObj.Total); " ; Passed = "; statObj.Correct; " ; Failed = "; statObj.Fail; " ; Ignored = "; statObj.Ignored
1647 | ? " Time spent: "; statObj.Time; "ms"
1648 | ? ""
1649 | ? ""
1650 | if (statObj.ignored > 0)
1651 | ? "IGNORED TESTS:"
1652 | for each ignoredItemName in statObj.IgnoredTestNames
1653 | print ignoredItemName
1654 | end for
1655 | end if
1656 | if (statObj.Total = statObj.Correct)
1657 | overrallResult = "Success"
1658 | else
1659 | overrallResult = "Fail"
1660 | end if
1661 | ? "RESULT: "; overrallResult
1662 | end sub
1663 | sub RBS_LOGGER_PrintSuiteStatistic(statSuiteObj as Object, hasFailures)
1664 | m.PrintSuiteStart(statSuiteObj.Name)
1665 | for each testCase in statSuiteObj.Tests
1666 | if (not hasFailures or ((not m.config.showOnlyFailures) OR testCase.Result <> "Success"))
1667 | m.PrintTestStatistic(testCase)
1668 | end if
1669 | end for
1670 | ? " |"
1671 | end sub
1672 | sub RBS_LOGGER_PrintTestStatistic(testCase as Object)
1673 | metaTestCase = testCase.metaTestCase
1674 | if (LCase(testCase.Result) <> "success")
1675 | testChar = "-"
1676 | assertIndex = metaTestCase.testResult.failedAssertIndex
1677 | locationLine = StrI(RBS_TC_GetAssertLine(metaTestCase,assertIndex)).trim()
1678 | else
1679 | testChar = "|"
1680 | locationLine = StrI(metaTestCase.lineNumber).trim()
1681 | end if
1682 | locationText = testCase.filePath.trim() + "(" + locationLine + ")"
1683 | insetText = ""
1684 | if (not metaTestcase.isParamTest)
1685 | messageLine = RBS_LOGGER_FillText(" " + testChar + " |--" + metaTestCase.Name + " : ", ".", 80)
1686 | ? messageLine ; testCase.Result ; " " ;locationText
1687 | else if ( metaTestcase.paramTestIndex = 0)
1688 | name = metaTestCase.Name
1689 | if (len(name) > 1 and right(name, 1) = "0")
1690 | name = left(name, len(name) - 1)
1691 | end if
1692 | ? " " + testChar + " |--" + name+ " : "
1693 | end if
1694 | if (metaTestcase.isParamTest)
1695 | insetText = " "
1696 | messageLine = RBS_LOGGER_FillText(" " + testChar + insetText + " |--" + metaTestCase.rawParams + " : ", ".", 80)
1697 | ? messageLine ; testCase.Result ; " " ;locationText
1698 | end if
1699 | if LCase(testCase.Result) <> "success"
1700 | ? " | "; insettext ;" |--Location: "; locationText
1701 | ? " | "; insettext ;" |--Error Message: "; testCase.Error.Message
1702 | end if
1703 | end sub
1704 | function RBS_LOGGER_FillText(text as string, fillChar = " ", numChars = 40) as string
1705 | if (len(text) >= numChars)
1706 | text = left(text, numChars - 5) + "..." + fillChar + fillChar
1707 | else
1708 | numToFill= numChars - len(text)
1709 | for i = 0 to numToFill
1710 | text += fillChar
1711 | end for
1712 | end if
1713 | return text
1714 | end function
1715 | sub RBS_LOGGER_PrintStart()
1716 | ? ""
1717 | ? "[START TEST REPORT]"
1718 | ? ""
1719 | end sub
1720 | sub RBS_LOGGER_PrintEnd()
1721 | ? ""
1722 | ? "[END TEST REPORT]"
1723 | ? ""
1724 | end sub
1725 | sub RBS_LOGGER_PrintSuiteSetUp(sName as String)
1726 | if m.verbosity = m.verbosityLevel.verbose
1727 | ? "================================================================="
1728 | ? "=== SetUp "; sName; " suite."
1729 | ? "================================================================="
1730 | end if
1731 | end sub
1732 | sub RBS_LOGGER_PrintMetaSuiteStart(metaTestSuite)
1733 | ? metaTestSuite.name; " (" ; metaTestSuite.filePath + "(1))"
1734 | end sub
1735 | sub RBS_LOGGER_PrintSuiteStart(sName as String)
1736 | ? " |-" ; sName
1737 | end sub
1738 | sub RBS_LOGGER_PrintSuiteTearDown(sName as String)
1739 | if m.verbosity = m.verbosityLevel.verbose
1740 | ? "================================================================="
1741 | ? "=== TearDown "; sName; " suite."
1742 | ? "================================================================="
1743 | end if
1744 | end sub
1745 | sub RBS_LOGGER_PrintTestSetUp(tName as String)
1746 | if m.verbosity = m.verbosityLevel.verbose
1747 | ? "----------------------------------------------------------------"
1748 | ? "--- SetUp "; tName; " test."
1749 | ? "----------------------------------------------------------------"
1750 | end if
1751 | end sub
1752 | sub RBS_LOGGER_PrintTestTearDown(tName as String)
1753 | if m.verbosity = m.verbosityLevel.verbose
1754 | ? "----------------------------------------------------------------"
1755 | ? "--- TearDown "; tName; " test."
1756 | ? "----------------------------------------------------------------"
1757 | end if
1758 | end sub
1759 | function UnitTestResult() as object
1760 | this = {}
1761 | this.messages = CreateObject("roArray", 0, true)
1762 | this.isFail = false
1763 | this.currentAssertIndex = 0
1764 | this.failedAssertIndex = 0
1765 | this.Reset = RBS_TRes_Reset
1766 | this.AddResult = RBS_TRes_AddResult
1767 | this.GetResult = RBS_TRes_GetResult
1768 | return this
1769 | end function
1770 | function RBS_TRes_Reset() as void
1771 | m.isFail = false
1772 | m.messages = CreateObject("roArray", 0, true)
1773 | end function
1774 | function RBS_TRes_AddResult(message as string) as string
1775 | if (message <> "")
1776 | m.messages.push(message)
1777 | if (not m.isFail)
1778 | m.failedAssertIndex = m.currentAssertIndex
1779 | end if
1780 | m.isFail = true
1781 | end if
1782 | m.currentAssertIndex++
1783 | return message
1784 | end function
1785 | function RBS_TRes_GetResult() as string
1786 | if (m.isFail)
1787 | msg = m.messages.peek()
1788 | if (msg <> invalid)
1789 | return msg
1790 | else
1791 | return "unknown test failure"
1792 | end if
1793 | else
1794 | return ""
1795 | end if
1796 | end function
1797 | function RBS_TR_TestRunner(args = {}) as Object
1798 | this = {}
1799 | this.testScene = args.testScene
1800 | fs = CreateObject("roFileSystem")
1801 | defaultConfig = {
1802 | logLevel : 1,
1803 | testsDirectory: "pkg:/source/Tests",
1804 | testFilePrefix: "Test__",
1805 | failFast: false,
1806 | showOnlyFailures: false,
1807 | maxLinesWithoutSuiteDirective: 100
1808 | }
1809 | rawConfig = invalid
1810 | config = invalid
1811 | if (args.testConfigPath <> invalid and fs.Exists(args.testConfigPath))
1812 | ? "Loading test config from " ; args.testConfigPath
1813 | rawConfig = ReadAsciiFile(args.testConfigPath)
1814 | else if (fs.Exists("pkg:/source/tests/testconfig.json"))
1815 | ? "Loading test config from default location : pkg:/source/tests/testconfig.json"
1816 | rawConfig = ReadAsciiFile("pkg:/source/tests/testconfig.json")
1817 | else
1818 | ? "None of the testConfig.json locations existed"
1819 | end if
1820 | if (rawConfig <> invalid)
1821 | config = ParseJson(rawConfig)
1822 | end if
1823 | if (config = invalid or not RBS_CMN_IsAssociativeArray(config) or RBS_CMN_IsNotEmptyString(config.rawtestsDirectory))
1824 | ? "WARNING : specified config is invalid - using default"
1825 | config = defaultConfig
1826 | end if
1827 | if (args.showOnlyFailures <> invalid)
1828 | config.showOnlyFailures = args.showOnlyFailures = "true"
1829 | end if
1830 | if (args.failFast <> invalid)
1831 | config.failFast = args.failFast = "true"
1832 | end if
1833 | this.testUtilsDecoratorMethodName = args.testUtilsDecoratorMethodName
1834 | this.config = config
1835 | this.config.testsDirectory = config.testsDirectory
1836 | this.logger = Logger(this.config)
1837 | this.Run = RBS_TR_Run
1838 | return this
1839 | end function
1840 | sub RBS_TR_Run()
1841 | totalStatObj = RBS_STATS_CreateTotalStatistic()
1842 | m.runtimeConfig = UnitTestRuntimeConfig(m.config.testsDirectory, m.config.maxLinesWithoutSuiteDirective, m.config.supportLegacyTests = true)
1843 | totalStatObj.testRunHasFailures = false
1844 | for each metaTestSuite in m.runtimeConfig.suites
1845 | if (m.runtimeConfig.hasSoloTests )
1846 | if (not metaTestSuite.hasSoloTests)
1847 | if (m.config.logLevel = 2)
1848 | ? "TestSuite " ; metaTestSuite.name ; " Is filtered because it has no solo tests"
1849 | end if
1850 | goto skipSuite
1851 | end if
1852 | else if (m.runtimeConfig.hasSoloSuites)
1853 | if (not metaTestSuite.isSolo)
1854 | if (m.config.logLevel = 2)
1855 | ? "TestSuite " ; metaTestSuite.name ; " Is filtered due to solo flag"
1856 | end if
1857 | goto skipSuite
1858 | end if
1859 | end if
1860 | if (metaTestSuite.isIgnored)
1861 | if (m.config.logLevel = 2)
1862 | ? "Ignoring TestSuite " ; metaTestSuite.name ; " Due to Ignore flag"
1863 | end if
1864 | totalstatobj.ignored ++
1865 | totalStatObj.IgnoredTestNames.push("|-" + metaTestSuite.name + " [WHOLE SUITE]")
1866 | goto skipSuite
1867 | end if
1868 | if (metaTestSuite.isNodeTest and metaTestSuite.nodeTestFileName <> "")
1869 | ? " +++++RUNNING NODE TEST"
1870 | nodeType = metaTestSuite.nodeTestFileName
1871 | ? " node type is " ; nodeType
1872 | node = m.testScene.CallFunc("Rooibos_CreateTestNode", nodeType)
1873 | if (type(node) = "roSGNode" and node.subType() = nodeType)
1874 | args = {
1875 | "metaTestSuite": metaTestSuite
1876 | "testUtilsDecoratorMethodName": m.testUtilsDecoratorMethodName
1877 | "config": m.config
1878 | "runtimeConfig": m.runtimeConfig
1879 | }
1880 | nodeStatResults = node.callFunc("Rooibos_RunNodeTests", args)
1881 | RBS_STATS_MergeTotalStatistic(totalStatObj, nodeStatResults)
1882 | m.testScene.RemoveChild(node)
1883 | else
1884 | ? " ERROR!! - could not create node required to execute tests for " ; metaTestSuite.name
1885 | ? " Node of type " ; nodeType ; " was not found/could not be instantiated"
1886 | end if
1887 | else
1888 | if (metaTestSuite.hasIgnoredTests)
1889 | totalStatObj.IgnoredTestNames.push("|-" + metaTestSuite.name)
1890 | end if
1891 | RBS_RT_RunItGroups(metaTestSuite, totalStatObj, m.testUtilsDecoratorMethodName, m.config, m.runtimeConfig)
1892 | end if
1893 | skipSuite:
1894 | end for
1895 | m.logger.PrintStatistic(totalStatObj)
1896 | RBS_TR_SendHomeKeypress()
1897 | end sub
1898 | sub RBS_RT_RunItGroups(metaTestSuite, totalStatObj, testUtilsDecoratorMethodName, config, runtimeConfig, nodeContext = invalid)
1899 | for each itGroup in metaTestSuite.itGroups
1900 | testSuite = RBS_ItG_GetRunnableTestSuite(itGroup)
1901 | if (nodeContext <> invalid)
1902 | testSuite.node = nodeContext
1903 | testSuite.global = nodeContext.global
1904 | testSuite.top = nodeContext.top
1905 | end if
1906 | if (testUtilsDecoratorMethodName <> invalid)
1907 | testUtilsDecorator = RBS_CMN_GetFunction(invalid, testUtilsDecoratorMethodName)
1908 | if (RBS_CMN_IsFunction(testUtilsDecorator))
1909 | testUtilsDecorator(testSuite)
1910 | else
1911 | ? "Test utils decorator method `" ; testUtilsDecoratorMethodName ;"` was not in scope!"
1912 | end if
1913 | end if
1914 | totalStatObj.Ignored += itGroup.ignoredTestCases.count()
1915 | if (itGroup.isIgnored)
1916 | if (config.logLevel = 2)
1917 | ? "Ignoring itGroup " ; itGroup.name ; " Due to Ignore flag"
1918 | end if
1919 | totalStatObj.ignored += itGroup.testCases.count()
1920 | totalStatObj.IgnoredTestNames.push(" |-" + itGroup.name + " [WHOLE GROUP]")
1921 | goto skipItGroup
1922 | else
1923 | if (itGroup.ignoredTestCases.count() > 0)
1924 | totalStatObj.IgnoredTestNames.push(" |-" + itGroup.name)
1925 | totalStatObj.ignored += itGroup.ignoredTestCases.count()
1926 | for each testCase in itGroup.ignoredTestCases
1927 | if (not testcase.isParamTest)
1928 | totalStatObj.IgnoredTestNames.push(" | |--" + testCase.name)
1929 | else if (testcase.paramTestIndex = 0)
1930 | testCaseName = testCase.Name
1931 | if (len(testCaseName) > 1 and right(testCaseName, 1) = "0")
1932 | testCaseName = left(testCaseName, len(testCaseName) - 1)
1933 | end if
1934 | totalStatObj.IgnoredTestNames.push(" | |--" + testCaseName)
1935 | end if
1936 | end for
1937 | end if
1938 | end if
1939 | if (runtimeConfig.hasSoloTests)
1940 | if (not itGroup.hasSoloTests)
1941 | if (config.logLevel = 2)
1942 | ? "Ignoring itGroup " ; itGroup.name ; " Because it has no solo tests"
1943 | end if
1944 | goto skipItGroup
1945 | end if
1946 | else if (runtimeConfig.hasSoloGroups)
1947 | if (not itGroup.isSolo)
1948 | goto skipItGroup
1949 | end if
1950 | end if
1951 | if (testSuite.testCases.Count() = 0)
1952 | if (config.logLevel = 2)
1953 | ? "Ignoring TestSuite " ; itGroup.name ; " - NO TEST CASES"
1954 | end if
1955 | goto skipItGroup
1956 | end if
1957 | if RBS_CMN_IsFunction(testSuite.SetUp)
1958 | testSuite.SetUp()
1959 | end if
1960 | RBS_RT_RunTestCases(metaTestSuite, itGroup, testSuite, totalStatObj, config, runtimeConfig)
1961 | if RBS_CMN_IsFunction(testSuite.TearDown)
1962 | testSuite.TearDown()
1963 | end if
1964 | if (totalStatObj.testRunHasFailures = true and config.failFast = true)
1965 | exit for
1966 | end if
1967 | skipItGroup:
1968 | end for
1969 | end sub
1970 | sub RBS_RT_RunTestCases(metaTestSuite, itGroup, testSuite, totalStatObj, config, runtimeConfig)
1971 | suiteStatObj = RBS_STATS_CreateSuiteStatistic(itGroup.Name)
1972 | for each testCase in testSuite.testCases
1973 | metaTestCase = itGroup.testCaseLookup[testCase.Name]
1974 | if (runtimeConfig.hasSoloTests and not metaTestCase.isSolo)
1975 | goto skipTestCase
1976 | end if
1977 | if RBS_CMN_IsFunction(testSuite.beforeEach)
1978 | testSuite.beforeEach()
1979 | end if
1980 | testTimer = CreateObject("roTimespan")
1981 | testStatObj = RBS_STATS_CreateTestStatistic(testCase.Name)
1982 | testSuite.testCase = testCase.Func
1983 | testStatObj.filePath = metaTestSuite.filePath
1984 | testStatObj.metaTestCase = metaTestCase
1985 | testSuite.currentResult = UnitTestResult()
1986 | testStatObj.metaTestCase.testResult = testSuite.currentResult
1987 | if (metaTestCase.rawParams <> invalid)
1988 | testCaseParams = invalid
1989 | isSucess = eval("testCaseParams = " + metaTestCase.rawParams)
1990 | argsValid = RBS_CMN_IsArray(testCaseParams)
1991 | if (argsValid)
1992 | if (testCaseParams.count() = 1)
1993 | testSuite.testCase(testCaseParams[0])
1994 | else if (testCaseParams.count() = 2)
1995 | testSuite.testCase(testCaseParams[0], testCaseParams[1])
1996 | else if (testCaseParams.count() = 3)
1997 | testSuite.testCase(testCaseParams[0], testCaseParams[1], testCaseParams[2])
1998 | else if (testCaseParams.count() = 4)
1999 | testSuite.testCase(testCaseParams[0], testCaseParams[1], testCaseParams[2], testCaseParams[3])
2000 | else if (testCaseParams.count() = 5)
2001 | testSuite.testCase(testCaseParams[0], testCaseParams[1], testCaseParams[2], testCaseParams[3], testCaseParams[4])
2002 | else if (testCaseParams.count() = 6)
2003 | testSuite.testCase(testCaseParams[0], testCaseParams[1], testCaseParams[2], testCaseParams[3], testCaseParams[4], testCaseParams[5])
2004 | end if
2005 | else
2006 | ? "Could not parse args for test " ; testCase.name
2007 | testSuite.Fail("Could not parse args for test ")
2008 | end if
2009 | else
2010 | testSuite.testCase()
2011 | end if
2012 | testSuite.AssertMocks()
2013 | testSuite.CleanMocks()
2014 | testSuite.CleanStubs()
2015 | runResult = testSuite.currentResult.GetResult()
2016 | if runResult <> ""
2017 | testStatObj.Result = "Fail"
2018 | testStatObj.Error.Code = 1
2019 | testStatObj.Error.Message = runResult
2020 | else
2021 | testStatObj.Result = "Success"
2022 | end if
2023 | testStatObj.Time = testTimer.TotalMilliseconds()
2024 | RBS_STATS_AppendTestStatistic(suiteStatObj, testStatObj)
2025 | if RBS_CMN_IsFunction(testCase.afterEach)
2026 | testSuite.afterEach()
2027 | end if
2028 | if testStatObj.Result <> "Success"
2029 | totalStatObj.testRunHasFailures = true
2030 | end if
2031 | if testStatObj.Result = "Fail" and config.failFast = true
2032 | exit for
2033 | end if
2034 | skipTestCase:
2035 | end for
2036 | suiteStatObj.metaTestSuite = metaTestSuite
2037 | RBS_STATS_AppendSuiteStatistic(totalStatObj, suiteStatObj)
2038 | end sub
2039 | sub RBS_TR_SendHomeKeypress()
2040 | ut = CreateObject("roUrlTransfer")
2041 | ut.SetUrl("http://localhost:8060/keypress/Home")
2042 | ut.PostFromString("")
2043 | end sub
2044 | function Rooibos_RunNodeTests(args) as Object
2045 | ? " RUNNING NODE TESTS"
2046 | totalStatObj = RBS_STATS_CreateTotalStatistic()
2047 | RBS_RT_RunItGroups(args.metaTestSuite, totalStatObj, args.testUtilsDecoratorMethodName, args.config, args.runtimeConfig, m)
2048 | return totalStatObj
2049 | end function
2050 | Function Rooibos_CreateTestNode(nodeType) as Object
2051 | node = createObject("roSGNode", nodeType)
2052 | if (type(node) = "roSGNode" and node.subType() = nodeType)
2053 | m.top.AppendChild(node)
2054 | return node
2055 | else
2056 | ? " Error creating test node of type " ; nodeType
2057 | return invalid
2058 | end if
2059 | End Function
2060 | function UnitTestSuite(filePath as string, maxLinesWithoutSuiteDirective = 100, supportLegacyTests = false)
2061 | this = {}
2062 | this.filePath = filePath
2063 | this.name = ""
2064 | this.valid = false
2065 | this.hasFailures = false
2066 | this.hasSoloTests = false
2067 | this.hasIgnoredTests = false
2068 | this.hasSoloGroups = false
2069 | this.isSolo = false
2070 | this.isIgnored = false
2071 | this.itGroups = CreateObject("roArray",0, true)
2072 | this.setupFunction = invalid
2073 | this.setupFunctionName = ""
2074 | this.tearDownFunction = invalid
2075 | this.tearDownFunctionName = ""
2076 | this.isNodeTest = false
2077 | this.nodeTestFileName = ""
2078 | this.ProcessSuite = RBS_TS_ProcessSuite
2079 | this.ResetCurrentTestCase = RBS_TS_ResetCurrentTestCase
2080 | this.ProcessLegacySuite = RBS_TS_ProcessLegacySuite
2081 | this.ProcessSuite(maxLinesWithoutSuiteDirective, supportLegacyTests )
2082 | this.currentGroup = invalid
2083 | return this
2084 | end function
2085 | function RBS_TS_ProcessSuite(maxLinesWithoutSuiteDirective, supportLegacyTests )
2086 | code = RBS_CMN_AsString(ReadAsciiFile(m.filePath))
2087 | isTestSuite = false
2088 | TAG_TEST_SUITE = "'@TestSuite"
2089 | TAG_IT = "'@It"
2090 | TAG_IGNORE = "'@Ignore"
2091 | TAG_SOLO = "'@Only"
2092 | TAG_TEST = "'@Test"
2093 | TAG_NODE_TEST = "'@SGNode"
2094 | TAG_SETUP = "'@Setup"
2095 | TAG_TEAR_DOWN = "'@TearDown"
2096 | TAG_BEFORE_EACH = "'@BeforeEach"
2097 | TAG_AFTER_EACH = "'@AfterEach"
2098 | TAG_TEST_PARAMS = "'@Params"
2099 | TAG_TEST_IGNORE_PARAMS = "'@IgnoreParams"
2100 | TAG_TEST_SOLO_PARAMS = "'@OnlyParams"
2101 | functionNameRegex = CreateObject("roRegex", "^(function|sub)\s([0-9a-z\_]*)\s*\(", "i")
2102 | assertInvocationRegex = CreateObject("roRegex", "^\s*(m.fail|m.Fail|m.assert|m.Assert)(.*)\(", "i")
2103 | functionEndRegex = CreateObject("roRegex", "^\s*(end sub|end function)", "i")
2104 | if code <> ""
2105 | isTokenItGroup = false
2106 | isNextTokenIgnore = false
2107 | isNextTokenSolo = false
2108 | isNextTokenTest = false
2109 | isTestSuite = false
2110 | isNextTokenSetup = false
2111 | isNextTokenTeardown = false
2112 | isNextTokenBeforeEach = false
2113 | isNextTokenAfterEach = false
2114 | isNextTokenNodeTest = false
2115 | isNextTokenTestCaseParam = false
2116 | nodeTestFileName = ""
2117 | nextName = ""
2118 | m.name = m.filePath
2119 | lineNumber = 0
2120 | m.ResetCurrentTestCase()
2121 | currentLocation =""
2122 | for each line in code.Split(chr(10))
2123 | lineNumber++
2124 | currentLocation = m.filePath + ":" + stri(lineNumber)
2125 | if (lineNumber > maxLinesWithoutSuiteDirective and not isTestSuite)
2126 | goto exitProcessing
2127 | end if
2128 | if (RBS_TS_IsTag(line, TAG_TEST_SUITE))
2129 | if (isTestSuite)
2130 | ? "Multiple suites per file are not supported - use '@It tag"
2131 | end if
2132 | name = RBS_TS_GetTagText(line, TAG_TEST_SUITE)
2133 | if (name <> "")
2134 | m.name = name
2135 | end if
2136 | if (isNextTokenSolo)
2137 | m.isSolo = true
2138 | m.name += " [ONLY]"
2139 | end if
2140 | isTestSuite = true
2141 | if (isNextTokenNodeTest)
2142 | m.nodeTestFileName = nodeTestFileName
2143 | m.isNodeTest = true
2144 | end if
2145 | if (isNextTokenIgnore)
2146 | m.isIgnored = true
2147 | goto exitProcessing
2148 | end if
2149 | goto exitLoop
2150 | else if (RBS_TS_IsTag(line, TAG_IT))
2151 | if (not isTestSuite)
2152 | ? "File not identified as testsuite!"
2153 | end if
2154 | name = RBS_TS_GetTagText(line, TAG_IT)
2155 | if (name = "")
2156 | name = "UNNAMED TAG_TEST GROUP - name this group for better readability - e.g. 'Tests the Load method... '"
2157 | end if
2158 | m.currentGroup = UnitTestItGroup(name, isNextTokenSolo, isNextTokenIgnore)
2159 | m.currentGroup.setupFunctionName = m.setupFunctionName
2160 | m.currentGroup.setupFunction = m.setupFunction
2161 | m.currentGroup.tearDownFunctionName = m.tearDownFunctionName
2162 | m.currentGroup.tearDownFunction = m.tearDownFunction
2163 | m.currentGroup.beforeEachFunctionName = m.beforeEachFunctionName
2164 | m.currentGroup.beforeEachFunction = m.beforeEachFunction
2165 | m.currentGroup.afterEachFunctionName = m.afterEachFunctionName
2166 | m.currentGroup.afterEachFunction = m.afterEachFunction
2167 | m.itGroups.push(m.currentGroup)
2168 | if (isNextTokenSolo)
2169 | m.hasSoloGroups = true
2170 | m.isSolo = true
2171 | end if
2172 | isTokenItGroup = true
2173 | else if (RBS_TS_IsTag(line, TAG_SOLO) and not RBS_TS_IsTag(line, TAG_TEST_SOLO_PARAMS))
2174 | if (isNextTokenSolo)
2175 | ? "TAG_TEST MARKED FOR TAG_IGNORE AND TAG_SOLO"
2176 | else
2177 | isNextTokenSolo = true
2178 | end if
2179 | goto exitLoop
2180 | else if (RBS_TS_IsTag(line, TAG_IGNORE) and not RBS_TS_IsTag(line, TAG_TEST_IGNORE_PARAMS))
2181 | isNextTokenIgnore = true
2182 | m.hasIgnoredTests = true
2183 | goto exitLoop
2184 | else if (RBS_TS_IsTag(line, TAG_NODE_TEST))
2185 | if (isTestSuite)
2186 | ? "FOUND " ; TAG_NODE_TEST ; " AFTER '@TestSuite annotation - This test will subsequently not run as a node test. "
2187 | ? "If you wish to run this suite of tests on a node, then make sure the " ; TAG_NODE_TEST ; " annotation appeares before the " ; TAG_TEST_SUITE ; " Annotation"
2188 | end if
2189 | nodeTestFileName = RBS_TS_GetTagText(line, TAG_NODE_TEST)
2190 | isNextTokenNodeTest = true
2191 | goto exitLoop
2192 | else if (RBS_TS_IsTag(line, TAG_TEST))
2193 | if (not isTestSuite)
2194 | ? "FOUND " ; TAG_TEST; " BEFORE '@TestSuite declaration - skipping test file! "; currentLocation
2195 | goto exitProcessing
2196 | end if
2197 | if (m.currentGroup = invalid)
2198 | ? "FOUND " ; TAG_TEST; " BEFORE '@It declaration - skipping test file!"; currentLocation
2199 | goto exitProcessing
2200 | end if
2201 | m.ResetCurrentTestCase()
2202 | isNextTokenTest = true
2203 | nextName = RBS_TS_GetTagText(line, TAG_TEST)
2204 | goto exitLoop
2205 | else if (RBS_TS_IsTag(line, TAG_SETUP))
2206 | if (not isTestSuite)
2207 | ? "FOUND " ; TAG_SETUP ; " BEFORE '@TestSuite declaration - skipping test file!"; currentLocation
2208 | goto exitProcessing
2209 | end if
2210 | isNextTokenSetup = true
2211 | goto exitLoop
2212 | else if (RBS_TS_IsTag(line, TAG_TEAR_DOWN))
2213 | if (not isTestSuite)
2214 | ? "FOUND " ; TAG_TEAR_DOWN ; " BEFORE '@TestSuite declaration - skipping test file!"; currentLocation
2215 | goto exitProcessing
2216 | end if
2217 | isNextTokenTeardown = true
2218 | goto exitLoop
2219 | else if (RBS_TS_IsTag(line, TAG_BEFORE_EACH))
2220 | if (not isTestSuite)
2221 | ? "FOUND " ; TAG_BEFORE_EACH ; " BEFORE '@TestSuite declaration - skipping test file!"; currentLocation
2222 | goto exitProcessing
2223 | end if
2224 | isNextTokenBeforeEach = true
2225 | goto exitLoop
2226 | else if (RBS_TS_IsTag(line, TAG_AFTER_EACH))
2227 | if (not isTestSuite)
2228 | ? "FOUND " ; TAG_AFTER_EACH ; " BEFORE '@TestSuite declaration - skipping test file!"; currentLocation
2229 | goto exitProcessing
2230 | end if
2231 | isNextTokenAfterEach = true
2232 | goto exitLoop
2233 | else if (assertInvocationRegex.IsMatch(line))
2234 | if (not m.hasCurrentTestCase)
2235 | ? "Found assert before test case was declared! " ; currentLocation
2236 | else
2237 | for testCaseIndex = 0 to m.currentTestCases.count() -1
2238 | tc = m.currentTestCases[testCaseIndex]
2239 | tc.AddAssertLine(lineNumber)
2240 | end for
2241 | end if
2242 | goto exitLoop
2243 | else if (isNextTokenTest and functionEndRegex.IsMatch(line))
2244 | m.ResetCurrentTestCase()
2245 | goto exitLoop
2246 | else if (RBS_TS_IsTag(line, TAG_TEST_IGNORE_PARAMS))
2247 | isNextTokenTestCaseParam = true ' this keeps the processing going down to the function
2248 | goto exitLoop
2249 | else if (RBS_TS_IsTag(line, TAG_TEST_PARAMS))
2250 | if (not isNextTokenTest)
2251 | ? "FOUND " ; TAG_TEST; " PARAM WITHOUT @Test declaration "; currentLocation
2252 | else
2253 | isNextTokenTestCaseParam = true
2254 | rawParams = RBS_TS_GetTagText(line, TAG_TEST_PARAMS)
2255 | m.testCaseParams.push(rawParams)
2256 | end if
2257 | goto exitLoop
2258 | else if (RBS_TS_IsTag(line, TAG_TEST_SOLO_PARAMS))
2259 | if (not isNextTokenTest)
2260 | ? "FOUND " ; TAG_TEST_SOLO_PARAMS; " PARAM WITHOUT @Test declaration "; currentLocation
2261 | else
2262 | isNextTokenSolo = true
2263 | isNextTokenTestCaseParam = true
2264 | rawParams = RBS_TS_GetTagText(line, TAG_TEST_SOLO_PARAMS)
2265 | m.testCaseOnlyParams.push(rawParams)
2266 | end if
2267 | goto exitLoop
2268 | end if
2269 | if (isTokenItGroup or isNextTokenTest or isNextTokenSetup or isNextTokenBeforeEach or isNextTokenAfterEach or isNextTokenTeardown)
2270 | if functionNameRegex.IsMatch(line)
2271 | functionName = functionNameRegex.Match(line).Peek()
2272 | functionPointer = RBS_CMN_GetFunction(invalid, functionName)
2273 | if (functionPointer <> invalid)
2274 | if (isNextTokenTest)
2275 | if (nextName <> "")
2276 | testName = nextName
2277 | else
2278 | testName = functionName
2279 | end if
2280 | if nodeTestFileName = "" nodeTestFileName = m.nodeTestFileName
2281 | if (m.testCaseParams.count() >0 or m.testCaseOnlyParams.count() >0)
2282 | if (m.testCaseOnlyParams.count() >0)
2283 | paramsToUse = m.testCaseOnlyParams
2284 | else
2285 | paramsToUse = m.testCaseParams
2286 | end if
2287 | for index = 0 to paramsToUse.count() -1
2288 | params = paramsToUse[index]
2289 | testCase = UnitTestCase(testName, functionPointer, functionName, isNextTokenSolo, isNextTokenIgnore, lineNumber, params, index)
2290 | testCase.isParamTest = true
2291 | if (testCase <> invalid)
2292 | m.currentTestCases.push(testCase)
2293 | else
2294 | ? "Skipping unparseable params for testcase " ; params ; " @" ; currentLocation
2295 | end if
2296 | end for
2297 | else
2298 | testCase = UnitTestCase(testName, functionPointer, functionName, isNextTokenSolo, isNextTokenIgnore, lineNumber)
2299 | m.currentTestCases.push(testCase)
2300 | end if
2301 | for each testCase in m.currentTestCases
2302 | m.currentGroup.AddTestCase(testCase)
2303 | end for
2304 | m.hasCurrentTestCase = true
2305 | if (isNextTokenSolo)
2306 | m.currentGroup.hasSoloTests = true
2307 | m.hasSoloTests = true
2308 | m.isSolo = true
2309 | end if
2310 | else if (isNextTokenSetup)
2311 | if (m.currentGroup = invalid)
2312 | m.setupFunctionName = functionName
2313 | m.setupFunction = functionPointer
2314 | else
2315 | m.currentGroup.setupFunctionName = functionName
2316 | m.currentGroup.setupFunction = functionPointer
2317 | end if
2318 | else if (isNextTokenTearDown)
2319 | if (m.currentGroup = invalid)
2320 | m.tearDownFunctionName = functionName
2321 | m.tearDownFunction = functionPointer
2322 | else
2323 | m.currentGroup.tearDownFunctionName = functionName
2324 | m.currentGroup.tearDownFunction = functionPointer
2325 | end if
2326 | else if (isNextTokenBeforeEach)
2327 | if (m.currentGroup = invalid)
2328 | m.beforeEachFunctionName = functionName
2329 | m.beforeEachFunction = functionPointer
2330 | else
2331 | m.currentGroup.beforeEachFunctionName = functionName
2332 | m.currentGroup.beforeEachFunction = functionPointer
2333 | end if
2334 | else if (isNextTokenAfterEach)
2335 | if (m.currentGroup = invalid)
2336 | m.afterEachFunctionName = functionName
2337 | m.afterEachFunction = functionPointer
2338 | else
2339 | m.currentGroup.afterEachFunctionName = functionName
2340 | m.currentGroup.afterEachFunction = functionPointer
2341 | end if
2342 | end if
2343 | else
2344 | ? " could not get function pointer for "; functionName ; " ignoring"
2345 | end if
2346 | else if (not isTokenItGroup and not isNextTokenTestCaseParam)
2347 | ? "Ignoring test - function name did not immediately follow '@Test or '@Params directive - line was instead : " ; line ; " @ "; m.filePath ; "("; StrI(lineNumber).trim() ; ")"
2348 | end if
2349 | isNextTokenIgnore = false
2350 | isNextTokenSolo = false
2351 | if (isNextTokenTestCaseParam)
2352 | isNextTokenTest = false
2353 | else
2354 | isNextTokenTest = false
2355 | end if
2356 | isNextTokenSetup = false
2357 | isNextTokenTearDown = false
2358 | isNextTokenAfterEach = false
2359 | isNextTokenBeforeEach = false
2360 | isNextTokenNodeTest = false
2361 | isTokenItGroup = false
2362 | isNextTokenTestCaseParam = false
2363 | nodeTestFileName = ""
2364 | nextName = ""
2365 | end if
2366 | exitLoop:
2367 | end for
2368 | exitProcessing:
2369 | else
2370 | ? " Error opening potential test file " ; filePath ; " ignoring..."
2371 | end if
2372 | m.delete("testCaseOnlyParams")
2373 | m.delete("testCaseParams")
2374 | m.delete("currentTestCases")
2375 | m.delete("hasCurrentTestCase")
2376 | if (isTestSuite)
2377 | m.isValid = true
2378 | else if (supportLegacyTests = true)
2379 | m.ProcessLegacySuite(maxLinesWithoutSuiteDirective)
2380 | else
2381 | ? "Ignoring non test/legacy test file "; filePath
2382 | m.isValid = false
2383 | end if
2384 | end function
2385 | function RBS_TS_IsTag(text, tag) as boolean
2386 | return Left(text,len(tag)) = tag
2387 | end function
2388 | function RBS_TS_GetTagText(text, tag) as string
2389 | return Mid(text, len(tag) +1).trim()
2390 | end function
2391 | function RBS_TS_ResetCurrentTestCase() as void
2392 | m.testCaseOnlyParams = []
2393 | m.testCaseParams = []
2394 | m.currentTestCases = [] ' we can have multiple test cases based on our params
2395 | m.hasCurrentTestCase = false
2396 | end function
2397 | function RBS_TS_ProcessLegacySuite(maxLinesWithoutSuiteDirective)
2398 | code = RBS_CMN_AsString(ReadAsciiFile(m.filePath))
2399 | isTestSuite = false
2400 | dblQ = chr(34)
2401 | testSuiteFunctionNameRegex = CreateObject("roRegex", "^\s*(function|sub)\s*testSuite_([0-9a-z\_]*)\s*\(", "i")
2402 | testCaseFunctionNameRegex = CreateObject("roRegex", "^\s*(function|sub)\s*testCase_([0-9a-z\_]*)\s*\(", "i")
2403 | functionNameRegex = CreateObject("roRegex", "^\s*(function|sub)\s([0-9a-z\_]*)\s*\(", "i")
2404 | assertInvocationRegex = CreateObject("roRegex", "^\s*(m.fail|m.Fail|m.assert|m.Assert)(.*)\(", "i")
2405 | functionEndRegex = CreateObject("roRegex", "^\s*(end sub|end function)", "i")
2406 | testSuiteNameRegex = CreateObject("roRegex", "^\s*this\.name\s*=\s*\" + dblQ + "([0-9a-z\_]*)\s*\" + dblQ, "i")
2407 | setupregex = CreateObject("roRegex", "^\s*this\.setup\s*=\s*([a-z_0-9]*)","i")
2408 | addTestregex = CreateObject("roRegex", "^\s*this\.addTest\s*\(\" + dblQ + "([0-9a-z\_]*)\" + dblQ + "\s*,\s*([0-9a-z\_]*)\s*\)", "i")
2409 | TAG_IGNORE = "'@Ignore"
2410 | TAG_SOLO = "'@Only"
2411 | isIgnored = false
2412 | isSolo = false
2413 | if code <> ""
2414 | m.testCaseMap = {} ' map of legacy test cases to function names
2415 | isInInitFunction = false
2416 | isTestSuite = false
2417 | nodeTestFileName = ""
2418 | m.name = m.filePath
2419 | lineNumber = 0
2420 | m.ResetCurrentTestCase()
2421 | currentTestCase = invalid
2422 | currentLocation =""
2423 | for each line in code.Split(chr(10))
2424 | lineNumber++
2425 | currentLocation = m.filePath + ":" + stri(lineNumber)
2426 | if (lineNumber > maxLinesWithoutSuiteDirective and not isTestSuite)
2427 | ? "IGNORING FILE WITH NO testSuiteInit function : "; currentLocation
2428 | goto exitProcessing
2429 | end if
2430 | if (RBS_TS_IsTag(line, TAG_SOLO))
2431 | isSolo = true
2432 | ? " IS SOLO TEST!"
2433 | goto exitLoop
2434 | end if
2435 | if (RBS_TS_IsTag(line, TAG_IGNORE))
2436 | isIgnored = true
2437 | ? " IS IGNORED TEST!"
2438 | goto exitLoop
2439 | end if
2440 | if testSuiteFunctionNameRegex.IsMatch(line)
2441 | isTestSuite = true
2442 | isInInitFunction = true
2443 | goto exitLoop
2444 | end if
2445 | if setupregex.IsMatch(line)
2446 | if not isInInitFunction
2447 | ? "Found test suite setup invocation outside of test suite init function"
2448 | goto exitLoop
2449 | end if
2450 | functionName = setupregex.Match(line).peek()
2451 | functionPointer = RBS_CMN_GetFunction(invalid, functionName)
2452 | if (functionPointer <> invalid)
2453 | m.setupFunctionName = functionName
2454 | m.setupFunction = functionPointer
2455 | else
2456 | ? " the function name for the setup method "; functionName ; " could not be found"
2457 | end if
2458 | goto exitLoop
2459 | end if
2460 | if functionEndRegex.IsMatch(line)
2461 | if (isInInitFunction)
2462 | m.currentGroup = UnitTestItGroup(m.name, false, false)
2463 | m.currentGroup.setupFunctionName = m.setupFunctionName
2464 | m.currentGroup.setupFunction = m.setupFunction
2465 | m.currentGroup.isLegacy = true
2466 | m.itGroups.push(m.currentGroup)
2467 | isInInitFunction = false
2468 | m.isSolo = isSolo
2469 | m.isIgnored = isIgnored
2470 | isIgnored = false
2471 | isSolo = false
2472 | end if
2473 | currentTestCase = invalid
2474 | goto exitLoop
2475 | end if
2476 | if testSuiteNameRegex.IsMatch(line)
2477 | if (not isInInitFunction)
2478 | ? "Found set testsuite name, when not in a legacy test suite init function. ignoring"
2479 | goto exitLoop
2480 | end if
2481 | name = testSuiteNameRegex.Match(line).Peek()
2482 | if (name <> "")
2483 | m.name = name
2484 | end if
2485 | goto exitLoop
2486 | end if
2487 | if addTestregex.IsMatch(line)
2488 | if (not isInInitFunction)
2489 | ? "Found addTestCase, when not in a legacy test suite init function. Ignoring"
2490 | goto exitLoop
2491 | end if
2492 | match = addTestregex.Match(line)
2493 | testCaseName = match[1]
2494 | testCaseFunctionName = match[2]
2495 | if (testCaseName <> "" and testCaseFunctionName <> "")
2496 | m.testCaseMap[lcase(testCaseFunctionName)] = testCaseName
2497 | else
2498 | ? " found badly formed add test case function call in test suite init fuction. Ignoring"
2499 | end if
2500 | goto exitLoop
2501 | end if
2502 | if (assertInvocationRegex.IsMatch(line))
2503 | if (not m.hasCurrentTestCase)
2504 | ? "Found assert before test case was declared! " ; currentLocation
2505 | else
2506 | currentTestCase.AddAssertLine(lineNumber)
2507 | end if
2508 | goto exitLoop
2509 | end if
2510 | if testCaseFunctionNameRegex.IsMatch(line)
2511 | if (m.currentGroup = invalid)
2512 | ? " found test case before a group was setup - could be that the init function never terminated"
2513 | goto exitLoop
2514 | end if
2515 | functionName = testCaseFunctionNameRegex.Match(line).peek()
2516 | m.ResetCurrentTestCase()
2517 | if (functionName <> "")
2518 | functionName = "testcase_" + lcase(functionName)
2519 | testName = m.testCaseMap[functionName]
2520 | if (testName = invalid or testName = "")
2521 | print "Encountered test function " ; functionName; " but found no matching AddTestCase invocation"
2522 | goto exitLoop
2523 | end if
2524 | functionPointer = RBS_CMN_GetFunction(invalid, functionName)
2525 | if (functionPointer <> invalid)
2526 | if nodeTestFileName = "" nodeTestFileName = m.nodeTestFileName
2527 | currentTestCase = UnitTestCase(testName, functionPointer, functionName, isSolo, isIgnored, lineNumber)
2528 | m.currentGroup.AddTestCase(currentTestCase)
2529 | m.hasCurrentTestCase = true
2530 | if (isSolo)
2531 | m.isSolo = true
2532 | end if
2533 | else
2534 | ? " could not get function pointer for "; functionName ; " ignoring"
2535 | end if
2536 | else
2537 | ? " found badly named test case function. ignoring"
2538 | end if
2539 | isSolo = false
2540 | isIgnored = false
2541 | end if
2542 | exitLoop:
2543 | end for
2544 | exitProcessing:
2545 | else
2546 | ? " Error opening potential test file " ; filePath ; " ignoring..."
2547 | end if
2548 | m.isValid = isTestSuite
2549 | end function
2550 |
--------------------------------------------------------------------------------