├── .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 | [![build status](https://img.shields.io/github/actions/workflow/status/rokucommunity/roku-requests/build.yml?logo=github&branch=master)](https://github.com/rokucommunity/roku-requests/actions/workflows/build.yml) 6 | [![monthly downloads](https://img.shields.io/npm/dm/roku-requests.svg?sanitize=true&logo=npm&logoColor=)](https://npmcharts.com/compare/roku-requests?minimal=true) 7 | [![npm version](https://img.shields.io/npm/v/roku-requests.svg?logo=npm)](https://www.npmjs.com/package/roku-requests) 8 | [![license](https://img.shields.io/github/license/rokucommunity/roku-requests.svg)](LICENSE) 9 | [![Slack](https://img.shields.io/badge/Slack-RokuCommunity-4A154B?logo=slack)](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 | --------------------------------------------------------------------------------