2868 lines
102 KiB
Plaintext
2868 lines
102 KiB
Plaintext
|
'*****************************************************************
|
||
|
'* Roku Unit Testing Framework
|
||
|
'* Automating test suites for Roku channels.
|
||
|
'*
|
||
|
'* Build Version: 2.1.1
|
||
|
'* Build Date: 05/06/2019
|
||
|
'*
|
||
|
'* Public Documentation is avaliable on GitHub:
|
||
|
'* https://github.com/rokudev/unit-testing-framework
|
||
|
'*
|
||
|
'*****************************************************************
|
||
|
'*****************************************************************
|
||
|
'* Copyright Roku 2011-2019
|
||
|
'* All Rights Reserved
|
||
|
'*****************************************************************
|
||
|
|
||
|
' Functions in this file:
|
||
|
|
||
|
' BaseTestSuite
|
||
|
' BTS__AddTest
|
||
|
' BTS__CreateTest
|
||
|
' BTS__Fail
|
||
|
' BTS__AssertFalse
|
||
|
' BTS__AssertTrue
|
||
|
' BTS__AssertEqual
|
||
|
' BTS__AssertNotEqual
|
||
|
' BTS__AssertInvalid
|
||
|
' BTS__AssertNotInvalid
|
||
|
' BTS__AssertAAHasKey
|
||
|
' BTS__AssertAANotHasKey
|
||
|
' BTS__AssertAAHasKeys
|
||
|
' BTS__AssertAANotHasKeys
|
||
|
' BTS__AssertArrayContains
|
||
|
' BTS__AssertArrayNotContains
|
||
|
' BTS__AssertArrayContainsSubset
|
||
|
' BTS__AssertArrayNotContainsSubset
|
||
|
' BTS__AssertArrayCount
|
||
|
' BTS__AssertArrayNotCount
|
||
|
' BTS__AssertEmpty
|
||
|
' BTS__AssertNotEmpty
|
||
|
|
||
|
' ----------------------------------------------------------------
|
||
|
' Main function. Create BaseTestSuite object.
|
||
|
|
||
|
' @return A BaseTestSuite object.
|
||
|
' ----------------------------------------------------------------
|
||
|
function BaseTestSuite()
|
||
|
this = {}
|
||
|
this.Name = "BaseTestSuite"
|
||
|
this.SKIP_TEST_MESSAGE_PREFIX = "SKIP_TEST_MESSAGE_PREFIX__"
|
||
|
' Test Cases methods
|
||
|
this.testCases = []
|
||
|
this.IS_NEW_APPROACH = false
|
||
|
this.addTest = BTS__AddTest
|
||
|
this.createTest = BTS__CreateTest
|
||
|
this.StorePerformanceData = BTS__StorePerformanceData
|
||
|
|
||
|
' Assertion methods which determine test failure or skipping
|
||
|
this.skip = BTS__Skip
|
||
|
this.fail = BTS__Fail
|
||
|
this.assertFalse = BTS__AssertFalse
|
||
|
this.assertTrue = BTS__AssertTrue
|
||
|
this.assertEqual = BTS__AssertEqual
|
||
|
this.assertNotEqual = BTS__AssertNotEqual
|
||
|
this.assertInvalid = BTS__AssertInvalid
|
||
|
this.assertNotInvalid = BTS__AssertNotInvalid
|
||
|
this.assertAAHasKey = BTS__AssertAAHasKey
|
||
|
this.assertAANotHasKey = BTS__AssertAANotHasKey
|
||
|
this.assertAAHasKeys = BTS__AssertAAHasKeys
|
||
|
this.assertAANotHasKeys = BTS__AssertAANotHasKeys
|
||
|
this.assertArrayContains = BTS__AssertArrayContains
|
||
|
this.assertArrayNotContains = BTS__AssertArrayNotContains
|
||
|
this.assertArrayContainsSubset = BTS__AssertArrayContainsSubset
|
||
|
this.assertArrayNotContainsSubset = BTS__AssertArrayNotContainsSubset
|
||
|
this.assertArrayCount = BTS__AssertArrayCount
|
||
|
this.assertArrayNotCount = BTS__AssertArrayNotCount
|
||
|
this.assertEmpty = BTS__AssertEmpty
|
||
|
this.assertNotEmpty = BTS__AssertNotEmpty
|
||
|
|
||
|
' Type Comparison Functionality
|
||
|
this.eqValues = TF_Utils__EqValues
|
||
|
this.eqAssocArrays = TF_Utils__EqAssocArray
|
||
|
this.eqArrays = TF_Utils__EqArray
|
||
|
this.baseComparator = TF_Utils__BaseComparator
|
||
|
|
||
|
return this
|
||
|
end function
|
||
|
|
||
|
' ----------------------------------------------------------------
|
||
|
' Add a test to a suite's test cases array.
|
||
|
|
||
|
' @param name (string) A test name.
|
||
|
' @param func (object) A pointer to test function.
|
||
|
' @param setup (object) A pointer to setup function.
|
||
|
' @param teardown (object) A pointer to teardown function.
|
||
|
' @param arg (dynamic) A test function arguments.
|
||
|
' @param hasArgs (boolean) True if test function has parameters.
|
||
|
' @param skip (boolean) Skip test run.
|
||
|
' ----------------------------------------------------------------
|
||
|
sub BTS__AddTest(name as string, func as object, setup = invalid as object, teardown = invalid as object, arg = invalid as dynamic, hasArgs = false as boolean, skip = false as boolean)
|
||
|
m.testCases.Push(m.createTest(name, func, setup, teardown, arg, hasArgs, skip))
|
||
|
end sub
|
||
|
|
||
|
' ----------------------------------------------------------------
|
||
|
' Create a test object.
|
||
|
|
||
|
' @param name (string) A test name.
|
||
|
' @param func (object) A pointer to test function.
|
||
|
' @param setup (object) A pointer to setup function.
|
||
|
' @param teardown (object) A pointer to teardown function.
|
||
|
' @param arg (dynamic) A test function arguments.
|
||
|
' @param hasArgs (boolean) True if test function has parameters.
|
||
|
' @param skip (boolean) Skip test run.
|
||
|
'
|
||
|
' @return TestCase object.
|
||
|
' ----------------------------------------------------------------
|
||
|
function BTS__CreateTest(name as string, func as object, setup = invalid as object, teardown = invalid as object, arg = invalid as dynamic, hasArgs = false as boolean, skip = false as boolean) as object
|
||
|
return {
|
||
|
Name: name
|
||
|
Func: func
|
||
|
SetUp: setup
|
||
|
TearDown: teardown
|
||
|
|
||
|
perfData: {}
|
||
|
|
||
|
hasArguments: hasArgs
|
||
|
arg: arg
|
||
|
|
||
|
skip: skip
|
||
|
}
|
||
|
end function
|
||
|
|
||
|
'----------------------------------------------------------------
|
||
|
' Store performance data to current test instance.
|
||
|
'
|
||
|
' @param name (string) A property name.
|
||
|
' @param value (Object) A value of data.
|
||
|
'----------------------------------------------------------------
|
||
|
sub BTS__StorePerformanceData(name as string, value as object)
|
||
|
timestamp = StrI(CreateObject("roDateTime").AsSeconds())
|
||
|
m.testInstance.perfData.Append({
|
||
|
name: {
|
||
|
"value": value
|
||
|
"timestamp": timestamp
|
||
|
}
|
||
|
})
|
||
|
' print performance data to console
|
||
|
? "PERF_DATA: " + m.testInstance.Name + ": " + timestamp + ": " + name + "|" + TF_Utils__AsString(value)
|
||
|
end sub
|
||
|
|
||
|
' ----------------------------------------------------------------
|
||
|
' Assertion methods which determine test failure or skipping
|
||
|
' ----------------------------------------------------------------
|
||
|
|
||
|
' ----------------------------------------------------------------
|
||
|
' Should be used to skip test cases. To skip test you must return the result of this method invocation.
|
||
|
|
||
|
' @param message (string) Optional skip message.
|
||
|
' Default value: "".
|
||
|
|
||
|
' @return A skip message, with a specific prefix added, in order to runner know that this test should be skipped.
|
||
|
' ----------------------------------------------------------------
|
||
|
function BTS__Skip(message = "" as string) as string
|
||
|
' add prefix so we know that this test is skipped, but not failed
|
||
|
return m.SKIP_TEST_MESSAGE_PREFIX + message
|
||
|
end function
|
||
|
|
||
|
' ----------------------------------------------------------------
|
||
|
' Fail immediately, with the given message
|
||
|
|
||
|
' @param msg (string) An error message.
|
||
|
' Default value: "Error".
|
||
|
|
||
|
' @return An error message.
|
||
|
' ----------------------------------------------------------------
|
||
|
function BTS__Fail(msg = "Error" as string) as string
|
||
|
return msg
|
||
|
end function
|
||
|
|
||
|
' ----------------------------------------------------------------
|
||
|
' Fail the test if the expression is true.
|
||
|
|
||
|
' @param expr (dynamic) An expression to evaluate.
|
||
|
' @param msg (string) An error message.
|
||
|
' Default value: "Expression evaluates to true"
|
||
|
|
||
|
' @return An error message.
|
||
|
' ----------------------------------------------------------------
|
||
|
function BTS__AssertFalse(expr as dynamic, msg = "Expression evaluates to true" as string) as string
|
||
|
if not TF_Utils__IsBoolean(expr) or expr
|
||
|
return BTS__Fail(msg)
|
||
|
end if
|
||
|
return ""
|
||
|
end function
|
||
|
|
||
|
' ----------------------------------------------------------------
|
||
|
' Fail the test unless the expression is true.
|
||
|
|
||
|
' @param expr (dynamic) An expression to evaluate.
|
||
|
' @param msg (string) An error message.
|
||
|
' Default value: "Expression evaluates to false"
|
||
|
|
||
|
' @return An error message.
|
||
|
' ----------------------------------------------------------------
|
||
|
function BTS__AssertTrue(expr as dynamic, msg = "Expression evaluates to false" as string) as string
|
||
|
if not TF_Utils__IsBoolean(expr) or not expr then
|
||
|
return msg
|
||
|
end if
|
||
|
return ""
|
||
|
end function
|
||
|
|
||
|
' ----------------------------------------------------------------
|
||
|
' Fail if the two objects are unequal as determined by the '<>' operator.
|
||
|
|
||
|
' @param first (dynamic) A first object to compare.
|
||
|
' @param second (dynamic) A second object to compare.
|
||
|
' @param msg (string) An error message.
|
||
|
' Default value: ""
|
||
|
|
||
|
' @return An error message.
|
||
|
' ----------------------------------------------------------------
|
||
|
function BTS__AssertEqual(first as dynamic, second as dynamic, msg = "" as string) as string
|
||
|
if not TF_Utils__EqValues(first, second)
|
||
|
if msg = ""
|
||
|
first_as_string = TF_Utils__AsString(first)
|
||
|
second_as_string = TF_Utils__AsString(second)
|
||
|
msg = first_as_string + " != " + second_as_string
|
||
|
end if
|
||
|
return msg
|
||
|
end if
|
||
|
return ""
|
||
|
end function
|
||
|
|
||
|
' ----------------------------------------------------------------
|
||
|
' Fail if the two objects are equal as determined by the '=' operator.
|
||
|
|
||
|
' @param first (dynamic) A first object to compare.
|
||
|
' @param second (dynamic) A second object to compare.
|
||
|
' @param msg (string) An error message.
|
||
|
' Default value: ""
|
||
|
|
||
|
' @return An error message.
|
||
|
' ----------------------------------------------------------------
|
||
|
function BTS__AssertNotEqual(first as dynamic, second as dynamic, msg = "" as string) as string
|
||
|
if TF_Utils__EqValues(first, second)
|
||
|
if msg = ""
|
||
|
first_as_string = TF_Utils__AsString(first)
|
||
|
second_as_string = TF_Utils__AsString(second)
|
||
|
msg = first_as_string + " == " + second_as_string
|
||
|
end if
|
||
|
return msg
|
||
|
end if
|
||
|
return ""
|
||
|
end function
|
||
|
|
||
|
' ----------------------------------------------------------------
|
||
|
' Fail if the value is not invalid.
|
||
|
|
||
|
' @param value (dynamic) A value to check.
|
||
|
' @param msg (string) An error message.
|
||
|
' Default value: ""
|
||
|
|
||
|
' @return An error message.
|
||
|
' ----------------------------------------------------------------
|
||
|
function BTS__AssertInvalid(value as dynamic, msg = "" as string) as string
|
||
|
if TF_Utils__IsValid(value)
|
||
|
if msg = ""
|
||
|
expr_as_string = TF_Utils__AsString(value)
|
||
|
msg = expr_as_string + " <> Invalid"
|
||
|
end if
|
||
|
return msg
|
||
|
end if
|
||
|
return ""
|
||
|
end function
|
||
|
|
||
|
' ----------------------------------------------------------------
|
||
|
' Fail if the value is invalid.
|
||
|
|
||
|
' @param value (dynamic) A value to check.
|
||
|
' @param msg (string) An error message.
|
||
|
' Default value: ""
|
||
|
|
||
|
' @return An error message.
|
||
|
' ----------------------------------------------------------------
|
||
|
function BTS__AssertNotInvalid(value as dynamic, msg = "" as string) as string
|
||
|
if not TF_Utils__IsValid(value)
|
||
|
if msg = ""
|
||
|
if LCase(Type(value)) = "<uninitialized>" then value = invalid
|
||
|
expr_as_string = TF_Utils__AsString(value)
|
||
|
msg = expr_as_string + " = Invalid"
|
||
|
end if
|
||
|
return msg
|
||
|
end if
|
||
|
return ""
|
||
|
end function
|
||
|
|
||
|
' ----------------------------------------------------------------
|
||
|
' Fail if the array doesn't have the key.
|
||
|
|
||
|
' @param array (dynamic) A target array.
|
||
|
' @param key (string) A key name.
|
||
|
' @param msg (string) An error message.
|
||
|
' Default value: ""
|
||
|
|
||
|
' @return An error message.
|
||
|
' ----------------------------------------------------------------
|
||
|
function BTS__AssertAAHasKey(array as dynamic, key as dynamic, msg = "" as string) as string
|
||
|
if not TF_Utils__IsString(key)
|
||
|
return "Key value has invalid type."
|
||
|
end if
|
||
|
|
||
|
if TF_Utils__IsAssociativeArray(array)
|
||
|
if not array.DoesExist(key)
|
||
|
if msg = ""
|
||
|
msg = "Array doesn't have the '" + key + "' key."
|
||
|
end if
|
||
|
return msg
|
||
|
end if
|
||
|
else
|
||
|
msg = "Input value is not an Associative Array."
|
||
|
return msg
|
||
|
end if
|
||
|
|
||
|
return ""
|
||
|
end function
|
||
|
|
||
|
' ----------------------------------------------------------------
|
||
|
' Fail if the array has the key.
|
||
|
|
||
|
' @param array (dynamic) A target array.
|
||
|
' @param key (string) A key name.
|
||
|
' @param msg (string) An error message.
|
||
|
' Default value: ""
|
||
|
|
||
|
' @return An error message.
|
||
|
' ----------------------------------------------------------------
|
||
|
function BTS__AssertAANotHasKey(array as dynamic, key as dynamic, msg = "" as string) as string
|
||
|
if not TF_Utils__IsString(key)
|
||
|
return "Key value has invalid type."
|
||
|
end if
|
||
|
|
||
|
if TF_Utils__IsAssociativeArray(array)
|
||
|
if array.DoesExist(key)
|
||
|
if msg = ""
|
||
|
msg = "Array has the '" + key + "' key."
|
||
|
end if
|
||
|
return msg
|
||
|
end if
|
||
|
else
|
||
|
msg = "Input value is not an Associative Array."
|
||
|
return msg
|
||
|
end if
|
||
|
|
||
|
return ""
|
||
|
end function
|
||
|
|
||
|
' ----------------------------------------------------------------
|
||
|
' Fail if the array doesn't have the keys list.
|
||
|
|
||
|
' @param array (dynamic) A target associative array.
|
||
|
' @param keys (object) A key names array.
|
||
|
' @param msg (string) An error message.
|
||
|
' Default value: ""
|
||
|
|
||
|
' @return An error message.
|
||
|
' ----------------------------------------------------------------
|
||
|
function BTS__AssertAAHasKeys(array as dynamic, keys as object, msg = "" as string) as string
|
||
|
if not TF_Utils__IsAssociativeArray(array)
|
||
|
return "Input value is not an Associative Array."
|
||
|
end if
|
||
|
|
||
|
if not TF_Utils__IsArray(keys) or keys.Count() = 0
|
||
|
return "Keys value is not an Array or is empty."
|
||
|
end if
|
||
|
|
||
|
if TF_Utils__IsAssociativeArray(array) and TF_Utils__IsArray(keys)
|
||
|
for each key in keys
|
||
|
if not TF_Utils__IsString(key)
|
||
|
return "Key value has invalid type."
|
||
|
end if
|
||
|
|
||
|
if not array.DoesExist(key)
|
||
|
if msg = ""
|
||
|
msg = "Array doesn't have the '" + key + "' key."
|
||
|
end if
|
||
|
|
||
|
return msg
|
||
|
end if
|
||
|
end for
|
||
|
else
|
||
|
msg = "Input value is not an Associative Array."
|
||
|
return msg
|
||
|
end if
|
||
|
|
||
|
return ""
|
||
|
end function
|
||
|
|
||
|
' ----------------------------------------------------------------
|
||
|
' Fail if the array has the keys list.
|
||
|
|
||
|
' @param array (dynamic) A target associative array.
|
||
|
' @param keys (object) A key names array.
|
||
|
' @param msg (string) An error message.
|
||
|
' Default value: ""
|
||
|
|
||
|
' @return An error message.
|
||
|
' ----------------------------------------------------------------
|
||
|
function BTS__AssertAANotHasKeys(array as dynamic, keys as object, msg = "" as string) as string
|
||
|
if not TF_Utils__IsAssociativeArray(array)
|
||
|
return "Input value is not an Associative Array."
|
||
|
end if
|
||
|
|
||
|
if not TF_Utils__IsArray(keys) or keys.Count() = 0
|
||
|
return "Keys value is not an Array or is empty."
|
||
|
end if
|
||
|
|
||
|
if TF_Utils__IsAssociativeArray(array) and TF_Utils__IsArray(keys)
|
||
|
for each key in keys
|
||
|
if not TF_Utils__IsString(key)
|
||
|
return "Key value has invalid type."
|
||
|
end if
|
||
|
|
||
|
if array.DoesExist(key)
|
||
|
if msg = ""
|
||
|
msg = "Array has the '" + key + "' key."
|
||
|
end if
|
||
|
return msg
|
||
|
end if
|
||
|
end for
|
||
|
else
|
||
|
msg = "Input value is not an Associative Array."
|
||
|
return msg
|
||
|
end if
|
||
|
return ""
|
||
|
end function
|
||
|
|
||
|
' ----------------------------------------------------------------
|
||
|
' Fail if the array doesn't have the item.
|
||
|
|
||
|
' @param array (dynamic) A target array.
|
||
|
' @param value (dynamic) A value to check.
|
||
|
' @param key (object) A key name for associative array.
|
||
|
' @param msg (string) An error message.
|
||
|
' Default value: ""
|
||
|
|
||
|
' @return An error message.
|
||
|
' ----------------------------------------------------------------
|
||
|
function BTS__AssertArrayContains(array as dynamic, value as dynamic, key = invalid as dynamic, msg = "" as string) as string
|
||
|
if key <> invalid and not TF_Utils__IsString(key)
|
||
|
return "Key value has invalid type."
|
||
|
end if
|
||
|
|
||
|
if TF_Utils__IsAssociativeArray(array) or TF_Utils__IsArray(array)
|
||
|
if not TF_Utils__ArrayContains(array, value, key)
|
||
|
msg = "Array doesn't have the '" + TF_Utils__AsString(value) + "' value."
|
||
|
|
||
|
return msg
|
||
|
end if
|
||
|
else
|
||
|
msg = "Input value is not an Array."
|
||
|
|
||
|
return msg
|
||
|
end if
|
||
|
|
||
|
return ""
|
||
|
end function
|
||
|
|
||
|
' ----------------------------------------------------------------
|
||
|
' Fail if the array has the item.
|
||
|
|
||
|
' @param array (dynamic) A target array.
|
||
|
' @param value (dynamic) A value to check.
|
||
|
' @param key (object) A key name for associative array.
|
||
|
' @param msg (string) An error message.
|
||
|
' Default value: ""
|
||
|
|
||
|
' @return An error message.
|
||
|
' ----------------------------------------------------------------
|
||
|
function BTS__AssertArrayNotContains(array as dynamic, value as dynamic, key = invalid as dynamic, msg = "" as string) as string
|
||
|
if key <> invalid and not TF_Utils__IsString(key)
|
||
|
return "Key value has invalid type."
|
||
|
end if
|
||
|
|
||
|
if TF_Utils__IsAssociativeArray(array) or TF_Utils__IsArray(array)
|
||
|
if TF_Utils__ArrayContains(array, value, key)
|
||
|
msg = "Array has the '" + TF_Utils__AsString(value) + "' value."
|
||
|
|
||
|
return msg
|
||
|
end if
|
||
|
else
|
||
|
msg = "Input value is not an Array."
|
||
|
|
||
|
return msg
|
||
|
end if
|
||
|
|
||
|
return ""
|
||
|
end function
|
||
|
|
||
|
' ----------------------------------------------------------------
|
||
|
' Fail if the array doesn't have the item subset.
|
||
|
|
||
|
' @param array (dynamic) A target array.
|
||
|
' @param subset (dynamic) An items array to check.
|
||
|
' @param msg (string) An error message.
|
||
|
' Default value: ""
|
||
|
|
||
|
' @return An error message.
|
||
|
' ----------------------------------------------------------------
|
||
|
function BTS__AssertArrayContainsSubset(array as dynamic, subset as dynamic, msg = "" as string) as string
|
||
|
if (TF_Utils__IsAssociativeArray(array) and TF_Utils__IsAssociativeArray(subset)) or (TF_Utils__IsArray(array) and TF_Utils__IsArray(subset))
|
||
|
isAA = TF_Utils__IsAssociativeArray(subset)
|
||
|
for each item in subset
|
||
|
key = invalid
|
||
|
value = item
|
||
|
if isAA
|
||
|
key = item
|
||
|
value = subset[key]
|
||
|
end if
|
||
|
|
||
|
if not TF_Utils__ArrayContains(array, value, key)
|
||
|
msg = "Array doesn't have the '" + TF_Utils__AsString(value) + "' value."
|
||
|
|
||
|
return msg
|
||
|
end if
|
||
|
end for
|
||
|
else
|
||
|
msg = "Input value is not an Array."
|
||
|
|
||
|
return msg
|
||
|
end if
|
||
|
|
||
|
return ""
|
||
|
end function
|
||
|
|
||
|
' ----------------------------------------------------------------
|
||
|
' Fail if the array have the item from subset.
|
||
|
|
||
|
' @param array (dynamic) A target array.
|
||
|
' @param subset (dynamic) A items array to check.
|
||
|
' @param msg (string) An error message.
|
||
|
' Default value: ""
|
||
|
|
||
|
' @return An error message.
|
||
|
' ----------------------------------------------------------------
|
||
|
function BTS__AssertArrayNotContainsSubset(array as dynamic, subset as dynamic, msg = "" as string) as string
|
||
|
if (TF_Utils__IsAssociativeArray(array) and TF_Utils__IsAssociativeArray(subset)) or (TF_Utils__IsArray(array) and TF_Utils__IsArray(subset))
|
||
|
isAA = TF_Utils__IsAssociativeArray(subset)
|
||
|
for each item in subset
|
||
|
key = invalid
|
||
|
value = item
|
||
|
if isAA
|
||
|
key = item
|
||
|
value = subset[key]
|
||
|
end if
|
||
|
|
||
|
if TF_Utils__ArrayContains(array, value, key)
|
||
|
msg = "Array has the '" + TF_Utils__AsString(value) + "' value."
|
||
|
|
||
|
return msg
|
||
|
end if
|
||
|
end for
|
||
|
else
|
||
|
msg = "Input value is not an Array."
|
||
|
|
||
|
return msg
|
||
|
end if
|
||
|
|
||
|
return ""
|
||
|
end function
|
||
|
|
||
|
' ----------------------------------------------------------------
|
||
|
' Fail if the array items count <> expected count
|
||
|
|
||
|
' @param array (dynamic) A target array.
|
||
|
' @param count (integer) An expected array items count.
|
||
|
' @param msg (string) An error message.
|
||
|
' Default value: ""
|
||
|
|
||
|
' @return An error message.
|
||
|
' ----------------------------------------------------------------
|
||
|
function BTS__AssertArrayCount(array as dynamic, count as dynamic, msg = "" as string) as string
|
||
|
if not TF_Utils__IsInteger(count)
|
||
|
return "Count value should be an integer."
|
||
|
end if
|
||
|
|
||
|
if TF_Utils__IsAssociativeArray(array) or TF_Utils__IsArray(array)
|
||
|
if array.Count() <> count
|
||
|
msg = "Array items count <> " + TF_Utils__AsString(count) + "."
|
||
|
|
||
|
return msg
|
||
|
end if
|
||
|
else
|
||
|
msg = "Input value is not an Array."
|
||
|
|
||
|
return msg
|
||
|
end if
|
||
|
|
||
|
return ""
|
||
|
end function
|
||
|
|
||
|
' ----------------------------------------------------------------
|
||
|
' Fail if the array items count = expected count.
|
||
|
|
||
|
' @param array (dynamic) A target array.
|
||
|
' @param count (integer) An expected array items count.
|
||
|
' @param msg (string) An error message.
|
||
|
' Default value: ""
|
||
|
|
||
|
' @return An error message.
|
||
|
' ----------------------------------------------------------------
|
||
|
function BTS__AssertArrayNotCount(array as dynamic, count as dynamic, msg = "" as string) as string
|
||
|
if not TF_Utils__IsInteger(count)
|
||
|
return "Count value should be an integer."
|
||
|
end if
|
||
|
|
||
|
if TF_Utils__IsAssociativeArray(array) or TF_Utils__IsArray(array)
|
||
|
if array.Count() = count
|
||
|
msg = "Array items count = " + TF_Utils__AsString(count) + "."
|
||
|
|
||
|
return msg
|
||
|
end if
|
||
|
else
|
||
|
msg = "Input value is not an Array."
|
||
|
|
||
|
return msg
|
||
|
end if
|
||
|
|
||
|
return ""
|
||
|
end function
|
||
|
|
||
|
' ----------------------------------------------------------------
|
||
|
' Fail if the item is not empty array or string.
|
||
|
|
||
|
' @param item (dynamic) An array or string to check.
|
||
|
' @param msg (string) An error message.
|
||
|
' Default value: ""
|
||
|
|
||
|
' @return An error message.
|
||
|
' ----------------------------------------------------------------
|
||
|
function BTS__AssertEmpty(item as dynamic, msg = "" as string) as string
|
||
|
if TF_Utils__IsAssociativeArray(item) or TF_Utils__IsArray(item)
|
||
|
if item.Count() > 0
|
||
|
msg = "Array is not empty."
|
||
|
|
||
|
return msg
|
||
|
end if
|
||
|
else if TF_Utils__IsString(item)
|
||
|
if Len(item) <> 0
|
||
|
msg = "Input value is not empty."
|
||
|
|
||
|
return msg
|
||
|
end if
|
||
|
else
|
||
|
msg = "Input value is not an Array, AssociativeArray or String."
|
||
|
|
||
|
return msg
|
||
|
end if
|
||
|
|
||
|
return ""
|
||
|
end function
|
||
|
|
||
|
' ----------------------------------------------------------------
|
||
|
' Fail if the item is empty array or string.
|
||
|
|
||
|
' @param item (dynamic) An array or string to check.
|
||
|
' @param msg (string) An error message.
|
||
|
' Default value: ""
|
||
|
|
||
|
' @return An error message.
|
||
|
' ----------------------------------------------------------------
|
||
|
function BTS__AssertNotEmpty(item as dynamic, msg = "" as string) as string
|
||
|
if TF_Utils__IsAssociativeArray(item) or TF_Utils__IsArray(item)
|
||
|
if item.Count() = 0
|
||
|
msg = "Array is empty."
|
||
|
|
||
|
return msg
|
||
|
end if
|
||
|
else if TF_Utils__IsString(item)
|
||
|
if Len(item) = 0
|
||
|
msg = "Input value is empty."
|
||
|
|
||
|
return msg
|
||
|
end if
|
||
|
else
|
||
|
msg = "Input value is not an Array, AssociativeArray or String."
|
||
|
|
||
|
return msg
|
||
|
end if
|
||
|
|
||
|
return ""
|
||
|
end function
|
||
|
|
||
|
'*****************************************************************
|
||
|
'* Copyright Roku 2011-2019
|
||
|
'* All Rights Reserved
|
||
|
'*****************************************************************
|
||
|
|
||
|
' Functions in this file:
|
||
|
' ItemGenerator
|
||
|
' IG_GetItem
|
||
|
' IG_GetAssocArray
|
||
|
' IG_GetArray
|
||
|
' IG_GetSimpleType
|
||
|
' IG_GetBoolean
|
||
|
' IG_GetInteger
|
||
|
' IG_GetFloat
|
||
|
' IG_GetString
|
||
|
|
||
|
' ----------------------------------------------------------------
|
||
|
' Main function to generate object according to specified scheme.
|
||
|
|
||
|
' @param scheme (object) A scheme with desired object structure. Can be
|
||
|
' any simple type, array of types or associative array in form
|
||
|
' { propertyName1 : "propertyType1"
|
||
|
' propertyName2 : "propertyType2"
|
||
|
' ...
|
||
|
' propertyNameN : "propertyTypeN" }
|
||
|
|
||
|
' @return An object according to specified scheme or invalid,
|
||
|
' if scheme is not valid.
|
||
|
' ----------------------------------------------------------------
|
||
|
function ItemGenerator(scheme as object) as object
|
||
|
this = {}
|
||
|
|
||
|
this.getItem = IG_GetItem
|
||
|
this.getAssocArray = IG_GetAssocArray
|
||
|
this.getArray = IG_GetArray
|
||
|
this.getSimpleType = IG_GetSimpleType
|
||
|
this.getInteger = IG_GetInteger
|
||
|
this.getFloat = IG_GetFloat
|
||
|
this.getString = IG_GetString
|
||
|
this.getBoolean = IG_GetBoolean
|
||
|
|
||
|
if not TF_Utils__IsValid(scheme)
|
||
|
return invalid
|
||
|
end if
|
||
|
|
||
|
return this.getItem(scheme)
|
||
|
end function
|
||
|
|
||
|
' TODO: Create IG_GetInvalidItem function with random type fields
|
||
|
|
||
|
' ----------------------------------------------------------------
|
||
|
' Generate object according to specified scheme.
|
||
|
|
||
|
' @param scheme (object) A scheme with desired object structure.
|
||
|
' Can be any simple type, array of types or associative array.
|
||
|
|
||
|
' @return An object according to specified scheme or invalid,
|
||
|
' if scheme is not one of simple type, array or
|
||
|
' associative array.
|
||
|
' ----------------------------------------------------------------
|
||
|
function IG_GetItem(scheme as object) as object
|
||
|
item = invalid
|
||
|
|
||
|
if TF_Utils__IsAssociativeArray(scheme)
|
||
|
item = IG_GetAssocArray(scheme)
|
||
|
else if TF_Utils__IsArray(scheme)
|
||
|
item = IG_GetArray(scheme)
|
||
|
else if TF_Utils__IsString(scheme)
|
||
|
item = IG_GetSimpleType(LCase(scheme))
|
||
|
end if
|
||
|
|
||
|
return item
|
||
|
end function
|
||
|
|
||
|
' ----------------------------------------------------------------
|
||
|
' Generates associative array according to specified scheme.
|
||
|
|
||
|
' @param scheme (object) An associative array with desired
|
||
|
' object structure in form
|
||
|
' { propertyName1 : "propertyType1"
|
||
|
' propertyName2 : "propertyType2"
|
||
|
' ...
|
||
|
' propertyNameN : "propertyTypeN" }
|
||
|
|
||
|
' @return An associative array according to specified scheme.
|
||
|
' ----------------------------------------------------------------
|
||
|
function IG_GetAssocArray(scheme as object) as object
|
||
|
item = {}
|
||
|
|
||
|
for each key in scheme
|
||
|
if not item.DoesExist(key)
|
||
|
item[key] = IG_GetItem(scheme[key])
|
||
|
end if
|
||
|
end for
|
||
|
|
||
|
return item
|
||
|
end function
|
||
|
|
||
|
' ----------------------------------------------------------------
|
||
|
' Generates array according to specified scheme.
|
||
|
|
||
|
' @param scheme (object) An array with desired object types.
|
||
|
|
||
|
' @return An array according to specified scheme.
|
||
|
' ----------------------------------------------------------------
|
||
|
function IG_GetArray(scheme as object) as object
|
||
|
item = []
|
||
|
|
||
|
for each key in scheme
|
||
|
item.Push(IG_GetItem(key))
|
||
|
end for
|
||
|
|
||
|
return item
|
||
|
end function
|
||
|
|
||
|
' ----------------------------------------------------------------
|
||
|
' Generates random value of specified type.
|
||
|
|
||
|
' @param typeStr (string) A name of desired object type.
|
||
|
|
||
|
' @return A simple type object or invalid if type is not supported.
|
||
|
' ----------------------------------------------------------------
|
||
|
function IG_GetSimpleType(typeStr as string) as object
|
||
|
item = invalid
|
||
|
|
||
|
if typeStr = "integer" or typeStr = "int" or typeStr = "roint"
|
||
|
item = IG_GetInteger()
|
||
|
else if typeStr = "float" or typeStr = "rofloat"
|
||
|
item = IG_GetFloat()
|
||
|
else if typeStr = "string" or typeStr = "rostring"
|
||
|
item = IG_GetString(10)
|
||
|
else if typeStr = "boolean" or typeStr = "roboolean"
|
||
|
item = IG_GetBoolean()
|
||
|
end if
|
||
|
|
||
|
return item
|
||
|
end function
|
||
|
|
||
|
' ----------------------------------------------------------------
|
||
|
' Generates random boolean value.
|
||
|
|
||
|
' @return A random boolean value.
|
||
|
' ----------------------------------------------------------------
|
||
|
function IG_GetBoolean() as boolean
|
||
|
return TF_Utils__AsBoolean(Rnd(2) \ Rnd(2))
|
||
|
end function
|
||
|
|
||
|
' ----------------------------------------------------------------
|
||
|
' Generates random integer value from 1 to specified seed value.
|
||
|
|
||
|
' @param seed (integer) A seed value for Rnd function.
|
||
|
' Default value: 100.
|
||
|
|
||
|
' @return A random integer value.
|
||
|
' ----------------------------------------------------------------
|
||
|
function IG_GetInteger(seed = 100 as integer) as integer
|
||
|
return Rnd(seed)
|
||
|
end function
|
||
|
|
||
|
' ----------------------------------------------------------------
|
||
|
' Generates random float value.
|
||
|
|
||
|
' @return A random float value.
|
||
|
' ----------------------------------------------------------------
|
||
|
function IG_GetFloat() as float
|
||
|
return Rnd(0)
|
||
|
end function
|
||
|
|
||
|
' ----------------------------------------------------------------
|
||
|
' Generates random string with specified length.
|
||
|
|
||
|
' @param seed (integer) A string length.
|
||
|
|
||
|
' @return A random string value or empty string if seed is 0.
|
||
|
' ----------------------------------------------------------------
|
||
|
function IG_GetString(seed as integer) as string
|
||
|
item = ""
|
||
|
if seed > 0
|
||
|
stringLength = Rnd(seed)
|
||
|
|
||
|
for i = 0 to stringLength
|
||
|
chType = Rnd(3)
|
||
|
|
||
|
if chType = 1 ' Chr(48-57) - numbers
|
||
|
chNumber = 47 + Rnd(10)
|
||
|
else if chType = 2 ' Chr(65-90) - Uppercase Letters
|
||
|
chNumber = 64 + Rnd(26)
|
||
|
else ' Chr(97-122) - Lowercase Letters
|
||
|
chNumber = 96 + Rnd(26)
|
||
|
end if
|
||
|
|
||
|
item = item + Chr(chNumber)
|
||
|
end for
|
||
|
end if
|
||
|
|
||
|
return item
|
||
|
end function
|
||
|
'*****************************************************************
|
||
|
'* Copyright Roku 2011-2019
|
||
|
'* All Rights Reserved
|
||
|
'*****************************************************************
|
||
|
|
||
|
' Functions in this file:
|
||
|
' Logger
|
||
|
' Logger__SetVerbosity
|
||
|
' Logger__SetEcho
|
||
|
' Logger__SetServerURL
|
||
|
' Logger__PrintStatistic
|
||
|
' Logger__SendToServer
|
||
|
' Logger__CreateTotalStatistic
|
||
|
' Logger__CreateSuiteStatistic
|
||
|
' Logger__CreateTestStatistic
|
||
|
' Logger__AppendSuiteStatistic
|
||
|
' Logger__AppendTestStatistic
|
||
|
' Logger__PrintSuiteStatistic
|
||
|
' Logger__PrintTestStatistic
|
||
|
' Logger__PrintStart
|
||
|
' Logger__PrintEnd
|
||
|
' Logger__PrintSuiteSetUp
|
||
|
' Logger__PrintSuiteStart
|
||
|
' Logger__PrintSuiteEnd
|
||
|
' Logger__PrintSuiteTearDown
|
||
|
' Logger__PrintTestSetUp
|
||
|
' Logger__PrintTestStart
|
||
|
' Logger__PrintTestEnd
|
||
|
' Logger__PrintTestTearDown
|
||
|
|
||
|
' ----------------------------------------------------------------
|
||
|
' Main function. Create Logger object.
|
||
|
|
||
|
' @return A Logger object.
|
||
|
' ----------------------------------------------------------------
|
||
|
function Logger() as object
|
||
|
this = {}
|
||
|
|
||
|
this.verbosityLevel = {
|
||
|
basic: 0
|
||
|
normal: 1
|
||
|
verboseFailed: 2
|
||
|
verbose: 3
|
||
|
}
|
||
|
|
||
|
' Internal properties
|
||
|
this.verbosity = this.verbosityLevel.normal
|
||
|
this.echoEnabled = false
|
||
|
this.serverURL = ""
|
||
|
this.jUnitEnabled = false
|
||
|
|
||
|
' Interface
|
||
|
this.SetVerbosity = Logger__SetVerbosity
|
||
|
this.SetEcho = Logger__SetEcho
|
||
|
this.SetJUnit = Logger__SetJUnit
|
||
|
this.SetServer = Logger__SetServer
|
||
|
this.SetServerURL = Logger__SetServerURL ' Deprecated. Use Logger__SetServer instead.
|
||
|
this.PrintStatistic = Logger__PrintStatistic
|
||
|
this.SendToServer = Logger__SendToServer
|
||
|
|
||
|
this.CreateTotalStatistic = Logger__CreateTotalStatistic
|
||
|
this.CreateSuiteStatistic = Logger__CreateSuiteStatistic
|
||
|
this.CreateTestStatistic = Logger__CreateTestStatistic
|
||
|
this.AppendSuiteStatistic = Logger__AppendSuiteStatistic
|
||
|
this.AppendTestStatistic = Logger__AppendTestStatistic
|
||
|
|
||
|
' Internal functions
|
||
|
this.PrintSuiteStatistic = Logger__PrintSuiteStatistic
|
||
|
this.PrintTestStatistic = Logger__PrintTestStatistic
|
||
|
this.PrintStart = Logger__PrintStart
|
||
|
this.PrintEnd = Logger__PrintEnd
|
||
|
this.PrintSuiteSetUp = Logger__PrintSuiteSetUp
|
||
|
this.PrintSuiteStart = Logger__PrintSuiteStart
|
||
|
this.PrintSuiteEnd = Logger__PrintSuiteEnd
|
||
|
this.PrintSuiteTearDown = Logger__PrintSuiteTearDown
|
||
|
this.PrintTestSetUp = Logger__PrintTestSetUp
|
||
|
this.PrintTestStart = Logger__PrintTestStart
|
||
|
this.PrintTestEnd = Logger__PrintTestEnd
|
||
|
this.PrintTestTearDown = Logger__PrintTestTearDown
|
||
|
this.PrintJUnitFormat = Logger__PrintJUnitFormat
|
||
|
|
||
|
return this
|
||
|
end function
|
||
|
|
||
|
' ----------------------------------------------------------------
|
||
|
' Set logging verbosity parameter.
|
||
|
|
||
|
' @param verbosity (integer) A verbosity level.
|
||
|
' Posible values:
|
||
|
' 0 - basic
|
||
|
' 1 - normal
|
||
|
' 2 - verbose failed tests
|
||
|
' 3 - verbose
|
||
|
' Default level: 1
|
||
|
' ----------------------------------------------------------------
|
||
|
sub Logger__SetVerbosity(verbosity = m.verbosityLevel.normal as integer)
|
||
|
if verbosity >= m.verbosityLevel.basic and verbosity <= m.verbosityLevel.verbose
|
||
|
m.verbosity = verbosity
|
||
|
end if
|
||
|
end sub
|
||
|
|
||
|
' ----------------------------------------------------------------
|
||
|
' Set logging echo parameter.
|
||
|
|
||
|
' @param enable (boolean) A echo trigger.
|
||
|
' Posible values: true or false
|
||
|
' Default value: false
|
||
|
' ----------------------------------------------------------------
|
||
|
sub Logger__SetEcho(enable = false as boolean)
|
||
|
m.echoEnabled = enable
|
||
|
end sub
|
||
|
|
||
|
' ----------------------------------------------------------------
|
||
|
' Set logging JUnit output parameter.
|
||
|
|
||
|
' @param enable (boolean) A JUnit output trigger.
|
||
|
' Posible values: true or false
|
||
|
' Default value: false
|
||
|
' ----------------------------------------------------------------
|
||
|
sub Logger__SetJUnit(enable = false as boolean)
|
||
|
m.jUnitEnabled = enable
|
||
|
end sub
|
||
|
|
||
|
' ----------------------------------------------------------------
|
||
|
' Set storage server parameters.
|
||
|
|
||
|
' @param url (string) Storage server host.
|
||
|
' Default value: ""
|
||
|
' @param port (string) Storage server port.
|
||
|
' Default value: ""
|
||
|
' ----------------------------------------------------------------
|
||
|
sub Logger__SetServer(host = "" as string, port = "" as string)
|
||
|
if TF_Utils__IsNotEmptyString(host)
|
||
|
if TF_Utils__IsNotEmptyString(port)
|
||
|
m.serverURL = "http://" + host + ":" + port
|
||
|
else
|
||
|
m.serverURL = "http://" + host
|
||
|
end if
|
||
|
end if
|
||
|
end sub
|
||
|
|
||
|
' ----------------------------------------------------------------
|
||
|
' Set storage server URL parameter.
|
||
|
|
||
|
' @param url (string) A storage server URL.
|
||
|
' Default value: ""
|
||
|
' ----------------------------------------------------------------
|
||
|
sub Logger__SetServerURL(url = "" as string)
|
||
|
? "This function is deprecated. Please use Logger__SetServer(host, port)"
|
||
|
end sub
|
||
|
|
||
|
'----------------------------------------------------------------
|
||
|
' Send test results as a POST json payload.
|
||
|
'
|
||
|
' @param statObj (object) stats of the test run.
|
||
|
' Default value: invalid
|
||
|
' ----------------------------------------------------------------
|
||
|
sub Logger__SendToServer(statObj as object)
|
||
|
if TF_Utils__IsNotEmptyString(m.serverURL) and TF_Utils__IsValid(statObj)
|
||
|
? "***"
|
||
|
? "*** Sending statsObj to server: "; m.serverURL
|
||
|
|
||
|
request = CreateObject("roUrlTransfer")
|
||
|
request.SetUrl(m.serverURL)
|
||
|
statString = FormatJson(statObj)
|
||
|
|
||
|
? "*** Response: "; request.postFromString(statString)
|
||
|
? "***"
|
||
|
? "******************************************************************"
|
||
|
end if
|
||
|
end sub
|
||
|
|
||
|
' ----------------------------------------------------------------
|
||
|
' Print statistic object with specified verbosity.
|
||
|
|
||
|
' @param statObj (object) A statistic object to print.
|
||
|
' ----------------------------------------------------------------
|
||
|
sub Logger__PrintStatistic(statObj as object)
|
||
|
if not m.echoEnabled
|
||
|
m.PrintStart()
|
||
|
|
||
|
if m.verbosity = m.verbosityLevel.normal or m.verbosity = m.verbosityLevel.verboseFailed
|
||
|
for each testSuite in statObj.Suites
|
||
|
for each testCase in testSuite.Tests
|
||
|
if m.verbosity = m.verbosityLevel.verboseFailed and testCase.result = "Fail"
|
||
|
m.printTestStatistic(testCase)
|
||
|
else
|
||
|
? "*** "; testSuite.Name; ": "; testCase.Name; " - "; testCase.Result
|
||
|
end if
|
||
|
end for
|
||
|
end for
|
||
|
else if m.verbosity = m.verbosityLevel.verbose
|
||
|
for each testSuite in statObj.Suites
|
||
|
m.PrintSuiteStatistic(testSuite)
|
||
|
end for
|
||
|
end if
|
||
|
end if
|
||
|
|
||
|
? "***"
|
||
|
? "*** Total = "; TF_Utils__AsString(statObj.Total); " ; Passed = "; statObj.Correct; " ; Failed = "; statObj.Fail; " ; Skipped = "; statObj.skipped; " ; Crashes = "; statObj.Crash;
|
||
|
? "*** Time spent: "; statObj.Time; "ms"
|
||
|
? "***"
|
||
|
|
||
|
m.PrintEnd()
|
||
|
|
||
|
m.SendToServer(statObj)
|
||
|
|
||
|
if m.jUnitEnabled
|
||
|
m.printJUnitFormat(statObj)
|
||
|
end if
|
||
|
end sub
|
||
|
|
||
|
' ----------------------------------------------------------------
|
||
|
' Create an empty statistic object for totals in output log.
|
||
|
|
||
|
' @return An empty statistic object.
|
||
|
' ----------------------------------------------------------------
|
||
|
function Logger__CreateTotalStatistic() as object
|
||
|
statTotalItem = {
|
||
|
Suites: []
|
||
|
Time: 0
|
||
|
Total: 0
|
||
|
Correct: 0
|
||
|
Fail: 0
|
||
|
Skipped: 0
|
||
|
Crash: 0
|
||
|
}
|
||
|
|
||
|
if m.echoEnabled
|
||
|
m.PrintStart()
|
||
|
end if
|
||
|
|
||
|
return statTotalItem
|
||
|
end function
|
||
|
|
||
|
' ----------------------------------------------------------------
|
||
|
' Create an empty statistic object for test suite with specified name.
|
||
|
|
||
|
' @param name (string) A test suite name for statistic object.
|
||
|
|
||
|
' @return An empty statistic object for test suite.
|
||
|
' ----------------------------------------------------------------
|
||
|
function Logger__CreateSuiteStatistic(name as string) as object
|
||
|
statSuiteItem = {
|
||
|
Name: name
|
||
|
Tests: []
|
||
|
Time: 0
|
||
|
Total: 0
|
||
|
Correct: 0
|
||
|
Fail: 0
|
||
|
Skipped: 0
|
||
|
Crash: 0
|
||
|
}
|
||
|
|
||
|
if m.echoEnabled
|
||
|
if m.verbosity = m.verbosityLevel.verbose
|
||
|
m.PrintSuiteStart(name)
|
||
|
end if
|
||
|
end if
|
||
|
|
||
|
return statSuiteItem
|
||
|
end function
|
||
|
|
||
|
' ----------------------------------------------------------------
|
||
|
' Create statistic object for test with specified name.
|
||
|
|
||
|
' @param name (string) A test name.
|
||
|
' @param result (string) A result of test running.
|
||
|
' Posible values: "Success", "Fail".
|
||
|
' Default value: "Success"
|
||
|
' @param time (integer) A test running time.
|
||
|
' Default value: 0
|
||
|
' @param errorCode (integer) An error code for failed test.
|
||
|
' Posible values:
|
||
|
' 252 (&hFC) : ERR_NORMAL_END
|
||
|
' 226 (&hE2) : ERR_VALUE_RETURN
|
||
|
' 233 (&hE9) : ERR_USE_OF_UNINIT_VAR
|
||
|
' 020 (&h14) : ERR_DIV_ZERO
|
||
|
' 024 (&h18) : ERR_TM
|
||
|
' 244 (&hF4) : ERR_RO2
|
||
|
' 236 (&hEC) : ERR_RO4
|
||
|
' 002 (&h02) : ERR_SYNTAX
|
||
|
' 241 (&hF1) : ERR_WRONG_NUM_PARAM
|
||
|
' Default value: 0
|
||
|
' @param errorMessage (string) An error message for failed test.
|
||
|
|
||
|
' @return A statistic object for test.
|
||
|
' ----------------------------------------------------------------
|
||
|
function Logger__CreateTestStatistic(name as string, result = "Success" as string, time = 0 as integer, errorCode = 0 as integer, errorMessage = "" as string, isInit = false as boolean) as object
|
||
|
statTestItem = {
|
||
|
Name: name
|
||
|
Result: result
|
||
|
Time: time
|
||
|
PerfData: {}
|
||
|
Error: {
|
||
|
Code: errorCode
|
||
|
Message: errorMessage
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if m.echoEnabled and not isInit
|
||
|
if m.verbosity = m.verbosityLevel.verbose
|
||
|
m.PrintTestStart(name)
|
||
|
end if
|
||
|
end if
|
||
|
|
||
|
return statTestItem
|
||
|
end function
|
||
|
|
||
|
' ----------------------------------------------------------------
|
||
|
' Append test statistic to test suite statistic.
|
||
|
|
||
|
' @param statSuiteObj (object) A target test suite object.
|
||
|
' @param statTestObj (object) A test statistic to append.
|
||
|
' ----------------------------------------------------------------
|
||
|
sub Logger__AppendTestStatistic(statSuiteObj as object, statTestObj as object)
|
||
|
if TF_Utils__IsAssociativeArray(statSuiteObj) and TF_Utils__IsAssociativeArray(statTestObj)
|
||
|
statSuiteObj.Tests.Push(statTestObj)
|
||
|
|
||
|
if TF_Utils__IsInteger(statTestObj.time)
|
||
|
statSuiteObj.Time = statSuiteObj.Time + statTestObj.Time
|
||
|
end if
|
||
|
|
||
|
statSuiteObj.Total = statSuiteObj.Total + 1
|
||
|
|
||
|
if LCase(statTestObj.Result) = "success"
|
||
|
statSuiteObj.Correct = statSuiteObj.Correct + 1
|
||
|
else if LCase(statTestObj.result) = "fail"
|
||
|
statSuiteObj.Fail = statSuiteObj.Fail + 1
|
||
|
else if LCase(statTestObj.result) = "skipped"
|
||
|
statSuiteObj.skipped++
|
||
|
else
|
||
|
statSuiteObj.crash = statSuiteObj.crash + 1
|
||
|
end if
|
||
|
|
||
|
if m.echoEnabled
|
||
|
if m.verbosity = m.verbosityLevel.normal
|
||
|
? "*** "; statSuiteObj.Name; ": "; statTestObj.Name; " - "; statTestObj.Result
|
||
|
else if m.verbosity = m.verbosityLevel.verbose
|
||
|
m.PrintTestStatistic(statTestObj)
|
||
|
end if
|
||
|
end if
|
||
|
end if
|
||
|
end sub
|
||
|
|
||
|
' ----------------------------------------------------------------
|
||
|
' Append suite statistic to total statistic object.
|
||
|
|
||
|
' @param statTotalObj (object) A target total statistic object.
|
||
|
' @param statSuiteObj (object) A test suite statistic object to append.
|
||
|
' ----------------------------------------------------------------
|
||
|
sub Logger__AppendSuiteStatistic(statTotalObj as object, statSuiteObj as object)
|
||
|
if TF_Utils__IsAssociativeArray(statTotalObj) and TF_Utils__IsAssociativeArray(statSuiteObj)
|
||
|
statTotalObj.Suites.Push(statSuiteObj)
|
||
|
statTotalObj.Time = statTotalObj.Time + statSuiteObj.Time
|
||
|
|
||
|
if TF_Utils__IsInteger(statSuiteObj.Total)
|
||
|
statTotalObj.Total = statTotalObj.Total + statSuiteObj.Total
|
||
|
end if
|
||
|
|
||
|
if TF_Utils__IsInteger(statSuiteObj.Correct)
|
||
|
statTotalObj.Correct = statTotalObj.Correct + statSuiteObj.Correct
|
||
|
end if
|
||
|
|
||
|
if TF_Utils__IsInteger(statSuiteObj.Fail)
|
||
|
statTotalObj.Fail = statTotalObj.Fail + statSuiteObj.Fail
|
||
|
end if
|
||
|
|
||
|
if TF_Utils__IsInteger(statSuiteObj.skipped)
|
||
|
statTotalObj.skipped += statSuiteObj.skipped
|
||
|
end if
|
||
|
|
||
|
if TF_Utils__IsInteger(statSuiteObj.Crash)
|
||
|
statTotalObj.Crash = statTotalObj.Crash + statSuiteObj.Crash
|
||
|
end if
|
||
|
|
||
|
if m.echoEnabled
|
||
|
if m.verbosity = m.verbosityLevel.verbose
|
||
|
m.PrintSuiteStatistic(statSuiteObj)
|
||
|
end if
|
||
|
end if
|
||
|
end if
|
||
|
end sub
|
||
|
|
||
|
' ----------------------------------------------------------------
|
||
|
' Print test suite statistic.
|
||
|
|
||
|
' @param statSuiteObj (object) A target test suite object to print.
|
||
|
' ----------------------------------------------------------------
|
||
|
sub Logger__PrintSuiteStatistic(statSuiteObj as object)
|
||
|
if not m.echoEnabled
|
||
|
m.PrintSuiteStart(statSuiteObj.Name)
|
||
|
|
||
|
for each testCase in statSuiteObj.Tests
|
||
|
m.PrintTestStatistic(testCase)
|
||
|
end for
|
||
|
end if
|
||
|
|
||
|
? "==="
|
||
|
? "=== Total = "; TF_Utils__AsString(statSuiteObj.Total); " ; Passed = "; statSuiteObj.Correct; " ; Failed = "; statSuiteObj.Fail; " ; Skipped = "; statSuiteObj.skipped; " ; Crashes = "; statSuiteObj.Crash;
|
||
|
? " Time spent: "; statSuiteObj.Time; "ms"
|
||
|
? "==="
|
||
|
|
||
|
m.PrintSuiteEnd(statSuiteObj.Name)
|
||
|
end sub
|
||
|
|
||
|
' ----------------------------------------------------------------
|
||
|
' Print test statistic.
|
||
|
|
||
|
' @param statTestObj (object) A target test object to print.
|
||
|
' ----------------------------------------------------------------
|
||
|
sub Logger__PrintTestStatistic(statTestObj as object)
|
||
|
if not m.echoEnabled
|
||
|
m.PrintTestStart(statTestObj.Name)
|
||
|
end if
|
||
|
|
||
|
? "--- Result: "; statTestObj.Result
|
||
|
? "--- Time: "; statTestObj.Time
|
||
|
|
||
|
if LCase(statTestObj.result) = "skipped"
|
||
|
if Len(statTestObj.message) > 0
|
||
|
? "--- Message: "; statTestObj.message
|
||
|
end if
|
||
|
else if LCase(statTestObj.Result) <> "success"
|
||
|
? "--- Error Code: "; statTestObj.Error.Code
|
||
|
? "--- Error Message: "; statTestObj.Error.Message
|
||
|
end if
|
||
|
|
||
|
m.PrintTestEnd(statTestObj.Name)
|
||
|
end sub
|
||
|
|
||
|
' ----------------------------------------------------------------
|
||
|
' Print testting start message.
|
||
|
' ----------------------------------------------------------------
|
||
|
sub Logger__PrintStart()
|
||
|
? ""
|
||
|
? "******************************************************************"
|
||
|
? "******************************************************************"
|
||
|
? "************* Start testing *************"
|
||
|
? "******************************************************************"
|
||
|
end sub
|
||
|
|
||
|
' ----------------------------------------------------------------
|
||
|
' Print testing end message.
|
||
|
' ----------------------------------------------------------------
|
||
|
sub Logger__PrintEnd()
|
||
|
? "******************************************************************"
|
||
|
? "************* End testing *************"
|
||
|
? "******************************************************************"
|
||
|
? "******************************************************************"
|
||
|
? ""
|
||
|
end sub
|
||
|
|
||
|
' ----------------------------------------------------------------
|
||
|
' Print test suite SetUp message.
|
||
|
' ----------------------------------------------------------------
|
||
|
sub Logger__PrintSuiteSetUp(sName as string)
|
||
|
if m.verbosity = m.verbosityLevel.verbose
|
||
|
? "================================================================="
|
||
|
? "=== SetUp "; sName; " suite."
|
||
|
? "================================================================="
|
||
|
end if
|
||
|
end sub
|
||
|
|
||
|
' ----------------------------------------------------------------
|
||
|
' Print test suite start message.
|
||
|
' ----------------------------------------------------------------
|
||
|
sub Logger__PrintSuiteStart(sName as string)
|
||
|
? "================================================================="
|
||
|
? "=== Start "; sName; " suite:"
|
||
|
? "==="
|
||
|
end sub
|
||
|
|
||
|
' ----------------------------------------------------------------
|
||
|
' Print test suite end message.
|
||
|
' ----------------------------------------------------------------
|
||
|
sub Logger__PrintSuiteEnd(sName as string)
|
||
|
? "==="
|
||
|
? "=== End "; sName; " suite."
|
||
|
? "================================================================="
|
||
|
end sub
|
||
|
|
||
|
' ----------------------------------------------------------------
|
||
|
' Print test suite TearDown message.
|
||
|
' ----------------------------------------------------------------
|
||
|
sub Logger__PrintSuiteTearDown(sName as string)
|
||
|
if m.verbosity = m.verbosityLevel.verbose
|
||
|
? "================================================================="
|
||
|
? "=== TearDown "; sName; " suite."
|
||
|
? "================================================================="
|
||
|
end if
|
||
|
end sub
|
||
|
|
||
|
' ----------------------------------------------------------------
|
||
|
' Print test setUp message.
|
||
|
' ----------------------------------------------------------------
|
||
|
sub Logger__PrintTestSetUp(tName as string)
|
||
|
if m.verbosity = m.verbosityLevel.verbose
|
||
|
? "----------------------------------------------------------------"
|
||
|
? "--- SetUp "; tName; " test."
|
||
|
? "----------------------------------------------------------------"
|
||
|
end if
|
||
|
end sub
|
||
|
|
||
|
' ----------------------------------------------------------------
|
||
|
' Print test start message.
|
||
|
' ----------------------------------------------------------------
|
||
|
sub Logger__PrintTestStart(tName as string)
|
||
|
? "----------------------------------------------------------------"
|
||
|
? "--- Start "; tName; " test:"
|
||
|
? "---"
|
||
|
end sub
|
||
|
|
||
|
' ----------------------------------------------------------------
|
||
|
' Print test end message.
|
||
|
' ----------------------------------------------------------------
|
||
|
sub Logger__PrintTestEnd(tName as string)
|
||
|
? "---"
|
||
|
? "--- End "; tName; " test."
|
||
|
? "----------------------------------------------------------------"
|
||
|
end sub
|
||
|
|
||
|
' ----------------------------------------------------------------
|
||
|
' Print test TearDown message.
|
||
|
' ----------------------------------------------------------------
|
||
|
sub Logger__PrintTestTearDown(tName as string)
|
||
|
if m.verbosity = m.verbosityLevel.verbose
|
||
|
? "----------------------------------------------------------------"
|
||
|
? "--- TearDown "; tName; " test."
|
||
|
? "----------------------------------------------------------------"
|
||
|
end if
|
||
|
end sub
|
||
|
|
||
|
sub Logger__PrintJUnitFormat(statObj as object)
|
||
|
' TODO finish report
|
||
|
xml = CreateObject("roXMLElement")
|
||
|
xml.SetName("testsuites")
|
||
|
for each testSuiteAA in statObj.suites
|
||
|
testSuite = xml.AddElement("testsuite")
|
||
|
' name="FeatureManagerTest" time="13.923" tests="2" errors="0" skipped="0" failures="0"
|
||
|
testSuite.AddAttribute("name", testSuiteAA.name)
|
||
|
testSuite.AddAttribute("time", testSuiteAA.time.toStr())
|
||
|
testSuite.AddAttribute("tests", testSuiteAA.Tests.count().toStr())
|
||
|
|
||
|
skippedNum = 0
|
||
|
failedNum = 0
|
||
|
for each testAA in testSuiteAA.Tests
|
||
|
test = testSuite.AddElement("testcase")
|
||
|
test.AddAttribute("name", testAA.name)
|
||
|
test.AddAttribute("time", testAA.time.toStr())
|
||
|
|
||
|
if LCase(testAA.result) = "skipped" then
|
||
|
test.AddElement("skipped")
|
||
|
skippedNum++
|
||
|
else if LCase(testAA.Result) <> "success"
|
||
|
failure = test.AddElement("failure")
|
||
|
failure.AddAttribute("message", testAA.error.message)
|
||
|
failure.AddAttribute("type", testAA.error.code.tostr())
|
||
|
failedNum++
|
||
|
end if
|
||
|
end for
|
||
|
testSuite.AddAttribute("errors", failedNum.tostr())
|
||
|
testSuite.AddAttribute("skipped", skippedNum.tostr())
|
||
|
end for
|
||
|
? xml.GenXML(true)
|
||
|
end sub
|
||
|
'*****************************************************************
|
||
|
'* Copyright Roku 2011-2019
|
||
|
'* All Rights Reserved
|
||
|
'*****************************************************************
|
||
|
|
||
|
' Functions in this file:
|
||
|
' TestRunner
|
||
|
' TestRunner__Run
|
||
|
' TestRunner__SetTestsDirectory
|
||
|
' TestRunner__SetTestFilePrefix
|
||
|
' TestRunner__SetTestSuitePrefix
|
||
|
' TestRunner__SetTestSuiteName
|
||
|
' TestRunner__SetTestCaseName
|
||
|
' TestRunner__SetFailFast
|
||
|
' TestRunner__GetTestSuitesList
|
||
|
' TestRunner__GetTestSuiteNamesList
|
||
|
' TestRunner__GetTestFilesList
|
||
|
' TestRunner__GetTestNodesList
|
||
|
' TestFramework__RunNodeTests
|
||
|
|
||
|
' ----------------------------------------------------------------
|
||
|
' Main function. Create TestRunner object.
|
||
|
|
||
|
' @return A TestRunner object.
|
||
|
' ----------------------------------------------------------------
|
||
|
function TestRunner() as object
|
||
|
this = {}
|
||
|
GetGlobalAA().globalErrorsList = []
|
||
|
this.isNodeMode = GetGlobalAA().top <> invalid
|
||
|
this.Logger = Logger()
|
||
|
|
||
|
' Internal properties
|
||
|
this.SKIP_TEST_MESSAGE_PREFIX = "SKIP_TEST_MESSAGE_PREFIX__"
|
||
|
this.nodesTestDirectory = "pkg:/components/tests"
|
||
|
if this.isNodeMode
|
||
|
this.testsDirectory = this.nodesTestDirectory
|
||
|
this.testFilePrefix = m.top.subtype()
|
||
|
else
|
||
|
this.testsDirectory = "pkg:/source/tests"
|
||
|
this.testFilePrefix = "Test__"
|
||
|
end if
|
||
|
this.testSuitePrefix = "TestSuite__"
|
||
|
this.testSuiteName = ""
|
||
|
this.testCaseName = ""
|
||
|
this.failFast = false
|
||
|
|
||
|
' Interface
|
||
|
this.Run = TestRunner__Run
|
||
|
this.SetTestsDirectory = TestRunner__SetTestsDirectory
|
||
|
this.SetTestFilePrefix = TestRunner__SetTestFilePrefix
|
||
|
this.SetTestSuitePrefix = TestRunner__SetTestSuitePrefix
|
||
|
this.SetTestSuiteName = TestRunner__SetTestSuiteName ' Obsolete, will be removed in next versions
|
||
|
this.SetTestCaseName = TestRunner__SetTestCaseName ' Obsolete, will be removed in next versions
|
||
|
this.SetFailFast = TestRunner__SetFailFast
|
||
|
this.SetFunctions = TestRunner__SetFunctions
|
||
|
this.SetIncludeFilter = TestRunner__SetIncludeFilter
|
||
|
this.SetExcludeFilter = TestRunner__SetExcludeFilter
|
||
|
|
||
|
' Internal functions
|
||
|
this.GetTestFilesList = TestRunner__GetTestFilesList
|
||
|
this.GetTestSuitesList = TestRunner__GetTestSuitesList
|
||
|
this.GetTestNodesList = TestRunner__GetTestNodesList
|
||
|
this.GetTestSuiteNamesList = TestRunner__GetTestSuiteNamesList
|
||
|
this.GetIncludeFilter = TestRunner__GetIncludeFilter
|
||
|
this.GetExcludeFilter = TestRunner__GetExcludeFilter
|
||
|
|
||
|
return this
|
||
|
end function
|
||
|
|
||
|
' ----------------------------------------------------------------
|
||
|
' Run main test loop.
|
||
|
|
||
|
' @param statObj (object, optional) statistic object to be used in tests
|
||
|
' @param testSuiteNamesList (array, optional) array of test suite function names to be used in tests
|
||
|
|
||
|
' @return Statistic object if run in node mode, invalid otherwise
|
||
|
' ----------------------------------------------------------------
|
||
|
function TestRunner__Run(statObj = m.Logger.CreateTotalStatistic() as object, testSuiteNamesList = [] as object) as object
|
||
|
alltestCount = 0
|
||
|
totalStatObj = statObj
|
||
|
testSuitesList = m.GetTestSuitesList(testSuiteNamesList)
|
||
|
|
||
|
globalErrorsList = GetGlobalAA().globalErrorsList
|
||
|
for each testSuite in testSuitesList
|
||
|
testCases = testSuite.testCases
|
||
|
testCount = testCases.Count()
|
||
|
alltestCount = alltestCount + testCount
|
||
|
|
||
|
IS_NEW_APPROACH = testSuite.IS_NEW_APPROACH
|
||
|
' create dedicated env for each test, so that they will have not global m and don't rely on m.that is set in another suite
|
||
|
env = {}
|
||
|
|
||
|
if TF_Utils__IsFunction(testSuite.SetUp)
|
||
|
m.Logger.PrintSuiteSetUp(testSuite.Name)
|
||
|
if IS_NEW_APPROACH then
|
||
|
env.functionToCall = testSuite.SetUp
|
||
|
env.functionToCall()
|
||
|
else
|
||
|
testSuite.SetUp()
|
||
|
end if
|
||
|
end if
|
||
|
|
||
|
suiteStatObj = m.Logger.CreateSuiteStatistic(testSuite.Name)
|
||
|
' Initiate empty test statistics object to print results if no tests was run
|
||
|
testStatObj = m.Logger.CreateTestStatistic("", "Success", 0, 0, "", true)
|
||
|
for each testCase in testCases
|
||
|
' clear all existing errors
|
||
|
globalErrorsList.clear()
|
||
|
|
||
|
if m.testCaseName = "" or (m.testCaseName <> "" and LCase(testCase.Name) = LCase(m.testCaseName))
|
||
|
skipTest = TF_Utils__AsBoolean(testCase.skip)
|
||
|
|
||
|
if TF_Utils__IsFunction(testCase.SetUp) and not skipTest
|
||
|
m.Logger.PrintTestSetUp(testCase.Name)
|
||
|
if IS_NEW_APPROACH then
|
||
|
env.functionToCall = testCase.SetUp
|
||
|
env.functionToCall()
|
||
|
else
|
||
|
testCase.SetUp()
|
||
|
end if
|
||
|
end if
|
||
|
|
||
|
testTimer = CreateObject("roTimespan")
|
||
|
testStatObj = m.Logger.CreateTestStatistic(testCase.Name)
|
||
|
|
||
|
if skipTest
|
||
|
runResult = m.SKIP_TEST_MESSAGE_PREFIX + "Test was skipped according to specified filters"
|
||
|
else
|
||
|
testSuite.testInstance = testCase
|
||
|
testSuite.testCase = testCase.Func
|
||
|
|
||
|
runResult = ""
|
||
|
if IS_NEW_APPROACH then
|
||
|
env.functionToCall = testCase.Func
|
||
|
|
||
|
if GetInterface(env.functionToCall, "ifFunction") <> invalid
|
||
|
if testCase.hasArguments then
|
||
|
env.functionToCall(testCase.arg)
|
||
|
else
|
||
|
env.functionToCall()
|
||
|
end if
|
||
|
else
|
||
|
UTF_fail("Failed to execute test """ + testCase.Name + """ function pointer not found")
|
||
|
end if
|
||
|
else
|
||
|
runResult = testSuite.testCase()
|
||
|
end if
|
||
|
end if
|
||
|
|
||
|
if TF_Utils__IsFunction(testCase.TearDown) and not skipTest
|
||
|
m.Logger.PrintTestTearDown(testCase.Name)
|
||
|
if IS_NEW_APPROACH then
|
||
|
env.functionToCall = testCase.TearDown
|
||
|
env.functionToCall()
|
||
|
else
|
||
|
testCase.TearDown()
|
||
|
end if
|
||
|
end if
|
||
|
|
||
|
if IS_NEW_APPROACH then
|
||
|
if globalErrorsList.count() > 0
|
||
|
for each error in globalErrorsList
|
||
|
runResult += error + Chr(10) + string(10, "-") + Chr(10)
|
||
|
end for
|
||
|
end if
|
||
|
end if
|
||
|
|
||
|
if runResult <> ""
|
||
|
if InStr(0, runResult, m.SKIP_TEST_MESSAGE_PREFIX) = 1
|
||
|
testStatObj.result = "Skipped"
|
||
|
testStatObj.message = runResult.Mid(Len(m.SKIP_TEST_MESSAGE_PREFIX)) ' remove prefix from the message
|
||
|
else
|
||
|
testStatObj.Result = "Fail"
|
||
|
testStatObj.Error.Code = 1
|
||
|
testStatObj.Error.Message = runResult
|
||
|
end if
|
||
|
else
|
||
|
testStatObj.Result = "Success"
|
||
|
end if
|
||
|
|
||
|
testStatObj.Time = testTimer.TotalMilliseconds()
|
||
|
m.Logger.AppendTestStatistic(suiteStatObj, testStatObj)
|
||
|
|
||
|
if testStatObj.Result = "Fail" and m.failFast
|
||
|
suiteStatObj.Result = "Fail"
|
||
|
exit for
|
||
|
end if
|
||
|
end if
|
||
|
end for
|
||
|
|
||
|
m.Logger.AppendSuiteStatistic(totalStatObj, suiteStatObj)
|
||
|
|
||
|
if TF_Utils__IsFunction(testSuite.TearDown)
|
||
|
m.Logger.PrintSuiteTearDown(testSuite.Name)
|
||
|
testSuite.TearDown()
|
||
|
end if
|
||
|
|
||
|
if suiteStatObj.Result = "Fail" and m.failFast
|
||
|
exit for
|
||
|
end if
|
||
|
end for
|
||
|
|
||
|
gthis = GetGlobalAA()
|
||
|
msg = ""
|
||
|
if gthis.notFoundFunctionPointerList <> invalid then
|
||
|
msg = Chr(10) + string(40, "---") + Chr(10)
|
||
|
if m.isNodeMode
|
||
|
fileNamesString = ""
|
||
|
|
||
|
for each testSuiteObject in testSuiteNamesList
|
||
|
if GetInterface(testSuiteObject, "ifString") <> invalid then
|
||
|
fileNamesString += testSuiteObject + ".brs, "
|
||
|
else if GetInterface(testSuiteObject, "ifAssociativeArray") <> invalid then
|
||
|
if testSuiteObject.filePath <> invalid then
|
||
|
fileNamesString += testSuiteObject.filePath + ", "
|
||
|
end if
|
||
|
end if
|
||
|
end for
|
||
|
|
||
|
msg += Chr(10) + "Create this function below in one of these files"
|
||
|
msg += Chr(10) + fileNamesString + Chr(10)
|
||
|
|
||
|
msg += Chr(10) + "sub init()"
|
||
|
end if
|
||
|
msg += Chr(10) + "Runner.SetFunctions([" + Chr(10) + " testCase" + Chr(10) + "])"
|
||
|
msg += Chr(10) + "For example we think this might resolve your issue"
|
||
|
msg += Chr(10) + "Runner = TestRunner()"
|
||
|
msg += Chr(10) + "Runner.SetFunctions(["
|
||
|
|
||
|
tmpMap = {}
|
||
|
for each functionName in gthis.notFoundFunctionPointerList
|
||
|
if tmpMap[functionName] = invalid then
|
||
|
tmpMap[functionName] = ""
|
||
|
msg += Chr(10) + " " + functionName
|
||
|
end if
|
||
|
end for
|
||
|
|
||
|
msg += Chr(10) + "])"
|
||
|
if m.isNodeMode then
|
||
|
msg += Chr(10) + "end sub"
|
||
|
else
|
||
|
msg += Chr(10) + "Runner.Run()"
|
||
|
end if
|
||
|
end if
|
||
|
|
||
|
if m.isNodeMode
|
||
|
if msg.Len() > 0 then
|
||
|
if totalStatObj.notFoundFunctionsMessage = invalid then totalStatObj.notFoundFunctionsMessage = ""
|
||
|
totalStatObj.notFoundFunctionsMessage += msg
|
||
|
end if
|
||
|
return totalStatObj
|
||
|
else
|
||
|
testNodes = m.getTestNodesList()
|
||
|
for each testNodeName in testNodes
|
||
|
testNode = CreateObject("roSGNode", testNodeName)
|
||
|
if testNode <> invalid
|
||
|
testSuiteNamesList = m.GetTestSuiteNamesList(testNodeName)
|
||
|
if CreateObject("roSGScreen").CreateScene(testNodeName) <> invalid
|
||
|
? "WARNING: Test cases cannot be run in main scene."
|
||
|
for each testSuiteName in testSuiteNamesList
|
||
|
suiteStatObj = m.Logger.CreateSuiteStatistic(testSuiteName)
|
||
|
suiteStatObj.fail = 1
|
||
|
suiteStatObj.total = 1
|
||
|
m.Logger.AppendSuiteStatistic(totalStatObj, suiteStatObj)
|
||
|
end for
|
||
|
else
|
||
|
params = [m, totalStatObj, testSuiteNamesList, m.GetIncludeFilter(), m.GetExcludeFilter()]
|
||
|
tmp = testNode.callFunc("TestFramework__RunNodeTests", params)
|
||
|
if tmp <> invalid then
|
||
|
totalStatObj = tmp
|
||
|
end if
|
||
|
end if
|
||
|
end if
|
||
|
end for
|
||
|
|
||
|
m.Logger.PrintStatistic(totalStatObj)
|
||
|
end if
|
||
|
|
||
|
if msg.Len() > 0 or totalStatObj.notFoundFunctionsMessage <> invalid then
|
||
|
title = ""
|
||
|
title += Chr(10) + "NOTE: If some your tests haven't been executed this might be due to outdated list of functions"
|
||
|
title += Chr(10) + "To resolve this issue please execute" + Chr(10) + Chr(10)
|
||
|
|
||
|
title += msg
|
||
|
|
||
|
if totalStatObj.notFoundFunctionsMessage <> invalid then
|
||
|
title += totalStatObj.notFoundFunctionsMessage
|
||
|
end if
|
||
|
? title
|
||
|
end if
|
||
|
end function
|
||
|
|
||
|
' ----------------------------------------------------------------
|
||
|
' Set testsDirectory property.
|
||
|
' ----------------------------------------------------------------
|
||
|
sub TestRunner__SetTestsDirectory(testsDirectory as string)
|
||
|
m.testsDirectory = testsDirectory
|
||
|
end sub
|
||
|
|
||
|
' ----------------------------------------------------------------
|
||
|
' Set testFilePrefix property.
|
||
|
' ----------------------------------------------------------------
|
||
|
sub TestRunner__SetTestFilePrefix(testFilePrefix as string)
|
||
|
m.testFilePrefix = testFilePrefix
|
||
|
end sub
|
||
|
|
||
|
' ----------------------------------------------------------------
|
||
|
' Set testSuitePrefix property.
|
||
|
' ----------------------------------------------------------------
|
||
|
sub TestRunner__SetTestSuitePrefix(testSuitePrefix as string)
|
||
|
m.testSuitePrefix = testSuitePrefix
|
||
|
end sub
|
||
|
|
||
|
' ----------------------------------------------------------------
|
||
|
' Set testSuiteName property.
|
||
|
' ----------------------------------------------------------------
|
||
|
sub TestRunner__SetTestSuiteName(testSuiteName as string)
|
||
|
m.testSuiteName = testSuiteName
|
||
|
end sub
|
||
|
|
||
|
' ----------------------------------------------------------------
|
||
|
' Set testCaseName property.
|
||
|
' ----------------------------------------------------------------
|
||
|
sub TestRunner__SetTestCaseName(testCaseName as string)
|
||
|
m.testCaseName = testCaseName
|
||
|
end sub
|
||
|
|
||
|
' ----------------------------------------------------------------
|
||
|
' Set failFast property.
|
||
|
' ----------------------------------------------------------------
|
||
|
sub TestRunner__SetFailFast(failFast = false as boolean)
|
||
|
m.failFast = failFast
|
||
|
end sub
|
||
|
|
||
|
' ----------------------------------------------------------------
|
||
|
' Builds an array of test suite objects.
|
||
|
|
||
|
' @param testSuiteNamesList (string, optional) array of names of test suite functions. If not passed, scans all test files for test suites
|
||
|
|
||
|
' @return An array of test suites.
|
||
|
' ----------------------------------------------------------------
|
||
|
function TestRunner__GetTestSuitesList(testSuiteNamesList = [] as object) as object
|
||
|
result = []
|
||
|
|
||
|
if testSuiteNamesList.count() > 0
|
||
|
for each value in testSuiteNamesList
|
||
|
if TF_Utils__IsString(value) then
|
||
|
tmpTestSuiteFunction = TestFramework__getFunctionPointer(value)
|
||
|
if tmpTestSuiteFunction <> invalid then
|
||
|
testSuite = tmpTestSuiteFunction()
|
||
|
|
||
|
if TF_Utils__IsAssociativeArray(testSuite)
|
||
|
result.Push(testSuite)
|
||
|
end if
|
||
|
end if
|
||
|
' also we can get AA that will give source code and filePath
|
||
|
' Please be aware this is executed in render thread
|
||
|
else if GetInterface(value, "ifAssociativeArray") <> invalid then
|
||
|
' try to use new approach
|
||
|
testSuite = ScanFileForNewTests(value.code, value.filePath)
|
||
|
if testSuite <> invalid then
|
||
|
result.push(testSuite)
|
||
|
end if
|
||
|
else if GetInterface(value, "ifFunction") <> invalid then
|
||
|
result.Push(value)
|
||
|
end if
|
||
|
end for
|
||
|
else
|
||
|
testSuiteRegex = CreateObject("roRegex", "^(function|sub)\s(" + m.testSuitePrefix + m.testSuiteName + "[0-9a-z\_]*)\s*\(", "i")
|
||
|
testFilesList = m.GetTestFilesList()
|
||
|
|
||
|
for each filePath in testFilesList
|
||
|
code = TF_Utils__AsString(ReadAsciiFile(filePath))
|
||
|
|
||
|
if code <> ""
|
||
|
foundTestSuite = false
|
||
|
for each line in code.Tokenize(Chr(10))
|
||
|
line.Trim()
|
||
|
|
||
|
if testSuiteRegex.IsMatch(line)
|
||
|
testSuite = invalid
|
||
|
functionName = testSuiteRegex.Match(line).Peek()
|
||
|
|
||
|
tmpTestSuiteFunction = TestFramework__getFunctionPointer(functionName)
|
||
|
if tmpTestSuiteFunction <> invalid then
|
||
|
testSuite = tmpTestSuiteFunction()
|
||
|
|
||
|
if TF_Utils__IsAssociativeArray(testSuite)
|
||
|
result.Push(testSuite)
|
||
|
foundTestSuite = true
|
||
|
else
|
||
|
' TODO check if we need this
|
||
|
' using new mode
|
||
|
' testSuite = ScanFileForNewTests(code, filePath)
|
||
|
|
||
|
' exit for
|
||
|
end if
|
||
|
end if
|
||
|
end if
|
||
|
end for
|
||
|
if not foundTestSuite then
|
||
|
testSuite = ScanFileForNewTests(code, filePath)
|
||
|
if testSuite <> invalid then
|
||
|
result.push(testSuite)
|
||
|
end if
|
||
|
end if
|
||
|
end if
|
||
|
end for
|
||
|
end if
|
||
|
|
||
|
return result
|
||
|
end function
|
||
|
|
||
|
function ScanFileForNewTests(souceCode, filePath)
|
||
|
foundAnyTest = false
|
||
|
testSuite = BaseTestSuite()
|
||
|
|
||
|
allowedAnnotationsRegex = CreateObject("roRegex", "^'\s*@(test|beforeall|beforeeach|afterall|aftereach|repeatedtest|parameterizedtest|methodsource|ignore)\s*|\n", "i")
|
||
|
voidFunctionRegex = CreateObject("roRegex", "^(function|sub)\s([a-z0-9A-Z_]*)\(\)", "i")
|
||
|
anyArgsFunctionRegex = CreateObject("roRegex", "^(function|sub)\s([a-z0-9A-Z_]*)\(", "i")
|
||
|
|
||
|
processors = {
|
||
|
testSuite: testSuite
|
||
|
filePath: filePath
|
||
|
currentLine: ""
|
||
|
annotations: {}
|
||
|
|
||
|
functionName: ""
|
||
|
|
||
|
tests: []
|
||
|
|
||
|
beforeEachFunc: invalid
|
||
|
beforeAllFunc: invalid
|
||
|
|
||
|
AfterEachFunc: invalid
|
||
|
AfterAllFunc: invalid
|
||
|
|
||
|
isParameterizedTest: false
|
||
|
MethodForArguments: ""
|
||
|
executedParametrizedAdding: false
|
||
|
|
||
|
test: sub()
|
||
|
skipTest = m.doSkipTest(m.functionName)
|
||
|
funcPointer = m.getFunctionPointer(m.functionName)
|
||
|
m.tests.push({ name: m.functionName, pointer: funcPointer, skip: skipTest })
|
||
|
end sub
|
||
|
|
||
|
repeatedtest: sub()
|
||
|
allowedAnnotationsRegex = CreateObject("roRegex", "^'\s*@(repeatedtest)\((\d*)\)", "i")
|
||
|
annotationLine = m.annotations["repeatedtest"].line
|
||
|
if allowedAnnotationsRegex.IsMatch(annotationLine)
|
||
|
groups = allowedAnnotationsRegex.Match(annotationLine)
|
||
|
numberOfLoops = groups[2]
|
||
|
if numberOfLoops <> invalid and TF_Utils__AsInteger(numberOfLoops) > 0 then
|
||
|
numberOfLoops = TF_Utils__AsInteger(numberOfLoops)
|
||
|
funcPointer = m.getFunctionPointer(m.functionName)
|
||
|
for index = 1 to numberOfLoops
|
||
|
skipTest = m.doSkipTest(m.functionName)
|
||
|
text = " " + index.tostr() + " of " + numberOfLoops.tostr()
|
||
|
m.tests.push({ name: m.functionName + text, pointer: funcPointer, skip: skipTest })
|
||
|
end for
|
||
|
end if
|
||
|
else
|
||
|
? "WARNING: Wrong format of repeatedTest(numberOfRuns) "annotationLine
|
||
|
end if
|
||
|
end sub
|
||
|
|
||
|
parameterizedTest: sub()
|
||
|
m.processParameterizedTests()
|
||
|
end sub
|
||
|
|
||
|
methodSource: sub()
|
||
|
m.processParameterizedTests()
|
||
|
end sub
|
||
|
|
||
|
processParameterizedTests: sub()
|
||
|
' add test if it was not added already
|
||
|
if not m.executedParametrizedAdding
|
||
|
if m.annotations.methodSource <> invalid and m.annotations.parameterizedTest <> invalid then
|
||
|
methodAnottation = m.annotations.methodSource.line
|
||
|
|
||
|
allowedAnnotationsRegex = CreateObject("roRegex", "^'\s*@(methodsource)\(" + Chr(34) + "([A-Za-z0-9_]*)" + Chr(34) + "\)", "i")
|
||
|
|
||
|
if allowedAnnotationsRegex.IsMatch(methodAnottation)
|
||
|
groups = allowedAnnotationsRegex.Match(methodAnottation)
|
||
|
providerFunction = groups[2]
|
||
|
|
||
|
providerFunctionPointer = m.getFunctionPointer(providerFunction)
|
||
|
|
||
|
if providerFunctionPointer <> invalid then
|
||
|
funcPointer = m.getFunctionPointer(m.functionName)
|
||
|
|
||
|
args = providerFunctionPointer()
|
||
|
|
||
|
index = 1
|
||
|
for each arg in args
|
||
|
skipTest = m.doSkipTest(m.functionName)
|
||
|
text = " " + index.tostr() + " of " + args.count().tostr()
|
||
|
m.tests.push({ name: m.functionName + text, pointer: funcPointer, arg: arg, hasArgs: true, skip: skipTest })
|
||
|
index++
|
||
|
end for
|
||
|
else
|
||
|
? "WARNING: Cannot find function [" providerFunction "]"
|
||
|
end if
|
||
|
end if
|
||
|
else
|
||
|
? "WARNING: Wrong format of @ParameterizedTest \n @MethodSource(providerFunctionName)"
|
||
|
? "m.executedParametrizedAdding = "m.executedParametrizedAdding
|
||
|
? "m.annotations.methodSource = "m.annotations.methodSource
|
||
|
? "m.annotations.parameterizedTest = "m.annotations.parameterizedTest
|
||
|
? ""
|
||
|
end if
|
||
|
end if
|
||
|
end sub
|
||
|
|
||
|
beforeEach: sub()
|
||
|
m.beforeEachFunc = m.getFunctionPointer(m.functionName)
|
||
|
end sub
|
||
|
|
||
|
beforeAll: sub()
|
||
|
m.beforeAllFunc = m.getFunctionPointer(m.functionName)
|
||
|
end sub
|
||
|
|
||
|
AfterEach: sub()
|
||
|
m.AfterEachFunc = m.getFunctionPointer(m.functionName)
|
||
|
end sub
|
||
|
|
||
|
AfterAll: sub()
|
||
|
m.AfterAllFunc = m.getFunctionPointer(m.functionName)
|
||
|
end sub
|
||
|
|
||
|
ignore: sub()
|
||
|
funcPointer = m.getFunctionPointer(m.functionName)
|
||
|
m.tests.push({ name: m.functionName, pointer: funcPointer, skip: true })
|
||
|
end sub
|
||
|
|
||
|
doSkipTest: function(name as string)
|
||
|
includeFilter = []
|
||
|
excludeFilter = []
|
||
|
|
||
|
gthis = GetGlobalAA()
|
||
|
if gthis.IncludeFilter <> invalid then includeFilter.append(gthis.IncludeFilter)
|
||
|
if gthis.ExcludeFilter <> invalid then excludeFilter.append(gthis.ExcludeFilter)
|
||
|
|
||
|
' apply test filters
|
||
|
skipTest = false
|
||
|
' skip test if it is found in exclude filter
|
||
|
for each testName in excludeFilter
|
||
|
if TF_Utils__IsNotEmptyString(testName) and LCase(testName.Trim()) = LCase(name.Trim())
|
||
|
skipTest = true
|
||
|
exit for
|
||
|
end if
|
||
|
end for
|
||
|
|
||
|
' skip test if it is not found in include filter
|
||
|
if not skipTest and includeFilter.Count() > 0
|
||
|
foundInIncludeFilter = false
|
||
|
|
||
|
for each testName in includeFilter
|
||
|
if TF_Utils__IsNotEmptyString(testName) and LCase(testName) = LCase(name)
|
||
|
foundInIncludeFilter = true
|
||
|
exit for
|
||
|
end if
|
||
|
end for
|
||
|
|
||
|
skipTest = not foundInIncludeFilter
|
||
|
end if
|
||
|
|
||
|
return skipTest
|
||
|
end function
|
||
|
|
||
|
buildTests: sub()
|
||
|
testSuite = m.testSuite
|
||
|
testSuite.Name = m.filePath
|
||
|
if m.beforeAllFunc <> invalid then testSuite.SetUp = m.beforeAllFunc
|
||
|
if m.AfterAllFunc <> invalid then testSuite.TearDown = m.AfterAllFunc
|
||
|
testSuite.IS_NEW_APPROACH = true
|
||
|
|
||
|
for each test in m.tests
|
||
|
' Add tests to suite's tests collection
|
||
|
arg = invalid
|
||
|
hasArgs = false
|
||
|
if test.hasArgs <> invalid then
|
||
|
arg = test.arg
|
||
|
hasArgs = true
|
||
|
end if
|
||
|
|
||
|
testSuite.addTest(test.name, test.pointer, m.beforeEachFunc, m.AfterEachFunc, arg, hasArgs, test.skip)
|
||
|
end for
|
||
|
end sub
|
||
|
|
||
|
getFunctionPointer: TestFramework__getFunctionPointer
|
||
|
}
|
||
|
|
||
|
currentAnottations = []
|
||
|
index = 0
|
||
|
|
||
|
for each line in souceCode.Tokenize(Chr(10))
|
||
|
line = line.Trim()
|
||
|
if line <> "" ' skipping empty lines
|
||
|
if allowedAnnotationsRegex.IsMatch(line)
|
||
|
groups = allowedAnnotationsRegex.Match(line)
|
||
|
anottationType = groups[1]
|
||
|
if anottationType <> invalid and processors[anottationType] <> invalid then
|
||
|
currentAnottations.push(anottationType)
|
||
|
processors.annotations[anottationType] = { line: line, lineIndex: index }
|
||
|
end if
|
||
|
else
|
||
|
if currentAnottations.count() > 0 then
|
||
|
isParametrized = anyArgsFunctionRegex.IsMatch(line)
|
||
|
properMap = { parameterizedtest: "", methodsource: "" }
|
||
|
for each availableAnottation in currentAnottations
|
||
|
isParametrized = isParametrized or properMap[availableAnottation] <> invalid
|
||
|
end for
|
||
|
|
||
|
if voidFunctionRegex.IsMatch(line) or isParametrized then
|
||
|
groups = voidFunctionRegex.Match(line)
|
||
|
|
||
|
if isParametrized then
|
||
|
groups = anyArgsFunctionRegex.Match(line)
|
||
|
end if
|
||
|
if groups[2] <> invalid then
|
||
|
processors.functionName = groups[2]
|
||
|
processors.currentLine = line
|
||
|
|
||
|
' process all handlers
|
||
|
if isParametrized then processors.executedParametrizedAdding = false
|
||
|
for each availableAnottation in currentAnottations
|
||
|
processors[availableAnottation]()
|
||
|
if isParametrized then processors.executedParametrizedAdding = true
|
||
|
end for
|
||
|
currentAnottations = []
|
||
|
processors.annotations = {}
|
||
|
foundAnyTest = true
|
||
|
end if
|
||
|
else
|
||
|
' invalidating annotation
|
||
|
' TODO print message here that we skipped annotation
|
||
|
? "WARNING: annotation " currentAnottations " isparametrized=" isParametrized " skipped at line " index ":[" line "]"
|
||
|
processors.annotations = {}
|
||
|
currentAnottations = []
|
||
|
end if
|
||
|
end if
|
||
|
end if
|
||
|
end if
|
||
|
index++
|
||
|
end for
|
||
|
|
||
|
processors.buildTests()
|
||
|
|
||
|
if not foundAnyTest then
|
||
|
testSuite = invalid
|
||
|
end if
|
||
|
return testSuite
|
||
|
end function
|
||
|
|
||
|
function TestFramework__getFunctionPointer(functionName as string) as dynamic
|
||
|
result = invalid
|
||
|
|
||
|
gthis = GetGlobalAA()
|
||
|
if gthis.FunctionsList <> invalid then
|
||
|
for each value in gthis.FunctionsList
|
||
|
if Type(value) <> "" and LCase(Type(value)) <> "<uninitialized>" and GetInterface(value, "ifFunction") <> invalid and LCase(value.tostr()) = "function: " + LCase(functionName) then
|
||
|
result = value
|
||
|
exit for
|
||
|
end if
|
||
|
end for
|
||
|
end if
|
||
|
|
||
|
if LCase(Type(result)) = "<uninitialized>" then result = invalid
|
||
|
if result = invalid then
|
||
|
if gthis.notFoundFunctionPointerList = invalid then gthis.notFoundFunctionPointerList = []
|
||
|
gthis.notFoundFunctionPointerList.push(functionName)
|
||
|
end if
|
||
|
return result
|
||
|
end function
|
||
|
|
||
|
sub TestRunner__SetFunctions(listOfFunctions as dynamic)
|
||
|
gthis = GetGlobalAA()
|
||
|
|
||
|
if gthis.FunctionsList = invalid then
|
||
|
gthis.FunctionsList = []
|
||
|
end if
|
||
|
gthis.FunctionsList.append(listOfFunctions)
|
||
|
end sub
|
||
|
|
||
|
sub TestRunner__SetIncludeFilter(listOfFunctions as dynamic)
|
||
|
gthis = GetGlobalAA()
|
||
|
|
||
|
if gthis.IncludeFilter = invalid
|
||
|
gthis.IncludeFilter = []
|
||
|
end if
|
||
|
|
||
|
if TF_Utils__IsArray(listOfFunctions)
|
||
|
gthis.IncludeFilter.Append(listOfFunctions)
|
||
|
else if TF_Utils__IsNotEmptyString(listOfFunctions)
|
||
|
gthis.IncludeFilter.Append(listOfFunctions.Split(","))
|
||
|
else
|
||
|
? "WARNING: Could not parse input parameters for Include Filter. Filter wont be applied."
|
||
|
end if
|
||
|
end sub
|
||
|
|
||
|
function TestRunner__GetIncludeFilter()
|
||
|
gthis = GetGlobalAA()
|
||
|
|
||
|
if gthis.IncludeFilter = invalid
|
||
|
gthis.IncludeFilter = []
|
||
|
end if
|
||
|
|
||
|
return gthis.IncludeFilter
|
||
|
end function
|
||
|
|
||
|
sub TestRunner__SetExcludeFilter(listOfFunctions as dynamic)
|
||
|
gthis = GetGlobalAA()
|
||
|
|
||
|
if gthis.ExcludeFilter = invalid
|
||
|
gthis.ExcludeFilter = []
|
||
|
end if
|
||
|
|
||
|
if TF_Utils__IsArray(listOfFunctions)
|
||
|
gthis.ExcludeFilter.Append(listOfFunctions)
|
||
|
else if TF_Utils__IsNotEmptyString(listOfFunctions)
|
||
|
gthis.ExcludeFilter.Append(listOfFunctions.Split(","))
|
||
|
else
|
||
|
? "WARNING: Could not parse input parameters for Exclude Filter. Filter wont be applied."
|
||
|
end if
|
||
|
end sub
|
||
|
|
||
|
function TestRunner__GetExcludeFilter()
|
||
|
gthis = GetGlobalAA()
|
||
|
|
||
|
if gthis.ExcludeFilter = invalid
|
||
|
gthis.ExcludeFilter = []
|
||
|
end if
|
||
|
|
||
|
return gthis.ExcludeFilter
|
||
|
end function
|
||
|
|
||
|
' ----------------------------------------------------------------
|
||
|
' Scans all test files for test suite function names for a given test node.
|
||
|
|
||
|
' @param testNodeName (string) name of a test node, test suites for which are needed
|
||
|
|
||
|
' @return An array of test suite names.
|
||
|
' ----------------------------------------------------------------
|
||
|
function TestRunner__GetTestSuiteNamesList(testNodeName as string) as object
|
||
|
result = []
|
||
|
testSuiteRegex = CreateObject("roRegex", "^(function|sub)\s(" + m.testSuitePrefix + m.testSuiteName + "[0-9a-z\_]*)\s*\(", "i")
|
||
|
testFilesList = m.GetTestFilesList(m.nodesTestDirectory, testNodeName)
|
||
|
|
||
|
for each filePath in testFilesList
|
||
|
code = TF_Utils__AsString(ReadAsciiFile(filePath))
|
||
|
|
||
|
if code <> ""
|
||
|
foundTestSuite = false
|
||
|
for each line in code.Tokenize(Chr(10))
|
||
|
line.Trim()
|
||
|
|
||
|
if testSuiteRegex.IsMatch(line)
|
||
|
functionName = testSuiteRegex.Match(line).Peek()
|
||
|
result.Push(functionName)
|
||
|
foundTestSuite = true
|
||
|
end if
|
||
|
end for
|
||
|
|
||
|
if not foundTestSuite then
|
||
|
' we cannot scan for new tests as we are not in proper scope
|
||
|
' so we need to pass some data so this can be executed in render thread
|
||
|
result.push({ filePath: filePath, code: code })
|
||
|
end if
|
||
|
end if
|
||
|
end for
|
||
|
|
||
|
return result
|
||
|
end function
|
||
|
|
||
|
' ----------------------------------------------------------------
|
||
|
' Scan testsDirectory and all subdirectories for test files.
|
||
|
|
||
|
' @param testsDirectory (string, optional) A target directory with test files.
|
||
|
' @param testFilePrefix (string, optional) prefix, used by test files
|
||
|
|
||
|
' @return An array of test files.
|
||
|
' ----------------------------------------------------------------
|
||
|
function TestRunner__GetTestFilesList(testsDirectory = m.testsDirectory as string, testFilePrefix = m.testFilePrefix as string) as object
|
||
|
result = []
|
||
|
testsFileRegex = CreateObject("roRegex", "^(" + testFilePrefix + ")[0-9a-z\_]*\.brs$", "i")
|
||
|
|
||
|
if testsDirectory <> ""
|
||
|
fileSystem = CreateObject("roFileSystem")
|
||
|
|
||
|
if m.isNodeMode
|
||
|
? string(2, Chr(10))
|
||
|
? string(10, "!!!")
|
||
|
? "Note if you crash here this means that we are in render thread and searching for tests"
|
||
|
? "Problem is that file naming is wrong"
|
||
|
? "check brs file name they should match pattern ""Test_ExactComponentName_anything.brs"""
|
||
|
? "In this case we were looking for "testFilePrefix
|
||
|
? string(10, "!!!") string(2, Chr(10))
|
||
|
end if
|
||
|
listing = fileSystem.GetDirectoryListing(testsDirectory)
|
||
|
|
||
|
for each item in listing
|
||
|
itemPath = testsDirectory + "/" + item
|
||
|
itemStat = fileSystem.Stat(itemPath)
|
||
|
|
||
|
if itemStat.type = "directory" then
|
||
|
result.Append(m.getTestFilesList(itemPath, testFilePrefix))
|
||
|
else if testsFileRegex.IsMatch(item) then
|
||
|
result.Push(itemPath)
|
||
|
end if
|
||
|
end for
|
||
|
end if
|
||
|
|
||
|
return result
|
||
|
end function
|
||
|
|
||
|
' ----------------------------------------------------------------
|
||
|
' Scan nodesTestDirectory and all subdirectories for test nodes.
|
||
|
|
||
|
' @param nodesTestDirectory (string, optional) A target directory with test nodes.
|
||
|
|
||
|
' @return An array of test node names.
|
||
|
' ----------------------------------------------------------------
|
||
|
function TestRunner__GetTestNodesList(testsDirectory = m.nodesTestDirectory as string) as object
|
||
|
result = []
|
||
|
testsFileRegex = CreateObject("roRegex", "^(" + m.testFilePrefix + ")[0-9a-z\_]*\.xml$", "i")
|
||
|
|
||
|
if testsDirectory <> ""
|
||
|
fileSystem = CreateObject("roFileSystem")
|
||
|
listing = fileSystem.GetDirectoryListing(testsDirectory)
|
||
|
|
||
|
for each item in listing
|
||
|
itemPath = testsDirectory + "/" + item
|
||
|
itemStat = fileSystem.Stat(itemPath)
|
||
|
|
||
|
if itemStat.type = "directory" then
|
||
|
result.Append(m.getTestNodesList(itemPath))
|
||
|
else if testsFileRegex.IsMatch(item) then
|
||
|
result.Push(item.replace(".xml", ""))
|
||
|
end if
|
||
|
end for
|
||
|
end if
|
||
|
|
||
|
return result
|
||
|
end function
|
||
|
|
||
|
' ----------------------------------------------------------------
|
||
|
' Creates and runs test runner. Should be used ONLY within a node.
|
||
|
|
||
|
' @param params (array) parameters, passed from main thread, used to setup new test runner
|
||
|
|
||
|
' @return statistic object.
|
||
|
' ----------------------------------------------------------------
|
||
|
function TestFramework__RunNodeTests(params as object) as object
|
||
|
this = params[0]
|
||
|
|
||
|
statObj = params[1]
|
||
|
testSuiteNamesList = params[2]
|
||
|
|
||
|
Runner = TestRunner()
|
||
|
|
||
|
Runner.SetTestSuitePrefix(this.testSuitePrefix)
|
||
|
Runner.SetTestFilePrefix(this.testFilePrefix)
|
||
|
Runner.SetTestSuiteName(this.testSuiteName)
|
||
|
Runner.SetTestCaseName(this.testCaseName)
|
||
|
Runner.SetFailFast(this.failFast)
|
||
|
|
||
|
Runner.SetIncludeFilter(params[3])
|
||
|
Runner.SetExcludeFilter(params[4])
|
||
|
|
||
|
return Runner.Run(statObj, testSuiteNamesList)
|
||
|
end function
|
||
|
function UTF_skip(msg = "")
|
||
|
return UTF_PushErrorMessage(BTS__Skip(msg))
|
||
|
end function
|
||
|
|
||
|
function UTF_fail(msg = "")
|
||
|
return UTF_PushErrorMessage(BTS__Fail(msg))
|
||
|
end function
|
||
|
|
||
|
function UTF_assertFalse(expr, msg = "Expression evaluates to true")
|
||
|
return UTF_PushErrorMessage(BTS__AssertFalse(expr, msg))
|
||
|
end function
|
||
|
|
||
|
function UTF_assertTrue(expr, msg = "Expression evaluates to false")
|
||
|
return UTF_PushErrorMessage(BTS__AssertTrue(expr, msg))
|
||
|
end function
|
||
|
|
||
|
function UTF_assertEqual(first, second, msg = "")
|
||
|
return UTF_PushErrorMessage(BTS__AssertEqual(first, second, msg))
|
||
|
end function
|
||
|
|
||
|
function UTF_assertNotEqual(first, second, msg = "")
|
||
|
return UTF_PushErrorMessage(BTS__AssertNotEqual(first, second, msg))
|
||
|
end function
|
||
|
|
||
|
function UTF_assertInvalid(value, msg = "")
|
||
|
return UTF_PushErrorMessage(BTS__AssertInvalid(value, msg))
|
||
|
end function
|
||
|
|
||
|
function UTF_assertNotInvalid(value, msg = "")
|
||
|
return UTF_PushErrorMessage(BTS__AssertNotInvalid(value, msg))
|
||
|
end function
|
||
|
|
||
|
function UTF_assertAAHasKey(array, key, msg = "")
|
||
|
return UTF_PushErrorMessage(BTS__AssertAAHasKey(array, key, msg))
|
||
|
end function
|
||
|
|
||
|
function UTF_assertAANotHasKey(array, key, msg = "")
|
||
|
return UTF_PushErrorMessage(BTS__AssertAANotHasKey(array, key, msg))
|
||
|
end function
|
||
|
|
||
|
function UTF_assertAAHasKeys(array, keys, msg = "")
|
||
|
return UTF_PushErrorMessage(BTS__AssertAAHasKeys(array, keys, msg))
|
||
|
end function
|
||
|
|
||
|
function UTF_assertAANotHasKeys(array, keys, msg = "")
|
||
|
return UTF_PushErrorMessage(BTS__AssertAANotHasKeys(array, keys, msg))
|
||
|
end function
|
||
|
|
||
|
function UTF_assertArrayContains(array, value, key = invalid, msg = "")
|
||
|
return UTF_PushErrorMessage(BTS__AssertArrayContains(array, value, key, msg))
|
||
|
end function
|
||
|
|
||
|
function UTF_assertArrayNotContains(array, value, key = invalid, msg = "")
|
||
|
return UTF_PushErrorMessage(BTS__AssertArrayNotContains(array, value, key, msg))
|
||
|
end function
|
||
|
|
||
|
function UTF_assertArrayContainsSubset(array, subset, msg = "")
|
||
|
return UTF_PushErrorMessage(BTS__AssertArrayContainsSubset(array, subset, msg))
|
||
|
end function
|
||
|
|
||
|
function UTF_assertArrayNotContainsSubset(array, subset, msg = "")
|
||
|
return UTF_PushErrorMessage(BTS__AssertArrayNotContainsSubset(array, subset, msg))
|
||
|
end function
|
||
|
|
||
|
function UTF_assertArrayCount(array, count, msg = "")
|
||
|
return UTF_PushErrorMessage(BTS__AssertArrayCount(array, count, msg))
|
||
|
end function
|
||
|
|
||
|
function UTF_assertArrayNotCount(array, count, msg = "")
|
||
|
return UTF_PushErrorMessage(BTS__AssertArrayNotCount(array, count, msg))
|
||
|
end function
|
||
|
|
||
|
function UTF_assertEmpty(item, msg = "")
|
||
|
return UTF_PushErrorMessage(BTS__AssertEmpty(item, msg))
|
||
|
end function
|
||
|
|
||
|
function UTF_assertNotEmpty(item, msg = "")
|
||
|
return UTF_PushErrorMessage(BTS__AssertNotEmpty(item, msg))
|
||
|
end function
|
||
|
|
||
|
function UTF_PushErrorMessage(message as string) as boolean
|
||
|
result = Len(message) <= 0
|
||
|
if not result then
|
||
|
m.globalErrorsList.push(message)
|
||
|
end if
|
||
|
|
||
|
return result
|
||
|
end function'*****************************************************************
|
||
|
'* Copyright Roku 2011-2019
|
||
|
'* All Rights Reserved
|
||
|
'*****************************************************************
|
||
|
' Common framework utility functions
|
||
|
' *****************************************************************
|
||
|
|
||
|
' *************************************************
|
||
|
' TF_Utils__IsXmlElement - check if value contains XMLElement interface
|
||
|
' @param value As Dynamic
|
||
|
' @return As Boolean - true if value contains XMLElement interface, else return false
|
||
|
' *************************************************
|
||
|
function TF_Utils__IsXmlElement(value as dynamic) as boolean
|
||
|
return TF_Utils__IsValid(value) and GetInterface(value, "ifXMLElement") <> invalid
|
||
|
end function
|
||
|
|
||
|
' *************************************************
|
||
|
' TF_Utils__IsFunction - check if value contains Function interface
|
||
|
' @param value As Dynamic
|
||
|
' @return As Boolean - true if value contains Function interface, else return false
|
||
|
' *************************************************
|
||
|
function TF_Utils__IsFunction(value as dynamic) as boolean
|
||
|
return TF_Utils__IsValid(value) and GetInterface(value, "ifFunction") <> invalid
|
||
|
end function
|
||
|
|
||
|
' *************************************************
|
||
|
' TF_Utils__IsBoolean - check if value contains Boolean interface
|
||
|
' @param value As Dynamic
|
||
|
' @return As Boolean - true if value contains Boolean interface, else return false
|
||
|
' *************************************************
|
||
|
function TF_Utils__IsBoolean(value as dynamic) as boolean
|
||
|
return TF_Utils__IsValid(value) and GetInterface(value, "ifBoolean") <> invalid
|
||
|
end function
|
||
|
|
||
|
' *************************************************
|
||
|
' TF_Utils__IsInteger - check if value type equals Integer
|
||
|
' @param value As Dynamic
|
||
|
' @return As Boolean - true if value type equals Integer, else return false
|
||
|
' *************************************************
|
||
|
function TF_Utils__IsInteger(value as dynamic) as boolean
|
||
|
return TF_Utils__IsValid(value) and GetInterface(value, "ifInt") <> invalid and (Type(value) = "roInt" or Type(value) = "roInteger" or Type(value) = "Integer")
|
||
|
end function
|
||
|
|
||
|
' *************************************************
|
||
|
' TF_Utils__IsFloat - check if value contains Float interface
|
||
|
' @param value As Dynamic
|
||
|
' @return As Boolean - true if value contains Float interface, else return false
|
||
|
' *************************************************
|
||
|
function TF_Utils__IsFloat(value as dynamic) as boolean
|
||
|
return TF_Utils__IsValid(value) and GetInterface(value, "ifFloat") <> invalid
|
||
|
end function
|
||
|
|
||
|
' *************************************************
|
||
|
' TF_Utils__IsDouble - check if value contains Double interface
|
||
|
' @param value As Dynamic
|
||
|
' @return As Boolean - true if value contains Double interface, else return false
|
||
|
' *************************************************
|
||
|
function TF_Utils__IsDouble(value as dynamic) as boolean
|
||
|
return TF_Utils__IsValid(value) and GetInterface(value, "ifDouble") <> invalid
|
||
|
end function
|
||
|
|
||
|
' *************************************************
|
||
|
' TF_Utils__IsLongInteger - check if value contains LongInteger interface
|
||
|
' @param value As Dynamic
|
||
|
' @return As Boolean - true if value contains LongInteger interface, else return false
|
||
|
' *************************************************
|
||
|
function TF_Utils__IsLongInteger(value as dynamic) as boolean
|
||
|
return TF_Utils__IsValid(value) and GetInterface(value, "ifLongInt") <> invalid
|
||
|
end function
|
||
|
|
||
|
' *************************************************
|
||
|
' TF_Utils__IsNumber - check if value contains LongInteger or Integer or Double or Float interface
|
||
|
' @param value As Dynamic
|
||
|
' @return As Boolean - true if value is number, else return false
|
||
|
' *************************************************
|
||
|
function TF_Utils__IsNumber(value as dynamic) as boolean
|
||
|
return TF_Utils__IsLongInteger(value) or TF_Utils__IsDouble(value) or TF_Utils__IsInteger(value) or TF_Utils__IsFloat(value)
|
||
|
end function
|
||
|
|
||
|
' *************************************************
|
||
|
' TF_Utils__IsList - check if value contains List interface
|
||
|
' @param value As Dynamic
|
||
|
' @return As Boolean - true if value contains List interface, else return false
|
||
|
' *************************************************
|
||
|
function TF_Utils__IsList(value as dynamic) as boolean
|
||
|
return TF_Utils__IsValid(value) and GetInterface(value, "ifList") <> invalid
|
||
|
end function
|
||
|
|
||
|
' *************************************************
|
||
|
' TF_Utils__IsArray - check if value contains Array interface
|
||
|
' @param value As Dynamic
|
||
|
' @return As Boolean - true if value contains Array interface, else return false
|
||
|
' *************************************************
|
||
|
function TF_Utils__IsArray(value as dynamic) as boolean
|
||
|
return TF_Utils__IsValid(value) and GetInterface(value, "ifArray") <> invalid
|
||
|
end function
|
||
|
|
||
|
' *************************************************
|
||
|
' TF_Utils__IsAssociativeArray - check if value contains AssociativeArray interface
|
||
|
' @param value As Dynamic
|
||
|
' @return As Boolean - true if value contains AssociativeArray interface, else return false
|
||
|
' *************************************************
|
||
|
function TF_Utils__IsAssociativeArray(value as dynamic) as boolean
|
||
|
return TF_Utils__IsValid(value) and GetInterface(value, "ifAssociativeArray") <> invalid
|
||
|
end function
|
||
|
|
||
|
' *************************************************
|
||
|
' TF_Utils__IsSGNode - check if value contains SGNodeChildren interface
|
||
|
' @param value As Dynamic
|
||
|
' @return As Boolean - true if value contains SGNodeChildren interface, else return false
|
||
|
' *************************************************
|
||
|
function TF_Utils__IsSGNode(value as dynamic) as boolean
|
||
|
return TF_Utils__IsValid(value) and GetInterface(value, "ifSGNodeChildren") <> invalid
|
||
|
end function
|
||
|
|
||
|
' *************************************************
|
||
|
' TF_Utils__IsString - check if value contains String interface
|
||
|
' @param value As Dynamic
|
||
|
' @return As Boolean - true if value contains String interface, else return false
|
||
|
' *************************************************
|
||
|
function TF_Utils__IsString(value as dynamic) as boolean
|
||
|
return TF_Utils__IsValid(value) and GetInterface(value, "ifString") <> invalid
|
||
|
end function
|
||
|
|
||
|
' *************************************************
|
||
|
' TF_Utils__IsNotEmptyString - check if value contains String interface and length more 0
|
||
|
' @param value As Dynamic
|
||
|
' @return As Boolean - true if value contains String interface and length more 0, else return false
|
||
|
' *************************************************
|
||
|
function TF_Utils__IsNotEmptyString(value as dynamic) as boolean
|
||
|
return TF_Utils__IsString(value) and Len(value) > 0
|
||
|
end function
|
||
|
|
||
|
' *************************************************
|
||
|
' TF_Utils__IsDateTime - check if value contains DateTime interface
|
||
|
' @param value As Dynamic
|
||
|
' @return As Boolean - true if value contains DateTime interface, else return false
|
||
|
' *************************************************
|
||
|
function TF_Utils__IsDateTime(value as dynamic) as boolean
|
||
|
return TF_Utils__IsValid(value) and (GetInterface(value, "ifDateTime") <> invalid or Type(value) = "roDateTime")
|
||
|
end function
|
||
|
|
||
|
' *************************************************
|
||
|
' TF_Utils__IsValid - check if value initialized and not equal invalid
|
||
|
' @param value As Dynamic
|
||
|
' @return As Boolean - true if value initialized and not equal invalid, else return false
|
||
|
' *************************************************
|
||
|
function TF_Utils__IsValid(value as dynamic) as boolean
|
||
|
return Type(value) <> "<uninitialized>" and value <> invalid
|
||
|
end function
|
||
|
|
||
|
' *************************************************
|
||
|
' TF_Utils__ValidStr - return value if his contains String interface else return empty string
|
||
|
' @param value As Object
|
||
|
' @return As String - value if his contains String interface else return empty string
|
||
|
' *************************************************
|
||
|
function TF_Utils__ValidStr(obj as object) as string
|
||
|
if obj <> invalid and GetInterface(obj, "ifString") <> invalid
|
||
|
return obj
|
||
|
else
|
||
|
return ""
|
||
|
end if
|
||
|
end function
|
||
|
|
||
|
' *************************************************
|
||
|
' TF_Utils__AsString - convert input to String if this possible, else return empty string
|
||
|
' @param input As Dynamic
|
||
|
' @return As String - return converted string
|
||
|
' *************************************************
|
||
|
function TF_Utils__AsString(input as dynamic) as string
|
||
|
if TF_Utils__IsValid(input) = false
|
||
|
return ""
|
||
|
else if TF_Utils__IsString(input)
|
||
|
return input
|
||
|
else if TF_Utils__IsInteger(input) or TF_Utils__IsLongInteger(input) or TF_Utils__IsBoolean(input)
|
||
|
return input.ToStr()
|
||
|
else if TF_Utils__IsFloat(input) or TF_Utils__IsDouble(input)
|
||
|
return Str(input).Trim()
|
||
|
else
|
||
|
return ""
|
||
|
end if
|
||
|
end function
|
||
|
|
||
|
' *************************************************
|
||
|
' TF_Utils__AsInteger - convert input to Integer if this possible, else return 0
|
||
|
' @param input As Dynamic
|
||
|
' @return As Integer - return converted Integer
|
||
|
' *************************************************
|
||
|
function TF_Utils__AsInteger(input as dynamic) as integer
|
||
|
if TF_Utils__IsValid(input) = false
|
||
|
return 0
|
||
|
else if TF_Utils__IsString(input)
|
||
|
return input.ToInt()
|
||
|
else if TF_Utils__IsInteger(input)
|
||
|
return input
|
||
|
else if TF_Utils__IsFloat(input) or TF_Utils__IsDouble(input) or TF_Utils__IsLongInteger(input)
|
||
|
return Int(input)
|
||
|
else
|
||
|
return 0
|
||
|
end if
|
||
|
end function
|
||
|
|
||
|
' *************************************************
|
||
|
' TF_Utils__AsLongInteger - convert input to LongInteger if this possible, else return 0
|
||
|
' @param input As Dynamic
|
||
|
' @return As Integer - return converted LongInteger
|
||
|
' *************************************************
|
||
|
function TF_Utils__AsLongInteger(input as dynamic) as longinteger
|
||
|
if TF_Utils__IsValid(input) = false
|
||
|
return 0
|
||
|
else if TF_Utils__IsString(input)
|
||
|
return TF_Utils__AsInteger(input)
|
||
|
else if TF_Utils__IsLongInteger(input) or TF_Utils__IsFloat(input) or TF_Utils__IsDouble(input) or TF_Utils__IsInteger(input)
|
||
|
return input
|
||
|
else
|
||
|
return 0
|
||
|
end if
|
||
|
end function
|
||
|
|
||
|
' *************************************************
|
||
|
' TF_Utils__AsFloat - convert input to Float if this possible, else return 0.0
|
||
|
' @param input As Dynamic
|
||
|
' @return As Float - return converted Float
|
||
|
' *************************************************
|
||
|
function TF_Utils__AsFloat(input as dynamic) as float
|
||
|
if TF_Utils__IsValid(input) = false
|
||
|
return 0.0
|
||
|
else if TF_Utils__IsString(input)
|
||
|
return input.ToFloat()
|
||
|
else if TF_Utils__IsInteger(input)
|
||
|
return (input / 1)
|
||
|
else if TF_Utils__IsFloat(input) or TF_Utils__IsDouble(input) or TF_Utils__IsLongInteger(input)
|
||
|
return input
|
||
|
else
|
||
|
return 0.0
|
||
|
end if
|
||
|
end function
|
||
|
|
||
|
' *************************************************
|
||
|
' TF_Utils__AsDouble - convert input to Double if this possible, else return 0.0
|
||
|
' @param input As Dynamic
|
||
|
' @return As Float - return converted Double
|
||
|
' *************************************************
|
||
|
function TF_Utils__AsDouble(input as dynamic) as double
|
||
|
if TF_Utils__IsValid(input) = false
|
||
|
return 0.0
|
||
|
else if TF_Utils__IsString(input)
|
||
|
return TF_Utils__AsFloat(input)
|
||
|
else if TF_Utils__IsInteger(input) or TF_Utils__IsLongInteger(input) or TF_Utils__IsFloat(input) or TF_Utils__IsDouble(input)
|
||
|
return input
|
||
|
else
|
||
|
return 0.0
|
||
|
end if
|
||
|
end function
|
||
|
|
||
|
' *************************************************
|
||
|
' TF_Utils__AsBoolean - convert input to Boolean if this possible, else return False
|
||
|
' @param input As Dynamic
|
||
|
' @return As Boolean
|
||
|
' *************************************************
|
||
|
function TF_Utils__AsBoolean(input as dynamic) as boolean
|
||
|
if TF_Utils__IsValid(input) = false
|
||
|
return false
|
||
|
else if TF_Utils__IsString(input)
|
||
|
return LCase(input) = "true"
|
||
|
else if TF_Utils__IsInteger(input) or TF_Utils__IsFloat(input)
|
||
|
return input <> 0
|
||
|
else if TF_Utils__IsBoolean(input)
|
||
|
return input
|
||
|
else
|
||
|
return false
|
||
|
end if
|
||
|
end function
|
||
|
|
||
|
' *************************************************
|
||
|
' TF_Utils__AsArray - if type of value equals array return value, else return array with one element [value]
|
||
|
' @param value As Object
|
||
|
' @return As Object - roArray
|
||
|
' *************************************************
|
||
|
function TF_Utils__AsArray(value as object) as object
|
||
|
if TF_Utils__IsValid(value)
|
||
|
if not TF_Utils__IsArray(value)
|
||
|
return [value]
|
||
|
else
|
||
|
return value
|
||
|
end if
|
||
|
end if
|
||
|
return []
|
||
|
end function
|
||
|
|
||
|
' =====================
|
||
|
' Strings
|
||
|
' =====================
|
||
|
|
||
|
' *************************************************
|
||
|
' TF_Utils__IsNullOrEmpty - check if value is invalid or empty
|
||
|
' @param value As Dynamic
|
||
|
' @return As Boolean - true if value is null or empty string, else return false
|
||
|
' *************************************************
|
||
|
function TF_Utils__IsNullOrEmpty(value as dynamic) as boolean
|
||
|
if TF_Utils__IsString(value)
|
||
|
return Len(value) = 0
|
||
|
else
|
||
|
return not TF_Utils__IsValid(value)
|
||
|
end if
|
||
|
end function
|
||
|
|
||
|
' =====================
|
||
|
' Arrays
|
||
|
' =====================
|
||
|
|
||
|
' *************************************************
|
||
|
' TF_Utils__FindElementIndexInArray - find an element index in array
|
||
|
' @param array As Object
|
||
|
' @param value As Object
|
||
|
' @param compareAttribute As Dynamic
|
||
|
' @param caseSensitive As Boolean
|
||
|
' @return As Integer - element index if array contains a value, else return -1
|
||
|
' *************************************************
|
||
|
function TF_Utils__FindElementIndexInArray(array as object, value as object, compareAttribute = invalid as dynamic, caseSensitive = false as boolean) as integer
|
||
|
if TF_Utils__IsArray(array)
|
||
|
for i = 0 to TF_Utils__AsArray(array).Count() - 1
|
||
|
compareValue = array[i]
|
||
|
|
||
|
if compareAttribute <> invalid and TF_Utils__IsAssociativeArray(compareValue) and compareValue.DoesExist(compareAttribute)
|
||
|
compareValue = compareValue.LookupCI(compareAttribute)
|
||
|
end if
|
||
|
|
||
|
if TF_Utils__IsString(compareValue) and TF_Utils__IsString(value) and not caseSensitive
|
||
|
if LCase(compareValue) = LCase(value)
|
||
|
return i
|
||
|
end if
|
||
|
else if TF_Utils__BaseComparator(compareValue, value)
|
||
|
return i
|
||
|
end if
|
||
|
|
||
|
item = array[i]
|
||
|
next
|
||
|
end if
|
||
|
|
||
|
return -1
|
||
|
end function
|
||
|
|
||
|
' *************************************************
|
||
|
' TF_Utils__ArrayContains - check if array contains specified value
|
||
|
' @param array As Object
|
||
|
' @param value As Object
|
||
|
' @param compareAttribute As Dynamic
|
||
|
' @return As Boolean - true if array contains a value, else return false
|
||
|
' *************************************************
|
||
|
function TF_Utils__ArrayContains(array as object, value as object, compareAttribute = invalid as dynamic) as boolean
|
||
|
return (TF_Utils__FindElementIndexInArray(array, value, compareAttribute) > -1)
|
||
|
end function
|
||
|
|
||
|
' ----------------------------------------------------------------
|
||
|
' Type Comparison Functionality
|
||
|
' ----------------------------------------------------------------
|
||
|
|
||
|
' ----------------------------------------------------------------
|
||
|
' Compare two arbitrary values to each other.
|
||
|
|
||
|
' @param Value1 (dynamic) A first item to compare.
|
||
|
' @param Value2 (dynamic) A second item to compare.
|
||
|
' @param comparator (Function, optional) Function, to compare 2 values. Should take in 2 parameters and return either true or false.
|
||
|
|
||
|
' @return True if values are equal or False in other case.
|
||
|
' ----------------------------------------------------------------
|
||
|
function TF_Utils__EqValues(Value1 as dynamic, Value2 as dynamic, comparator = invalid as object) as boolean
|
||
|
if comparator = invalid
|
||
|
return TF_Utils__BaseComparator(value1, value2)
|
||
|
else
|
||
|
return comparator(value1, value2)
|
||
|
end if
|
||
|
end function
|
||
|
|
||
|
' ----------------------------------------------------------------
|
||
|
' Base comparator for comparing two values.
|
||
|
|
||
|
' @param Value1 (dynamic) A first item to compare.
|
||
|
' @param Value2 (dynamic) A second item to compare.
|
||
|
|
||
|
' @return True if values are equal or False in other case.
|
||
|
function TF_Utils__BaseComparator(value1 as dynamic, value2 as dynamic) as boolean
|
||
|
value1Type = Type(value1)
|
||
|
value2Type = Type(value2)
|
||
|
|
||
|
if (value1Type = "roList" or value1Type = "roArray") and (value2Type = "roList" or value2Type = "roArray")
|
||
|
return TF_Utils__EqArray(value1, value2)
|
||
|
else if value1Type = "roAssociativeArray" and value2Type = "roAssociativeArray"
|
||
|
return TF_Utils__EqAssocArray(value1, value2)
|
||
|
else if Type(box(value1), 3) = Type(box(value2), 3)
|
||
|
return value1 = value2
|
||
|
else
|
||
|
return false
|
||
|
end if
|
||
|
end function
|
||
|
|
||
|
' ----------------------------------------------------------------
|
||
|
' Compare two roAssociativeArray objects for equality.
|
||
|
|
||
|
' @param Value1 (object) A first associative array.
|
||
|
' @param Value2 (object) A second associative array.
|
||
|
|
||
|
' @return True if arrays are equal or False in other case.
|
||
|
' ----------------------------------------------------------------
|
||
|
function TF_Utils__EqAssocArray(Value1 as object, Value2 as object) as boolean
|
||
|
l1 = Value1.Count()
|
||
|
l2 = Value2.Count()
|
||
|
|
||
|
if not l1 = l2
|
||
|
return false
|
||
|
else
|
||
|
for each k in Value1
|
||
|
if not Value2.DoesExist(k)
|
||
|
return false
|
||
|
else
|
||
|
v1 = Value1[k]
|
||
|
v2 = Value2[k]
|
||
|
if not TF_Utils__EqValues(v1, v2)
|
||
|
return false
|
||
|
end if
|
||
|
end if
|
||
|
end for
|
||
|
return true
|
||
|
end if
|
||
|
end function
|
||
|
|
||
|
' ----------------------------------------------------------------
|
||
|
' Compare two roArray objects for equality.
|
||
|
|
||
|
' @param Value1 (object) A first array.
|
||
|
' @param Value2 (object) A second array.
|
||
|
|
||
|
' @return True if arrays are equal or False in other case.
|
||
|
' ----------------------------------------------------------------
|
||
|
function TF_Utils__EqArray(Value1 as object, Value2 as object) as boolean
|
||
|
l1 = Value1.Count()
|
||
|
l2 = Value2.Count()
|
||
|
|
||
|
if not l1 = l2
|
||
|
return false
|
||
|
else
|
||
|
for i = 0 to l1 - 1
|
||
|
v1 = Value1[i]
|
||
|
v2 = Value2[i]
|
||
|
if not TF_Utils__EqValues(v1, v2) then
|
||
|
return false
|
||
|
end if
|
||
|
end for
|
||
|
return true
|
||
|
end if
|
||
|
end function
|