function unittest_report() -- Create test report. local report = {} local summary = {} local fails = 0 for _, v in pairs(unittestResults) do if v["success"] then table.insert(summary, ".") else table.insert(summary, "F") fails = fails + 1 table.insert(report, "FAIL: " .. v["title"]) table.insert(report, v["log"]) end end table.insert(report, 1, table.concat(summary)) table.insert(report, "") table.insert(report, "Number of tests run: " .. #unittestResults) table.insert(report, "") if fails > 0 then table.insert(report, "FAILED (failures=" .. fails .. ")") else table.insert(report, "OK") end return table.concat(report, "\n") end function assertEquals(actual, expected, message) -- Asserts that a value equals another value. assert(unittestResults ~= nil, "Orphaned assert equals: " .. message) if type(actual) == "table" and type(expected) == "table" then local lists_match = #actual == #expected if lists_match then for i, v1 in ipairs(actual) do local v2 = expected[i] if type(v1) == "number" and type(v2) == "number" then if math.abs(v1 - v2) > 1e-9 then lists_match = false end elseif v1 ~= v2 then lists_match = false end end end if lists_match then table.insert(unittestResults, {success=true, log="OK", title=message}) return else -- produce the non-matching strings for a human-readable error expected = "{" .. table.concat(expected, ", ") .. "}" actual = "{" .. table.concat(actual, ", ") .. "}" end end if actual == expected or (type(actual) == "number" and type(expected) == "number" and math.abs(actual - expected) < 1e-9) then table.insert(unittestResults, {success=true, log="OK", title=message}) else table.insert(unittestResults, {success=false, log=string.format("Expected: %s\nActual: %s", tostring(expected), tostring(actual)), title=message}) end end function unittest_fail(message) -- Always assert an error. assert(unittestResults ~= nil, "Orphaned assert fail: " .. message) table.insert(unittestResults, {success=false, log="Fail.", title=message}) end -- Describe this function... function test_if() if false then unittest_fail('if false') end ok = false if true then ok = true end assertEquals(ok, true, 'if true') ok = false if false then unittest_fail('if/else false') else ok = true end assertEquals(ok, true, 'if/else false') ok = false if true then ok = true else unittest_fail('if/else true') end assertEquals(ok, true, 'if/else true') ok = false if false then unittest_fail('elseif 1') elseif true then ok = true elseif true then unittest_fail('elseif 2') else unittest_fail('elseif 3') end assertEquals(ok, true, 'elseif 4') end -- Describe this function... function test_ifelse() ok = false if true then ok = true else unittest_fail('ifelse true') end assertEquals(ok, true, 'ifelse true') ok = false if false then unittest_fail('ifelse false') else ok = true end assertEquals(ok, true, 'ifelse false') end -- Describe this function... function test_equalities() assertEquals(2 == 2, true, 'Equal yes') assertEquals(3 == 4, false, 'Equal no') assertEquals(5 ~= 6, true, 'Not equal yes') assertEquals(3 == 4, false, 'Not equal no') assertEquals(5 < 6, true, 'Smaller yes') assertEquals(7 < 7, false, 'Smaller no') assertEquals(9 > 8, true, 'Greater yes') assertEquals(10 > 10, false, 'Greater no') assertEquals(11 <= 11, true, 'Smaller-equal yes') assertEquals(13 <= 12, false, 'Smaller-equal no') assertEquals(14 >= 14, true, 'Greater-equal yes') assertEquals(15 >= 16, false, 'Greater-equal no') end -- Describe this function... function test_and() assertEquals(true and true, true, 'And true/true') assertEquals(false and true, false, 'And false/true') assertEquals(true and false, false, 'And true/false') assertEquals(false and false, false, 'And false/false') end -- Describe this function... function test_or() assertEquals(true or true, true, 'Or true/true') assertEquals(false or true, true, 'Or false/true') assertEquals(true or false, true, 'Or true/false') assertEquals(false or false, false, 'Or false/false') end -- Describe this function... function test_ternary() assertEquals(true and 42 or 99, 42, 'if true') assertEquals(false and 42 or 99, 99, 'if true') end -- Describe this function... function test_foreach() log = '' for _, x in ipairs({'a', 'b', 'c'}) do log = log .. x end assertEquals(log, 'abc', 'for loop') end -- Describe this function... function test_repeat() count = 0 for count2 = 1, 10 do count = count + 1 end assertEquals(count, 10, 'repeat 10') end -- Describe this function... function test_while() while false do unittest_fail('while 0') end while not true do unittest_fail('until 0') end count = 1 while count ~= 10 do count = count + 1 end assertEquals(count, 10, 'while 10') count = 1 while not (count == 10) do count = count + 1 end assertEquals(count, 10, 'until 10') end -- Describe this function... function test_repeat_ext() count = 0 for count3 = 1, 10 do count = count + 1 end assertEquals(count, 10, 'repeat 10') end -- Describe this function... function test_count_by() log = '' for x = 1, 8, 2 do log = log .. x end assertEquals(log, '1357', 'count up ints') log = '' for x = 8, 1, -2 do log = log .. x end assertEquals(log, '8642', 'count down ints') loglist = {} for x = 1, 8, 1.5 do table.insert(loglist, #loglist + 1, x) end assertEquals(loglist, {1, 2.5, 4, 5.5, 7}, 'count with floats') loglist = {} x_inc = math.abs(1 - 2) if (1 + 0) > (8 + 0) then x_inc = -x_inc end for x = 1 + 0, 8 + 0, x_inc do table.insert(loglist, #loglist + 1, x) end assertEquals(loglist, {1, 2, 3, 4, 5, 6, 7, 8}, 'count up non-trivial ints') loglist = {} x_inc2 = 2 if (8 + 0) > (1 + 0) then x_inc2 = -x_inc2 end for x = 8 + 0, 1 + 0, x_inc2 do table.insert(loglist, #loglist + 1, x) end assertEquals(loglist, {8, 6, 4, 2}, 'count down non-trivial ints') loglist = {} x_inc3 = math.abs(1 + 0) if (5 + 0.5) > (1 + 0) then x_inc3 = -x_inc3 end for x = 5 + 0.5, 1 + 0, x_inc3 do table.insert(loglist, #loglist + 1, x) end assertEquals(loglist, {5.5, 4.5, 3.5, 2.5, 1.5}, 'count with floats') end -- Describe this function... function test_count_loops() log = '' for x = 1, 8, 1 do log = log .. x end assertEquals(log, '12345678', 'count up') log = '' for x = 8, 1, -1 do log = log .. x end assertEquals(log, '87654321', 'count down') loglist = {} x_inc4 = 1 if (1 + 0) > (4 + 0) then x_inc4 = -x_inc4 end for x = 1 + 0, 4 + 0, x_inc4 do table.insert(loglist, #loglist + 1, x) end assertEquals(loglist, {1, 2, 3, 4}, 'count up non-trivial') loglist = {} x_inc5 = 1 if (3 + 1) > (1 + 0) then x_inc5 = -x_inc5 end for x = 3 + 1, 1 + 0, x_inc5 do table.insert(loglist, #loglist + 1, x) end assertEquals(loglist, {4, 3, 2, 1}, 'count down non-trivial') end -- Describe this function... function test_continue() log = '' count = 0 while count ~= 8 do count = count + 1 if count == 5 then goto continue end log = log .. count ::continue:: end assertEquals(log, '1234678', 'while continue') log = '' count = 0 while not (count == 8) do count = count + 1 if count == 5 then goto continue end log = log .. count ::continue:: end assertEquals(log, '1234678', 'until continue') log = '' for x = 1, 8, 1 do if x == 5 then goto continue end log = log .. x ::continue:: end assertEquals(log, '1234678', 'count continue') log = '' for _, x in ipairs({'a', 'b', 'c', 'd'}) do if x == 'c' then goto continue end log = log .. x ::continue:: end assertEquals(log, 'abd', 'for continue') end -- Describe this function... function test_break() count = 1 while count ~= 10 do if count == 5 then break end count = count + 1 end assertEquals(count, 5, 'while break') count = 1 while not (count == 10) do if count == 5 then break end count = count + 1 end assertEquals(count, 5, 'until break') log = '' for x = 1, 8, 1 do if x == 5 then break end log = log .. x end assertEquals(log, '1234', 'count break') log = '' for _, x in ipairs({'a', 'b', 'c', 'd'}) do if x == 'c' then break end log = log .. x end assertEquals(log, 'ab', 'for break') end -- Tests the "single" block. function test_single() assertEquals(math.sqrt(25), 5, 'sqrt') assertEquals(math.abs(-25), 25, 'abs') assertEquals(-(-25), 25, 'negate') assertEquals(math.log(1), 0, 'ln') assertEquals(math.log(100, 10), 2, 'log10') assertEquals(math.exp(2), 7.38905609893065, 'exp') assertEquals(10 ^ 2, 100, 'power10') end -- Tests the "arithmetic" block for all operations and checks -- parenthesis are properly generated for different orders. function test_arithmetic() assertEquals(1 + 2, 3, 'add') assertEquals(1 - 2, -1, 'subtract') assertEquals(1 - (0 + 2), -1, 'subtract order with add') assertEquals(1 - (0 - 2), 3, 'subtract order with subtract') assertEquals(4 * 2.5, 10, 'multiply') assertEquals(4 * (0 + 2.5), 10, 'multiply order') assertEquals(8.2 / -5, -1.64, 'divide') assertEquals(8.2 / (0 + -5), -1.64, 'divide order') assertEquals(10 ^ 4, 10000, 'power') assertEquals(10 ^ (0 + 4), 10000, 'power order') end -- Tests the "trig" block. function test_trig() assertEquals(math.sin(math.rad(90)), 1, 'sin') assertEquals(math.cos(math.rad(180)), -1, 'cos') assertEquals(math.tan(math.rad(0)), 0, 'tan') assertEquals(math.deg(math.asin(-1)), -90, 'asin') assertEquals(math.deg(math.acos(1)), 0, 'acos') assertEquals(math.deg(math.atan(1)), 45, 'atan') end -- Tests the "constant" blocks. function test_constant() assertEquals(math.floor(math.pi * 1000), 3141, 'const pi') assertEquals(math.floor(math.exp(1) * 1000), 2718, 'const e') assertEquals(math.floor(((1 + math.sqrt(5)) / 2) * 1000), 1618, 'const golden') assertEquals(math.floor(math.sqrt(2) * 1000), 1414, 'const sqrt 2') assertEquals(math.floor(math.sqrt(1 / 2) * 1000), 707, 'const sqrt 0.5') assertEquals(9999 < math.huge, true, 'const infinity') end function math_isPrime(n) -- https://en.wikipedia.org/wiki/Primality_test#Naive_methods if n == 2 or n == 3 then return true end -- False if n is NaN, negative, is 1, or not whole. -- And false if n is divisible by 2 or 3. if not(n > 1) or n % 1 ~= 0 or n % 2 == 0 or n % 3 == 0 then return false end -- Check all the numbers of form 6k +/- 1, up to sqrt(n). for x = 6, math.sqrt(n) + 1.5, 6 do if n % (x - 1) == 0 or n % (x + 1) == 0 then return false end end return true end -- Tests the "number property" blocks. function test_number_properties() assertEquals(42 % 2 == 0, true, 'even') assertEquals(42.1 % 2 == 1, false, 'odd') assertEquals(math_isPrime(5), true, 'prime 5') assertEquals(math_isPrime(25), false, 'prime 25') assertEquals(math_isPrime(-31.1), false, 'prime negative') assertEquals(math.pi % 1 == 0, false, 'whole') assertEquals(math.huge > 0, true, 'positive') assertEquals(-42 < 0, true, 'negative') assertEquals(42 % 2 == 0, true, 'divisible') end -- Tests the "round" block. function test_round() assertEquals(math.floor(42.42 + .5), 42, 'round') assertEquals(math.ceil(-42.42), -42, 'round up') assertEquals(math.floor(42.42), 42, 'round down') end -- Tests the "change" block. function test_change() varToChange = 100 varToChange = varToChange + 42 assertEquals(varToChange, 142, 'change') end function math_sum(t) local result = 0 for _, v in ipairs(t) do result = result + v end return result end function math_min(t) if #t == 0 then return 0 end local result = math.huge for _, v in ipairs(t) do if v < result then result = v end end return result end function math_max(t) if #t == 0 then return 0 end local result = -math.huge for _, v in ipairs(t) do if v > result then result = v end end return result end function math_average(t) if #t == 0 then return 0 end return math_sum(t) / #t end function math_median(t) -- Source: http://lua-users.org/wiki/SimpleStats if #t == 0 then return 0 end local temp={} for _, v in ipairs(t) do if type(v) == "number" then table.insert(temp, v) end end table.sort(temp) if #temp % 2 == 0 then return (temp[#temp/2] + temp[(#temp/2)+1]) / 2 else return temp[math.ceil(#temp/2)] end end function math_modes(t) -- Source: http://lua-users.org/wiki/SimpleStats local counts={} for _, v in ipairs(t) do if counts[v] == nil then counts[v] = 1 else counts[v] = counts[v] + 1 end end local biggestCount = 0 for _, v in pairs(counts) do if v > biggestCount then biggestCount = v end end local temp={} for k, v in pairs(counts) do if v == biggestCount then table.insert(temp, k) end end return temp end function math_standard_deviation(t) local m local vm local total = 0 local count = 0 local result m = #t == 0 and 0 or math_sum(t) / #t for _, v in ipairs(t) do if type(v) == 'number' then vm = v - m total = total + (vm * vm) count = count + 1 end end result = math.sqrt(total / (count-1)) return result end function math_random_list(t) if #t == 0 then return nil end return t[math.random(#t)] end function first_index(t, elem) for k, v in ipairs(t) do if v == elem then return k end end return 0 end -- Tests the "list operation" blocks. function test_operations_on_list() assertEquals(math_sum({3, 4, 5}), 12, 'sum') assertEquals(math_min({3, 4, 5}), 3, 'min') assertEquals(math_max({3, 4, 5}), 5, 'max') assertEquals(math_average({3, 4, 5}), 4, 'average') assertEquals(math_median({3, 4, 5, 1}), 3.5, 'median') assertEquals(math_modes({3, 4, 3}), {3}, 'modes') assertEquals(math_modes({3, 4, 3, 1, 4}), {3, 4}, 'modes multiple') assertEquals(math_standard_deviation({3, 3, 3}), 0, 'standard dev') assertEquals(first_index({3, 4, 5}, math_random_list({3, 4, 5})) > 0, true, 'random') end -- Tests the "mod" block. function test_mod() assertEquals(42 % 5, 2, 'mod') end -- Tests the "constrain" block. function test_constraint() assertEquals(math.min(math.max(100, 0), 42), 42, 'constraint') end -- Tests the "random integer" block. function test_random_integer() rand = math.random(5, 10) assertEquals(rand >= 5 and rand <= 10, true, 'randRange') assertEquals(rand % 1 == 0, true, 'randInteger') end -- Tests the "random fraction" block. function test_random_fraction() rand = math.random() assertEquals(rand >= 0 and rand <= 1, true, 'randFloat') end -- Describe this function... function test_atan2() assertEquals(math.deg(math.atan2(5, -5)), 135, 'atan2') assertEquals(math.deg(math.atan2(-12, 0)), -90, 'atan2') end -- Checks that the number of calls is one in order -- to confirm that a function was only called once. function check_number_of_calls(test_name) test_name = test_name .. 'number of calls' assertEquals(number_of_calls, 1, test_name) end -- Tests the "create text with" block with varying number of inputs. function test_create_text() assertEquals('', '', 'no text') assertEquals(tostring('Hello'), 'Hello', 'create single') assertEquals(tostring(-1), '-1', 'create single number') assertEquals('K' .. 9, 'K9', 'create double text') assertEquals(4 .. 2, '42', 'create double text numbers') assertEquals(table.concat({1, 2, 3}), '123', 'create triple') assertEquals(table.concat({1, true and 0 or nil, 'M'}), '10M', 'create order') end -- Creates an empty string for use with the empty test. function get_empty() return '' end -- Tests the "is empty" block". function test_empty_text() assertEquals(#'Google' == 0, false, 'not empty') assertEquals(#'' == 0, true, 'empty') assertEquals(#get_empty() == 0, true, 'empty complex') assertEquals(#(true and '' or nil) == 0, true, 'empty order') end -- Tests the "length" block. function test_text_length() assertEquals(#'', 0, 'zero length') assertEquals(#'Google', 6, 'non-zero length') assertEquals(#(true and 'car' or nil), 3, 'length order') end -- Tests the "append text" block with different types of parameters. function test_append() item = 'Miserable' item = item .. 'Failure' assertEquals(item, 'MiserableFailure', 'append text') item = 12 item = item .. 34 assertEquals(item, '1234', 'append number') item = 'Something ' item = item .. (true and 'Positive' or nil) assertEquals(item, 'Something Positive', 'append order') end function firstIndexOf(str, substr) local i = string.find(str, substr, 1, true) if i == nil then return 0 else return i end end function lastIndexOf(str, substr) local i = string.find(string.reverse(str), string.reverse(substr), 1, true) if i then return #str + 2 - i - #substr end return 0 end -- Tests the "find" block with a variable. function test_find_text_simple() text = 'Banana' assertEquals(firstIndexOf(text, 'an'), 2, 'find first simple') assertEquals(lastIndexOf(text, 'an'), 4, 'find last simple') assertEquals(firstIndexOf(text, 'Peel'), 0, 'find none simple') end -- Creates a string for use with the find test. function get_fruit() number_of_calls = number_of_calls + 1 return 'Banana' end -- Tests the "find" block with a function call. function test_find_text_complex() number_of_calls = 0 assertEquals(firstIndexOf(get_fruit(), 'an'), 2, 'find first complex') check_number_of_calls('find first complex') number_of_calls = 0 assertEquals(firstIndexOf(true and get_fruit() or nil, 'an'), 2, 'find first order complex') check_number_of_calls('find first order complex') number_of_calls = 0 assertEquals(lastIndexOf(get_fruit(), 'an'), 4, 'find last complex') check_number_of_calls('find last complex') number_of_calls = 0 assertEquals(lastIndexOf(true and get_fruit() or nil, 'an'), 4, 'find last order complex') check_number_of_calls('find last order complex') number_of_calls = 0 assertEquals(firstIndexOf(get_fruit(), 'Peel'), 0, 'find none complex') check_number_of_calls('find none complex') number_of_calls = 0 assertEquals(firstIndexOf(true and get_fruit() or nil, 'Peel'), 0, 'find none order complex') check_number_of_calls('find none order complex') end function text_random_letter(str) local index = math.random(string.len(str)) return string.sub(str, index, index) end function text_char_at(str, index) return string.sub(str, index, index) end -- Tests the "get letter" block with a variable. function test_get_text_simple() text = 'Blockly' assertEquals(string.sub(text, 1, 1), 'B', 'get first simple') assertEquals(string.sub(text, -1, -1), 'y', 'get last simple') assertEquals(firstIndexOf(text, text_random_letter(text)) > 0, true, 'get random simple') assertEquals(string.sub(text, 3, 3), 'o', 'get # simple') assertEquals(text_char_at(text, true and 3 or nil), 'o', 'get # order simple') assertEquals(string.sub(text, -3, -3), 'k', 'get #-end simple') -- The order for index for #-end is addition because this will catch errors in generators where most perform the operation ... - index. assertEquals(text_char_at(text, -(0 + 3)), 'k', 'get #-end order simple') end -- Creates a string for use with the get test. function get_Blockly() number_of_calls = number_of_calls + 1 return 'Blockly' end -- Tests the "get letter" block with a function call. function test_get_text_complex() text = 'Blockly' number_of_calls = 0 assertEquals(string.sub(get_Blockly(), 1, 1), 'B', 'get first complex') check_number_of_calls('get first complex') number_of_calls = 0 assertEquals(string.sub(true and get_Blockly() or nil, 1, 1), 'B', 'get first order complex') check_number_of_calls('get first order complex') number_of_calls = 0 assertEquals(string.sub(get_Blockly(), -1, -1), 'y', 'get last complex') check_number_of_calls('get last complex') number_of_calls = 0 assertEquals(string.sub(true and get_Blockly() or nil, -1, -1), 'y', 'get last order complex') check_number_of_calls('get last order complex') number_of_calls = 0 assertEquals(firstIndexOf(text, text_random_letter(get_Blockly())) > 0, true, 'get random complex') check_number_of_calls('get random complex') number_of_calls = 0 assertEquals(firstIndexOf(text, text_random_letter(true and get_Blockly() or nil)) > 0, true, 'get random order complex') check_number_of_calls('get random order complex') number_of_calls = 0 assertEquals(string.sub(get_Blockly(), 3, 3), 'o', 'get # complex') check_number_of_calls('get # complex') number_of_calls = 0 assertEquals(text_char_at(true and get_Blockly() or nil, true and 3 or nil), 'o', 'get # order complex') check_number_of_calls('get # order complex') number_of_calls = 0 assertEquals(string.sub(get_Blockly(), -3, -3), 'k', 'get #-end complex') check_number_of_calls('get #-end complex') number_of_calls = 0 -- The order for index for #-end is addition because this will catch errors in generators where most perform the operation ... - index. assertEquals(text_char_at(true and get_Blockly() or nil, -(0 + 3)), 'k', 'get #-end order complex') check_number_of_calls('get #-end order complex') end -- Creates a string for use with the substring test. function get_numbers() number_of_calls = number_of_calls + 1 return '123456789' end -- Tests the "get substring" block with a variable. function test_substring_simple() text = '123456789' assertEquals(string.sub(text, 2, 3), '23', 'substring # simple') assertEquals(string.sub(text, true and 2 or nil, true and 3 or nil), '23', 'substring # simple order') assertEquals(string.sub(text, -3, -2), '78', 'substring #-end simple') -- The order for index for #-end is addition because this will catch errors in generators where most perform the operation ... - index. assertEquals(string.sub(text, -(0 + 3), -(0 + 2)), '78', 'substring #-end simple order') assertEquals(string.sub(text, 1, -1), text, 'substring first-last simple') assertEquals(string.sub(text, 2, -2), '2345678', 'substring # #-end simple') assertEquals(string.sub(text, -7, 4), '34', 'substring #-end # simple') assertEquals(string.sub(text, 1, 4), '1234', 'substring first # simple') assertEquals(string.sub(text, 1, -2), '12345678', 'substring first #-end simple') assertEquals(string.sub(text, 7, -1), '789', 'substring # last simple') assertEquals(string.sub(text, -3, -1), '789', 'substring #-end last simple') assertEquals(string.sub(text, 1, -1), '123456789', 'substring all with # #-end simple') assertEquals(string.sub(text, -9, 9), '123456789', 'substring all with #-end # simple') -- Checks that the whole string is properly retrieved even if the value for start and end is not a simple number. This is especially important in generators where substring uses [x:length - y] for # #-end. assertEquals(string.sub(text, 0 + 1, -(0 + 1)), '123456789', 'substring all with # #-end math simple') end -- Tests the "get substring" block with a function call. function test_substring_complex() number_of_calls = 0 assertEquals(string.sub(get_numbers(), 2, 3), '23', 'substring # complex') check_number_of_calls('substring # complex') number_of_calls = 0 assertEquals(string.sub(true and get_numbers() or nil, true and 2 or nil, true and 3 or nil), '23', 'substring # complex order') check_number_of_calls('substring # complex order') number_of_calls = 0 -- The order for index for #-end is addition because this will catch errors in generators where most perform the operation ... - index. assertEquals(string.sub(get_numbers(), -3, -2), '78', 'substring #-end complex') check_number_of_calls('substring #-end complex') number_of_calls = 0 assertEquals(string.sub(true and get_numbers() or nil, -(0 + 3), -(0 + 2)), '78', 'substring #-end order order') check_number_of_calls('substring #-end order order') number_of_calls = 0 assertEquals(string.sub(get_numbers(), 1, -1), text, 'substring first-last') check_number_of_calls('substring first-last') number_of_calls = 0 assertEquals(string.sub(get_numbers(), 2, -2), '2345678', 'substring # #-end complex') check_number_of_calls('substring # #-end complex') number_of_calls = 0 assertEquals(string.sub(get_numbers(), -7, 4), '34', 'substring #-end # complex') check_number_of_calls('substring #-end # complex') number_of_calls = 0 assertEquals(string.sub(get_numbers(), 1, 4), '1234', 'substring first # complex') check_number_of_calls('substring first # complex') number_of_calls = 0 assertEquals(string.sub(get_numbers(), 1, -2), '12345678', 'substring first #-end complex') check_number_of_calls('substring first #-end complex') number_of_calls = 0 assertEquals(string.sub(get_numbers(), 7, -1), '789', 'substring # last complex') check_number_of_calls('substring # last complex') number_of_calls = 0 assertEquals(string.sub(get_numbers(), -3, -1), '789', 'substring #-end last complex') check_number_of_calls('substring #-end last complex') number_of_calls = 0 assertEquals(string.sub(get_numbers(), 1, -1), '123456789', 'substring all with # #-end complex') check_number_of_calls('substring all with # #-end complex') number_of_calls = 0 assertEquals(string.sub(get_numbers(), -9, 9), '123456789', 'substring all with #-end # complex') check_number_of_calls('substring all with #-end # complex') number_of_calls = 0 -- Checks that the whole string is properly retrieved even if the value for start and end is not a simple number. This is especially important in generators where substring uses [x:length - y] for # #-end. assertEquals(string.sub(get_numbers(), 0 + 1, -(0 + 1)), '123456789', 'substring all with # #-end math complex') check_number_of_calls('substring all with # #-end math complex') end function text_titlecase(str) local buf = {} local inWord = false for i = 1, #str do local c = string.sub(str, i, i) if inWord then table.insert(buf, string.lower(c)) if string.find(c, "%s") then inWord = false end else table.insert(buf, string.upper(c)) inWord = true end end return table.concat(buf) end -- Tests the "change casing" block. function test_case() text = 'Hello World' assertEquals(string.upper(text), 'HELLO WORLD', 'uppercase') assertEquals(string.upper(true and text or nil), 'HELLO WORLD', 'uppercase order') text = 'Hello World' assertEquals(string.lower(text), 'hello world', 'lowercase') assertEquals(string.lower(true and text or nil), 'hello world', 'lowercase order') text = 'heLLo WorlD' assertEquals(text_titlecase(text), 'Hello World', 'titlecase') assertEquals(text_titlecase(true and text or nil), 'Hello World', 'titlecase order') end -- Tests the "trim" block. function test_trim() text = ' abc def ' assertEquals(string.gsub(text, "^%s*(.-)%s*$", "%1"), 'abc def', 'trim both') assertEquals(string.gsub(true and text or nil, "^%s*(.-)%s*$", "%1"), 'abc def', 'trim both order') assertEquals(string.gsub(text, "^%s*(,-)", "%1"), 'abc def ', 'trim left') assertEquals(string.gsub(true and text or nil, "^%s*(,-)", "%1"), 'abc def ', 'trim left order') assertEquals(string.gsub(text, "(.-)%s*$", "%1"), ' abc def', 'trim right') assertEquals(string.gsub(true and text or nil, "(.-)%s*$", "%1"), ' abc def', 'trim right order') end function text_count(haystack, needle) if #needle == 0 then return #haystack + 1 end local i = 1 local count = 0 while true do i = string.find(haystack, needle, i, true) if i == nil then break end count = count + 1 i = i + #needle end return count end -- Tests the "trim" block. function test_count_text() text = 'woolloomooloo' assertEquals(text_count(text, 'o'), 8, 'len 1') assertEquals(text_count(text, 'oo'), 4, 'len 2') assertEquals(text_count(text, 'loo'), 2, 'len 3') assertEquals(text_count(text, 'wool'), 1, 'start') assertEquals(text_count(text, 'chicken'), 0, 'missing') assertEquals(text_count(text, ''), 14, 'empty needle') assertEquals(text_count('', 'chicken'), 0, 'empty source') end -- Tests the "trim" block. function test_text_reverse() assertEquals(string.reverse(''), '', 'empty string') assertEquals(string.reverse('a'), 'a', 'len 1') assertEquals(string.reverse('ab'), 'ba', 'len 2') assertEquals(string.reverse('woolloomooloo'), 'ooloomoolloow', 'longer') end function text_replace(haystack, needle, replacement) local buf = {} local i = 1 while i <= #haystack do if string.sub(haystack, i, i + #needle - 1) == needle then for j = 1, #replacement do table.insert(buf, string.sub(replacement, j, j)) end i = i + #needle else table.insert(buf, string.sub(haystack, i, i)) i = i + 1 end end return table.concat(buf) end -- Tests the "trim" block. function test_replace() assertEquals(text_replace('woolloomooloo', 'oo', '123'), 'w123ll123m123l123', 'replace all instances 1') assertEquals(text_replace('woolloomooloo', '.oo', 'X'), 'woolloomooloo', 'literal string replacement') assertEquals(text_replace('woolloomooloo', 'abc', 'X'), 'woolloomooloo', 'not found') assertEquals(text_replace('woolloomooloo', 'o', ''), 'wllml', 'empty replacement 1') assertEquals(text_replace('aaaaa', 'aaaaa', ''), '', 'empty replacement 2') assertEquals(text_replace('aaaaa', 'a', ''), '', 'empty replacement 3') assertEquals(text_replace('', 'a', 'chicken'), '', 'empty source') end -- Tests the "multiline" block. function test_multiline() assertEquals('', '', 'no text') assertEquals('Google', 'Google', 'simple') assertEquals('paragraph' .. '\n' .. 'with newlines' .. '\n' .. 'yup', 'paragraph' .. '\n' .. 'with newlines' .. '\n' .. 'yup', 'no compile error with newlines') assertEquals(text_count('bark bark' .. '\n' .. 'bark bark bark' .. '\n' .. 'bark bark bark bark', 'bark'), 9, 'count with newlines') end -- Checks that the number of calls is one in order -- to confirm that a function was only called once. function check_number_of_calls2(test_name) test_name = test_name .. 'number of calls' assertEquals(number_of_calls, 1, test_name) end function create_list_repeated(item, count) local t = {} for i = 1, count do table.insert(t, item) end return t end -- Tests the "create list with" and "create empty list" blocks. function test_create_lists() assertEquals({}, {}, 'create empty') assertEquals({true, 'love'}, {true, 'love'}, 'create items') assertEquals(create_list_repeated('Eject', 3), {'Eject', 'Eject', 'Eject'}, 'create repeated') assertEquals(create_list_repeated('Eject', 0 + 3), {'Eject', 'Eject', 'Eject'}, 'create repeated order') end -- Creates an empty list for use with the empty test. function get_empty_list() return {} end -- Tests the "is empty" block. function test_lists_empty() assertEquals(#{0} == 0, false, 'not empty') assertEquals(#{} == 0, true, 'empty') assertEquals(#get_empty_list() == 0, true, 'empty complex') assertEquals(#(true and {} or nil) == 0, true, 'empty order') end -- Tests the "length" block. function test_lists_length() assertEquals(#{}, 0, 'zero length') assertEquals(#{'cat'}, 1, 'one length') assertEquals(#{'cat', true, {}}, 3, 'three length') assertEquals(#(true and {'cat', true} or nil), 2, 'two length order') end function last_index(t, elem) for i = #t, 1, -1 do if t[i] == elem then return i end end return 0 end -- Tests the "find" block with a variable. function test_find_lists_simple() list = {'Alice', 'Eve', 'Bob', 'Eve'} assertEquals(first_index(list, 'Eve'), 2, 'find first simple') assertEquals(last_index(list, 'Eve'), 4, 'find last simple') assertEquals(first_index(list, 'Dave'), 0, 'find none simple') end -- Creates a list for use with the find test. function get_names() number_of_calls = number_of_calls + 1 return {'Alice', 'Eve', 'Bob', 'Eve'} end -- Tests the "find" block with a function call. function test_find_lists_complex() number_of_calls = 0 assertEquals(first_index(get_names(), 'Eve'), 2, 'find first complex') check_number_of_calls('find first complex') number_of_calls = 0 assertEquals(first_index(true and get_names() or nil, 'Eve'), 2, 'find first order complex') check_number_of_calls('find first order complex') number_of_calls = 0 assertEquals(last_index(get_names(), 'Eve'), 4, 'find last complex') check_number_of_calls('find last complex') number_of_calls = 0 assertEquals(last_index(true and get_names() or nil, 'Eve'), 4, 'find last order complex') check_number_of_calls('find last order complex') number_of_calls = 0 assertEquals(first_index(get_names(), 'Dave'), 0, 'find none complex') check_number_of_calls('find none complex') number_of_calls = 0 assertEquals(first_index(true and get_names() or nil, 'Dave'), 0, 'find none order complex') check_number_of_calls('find none order complex') end -- Tests the "get" block with a variable. function test_get_lists_simple() list = {'Kirk', 'Spock', 'McCoy'} assertEquals(list[1], 'Kirk', 'get first simple') assertEquals(list[#list], 'McCoy', 'get last simple') assertEquals(first_index(list, list[math.random(#list)]) > 0, true, 'get random simple') assertEquals(list[2], 'Spock', 'get # simple') assertEquals(list[true and 2 or nil], 'Spock', 'get # order simple') assertEquals(list[#list + 1 - 3], 'Kirk', 'get #-end simple') -- The order for index for #-end is addition because this will catch errors in generators where most perform the operation ... - index. assertEquals(list[#list + 1 - (0 + 3)], 'Kirk', 'get #-end order simple') end function list_get_last(t) return t[#t] end function list_get_random(t) return t[math.random(#t)] end function list_get_from_end(t, at) return t[#t + 1 - at] end -- Tests the "get" block with create list call. function test_get_lists_create_list() assertEquals(({'Kirk', 'Spock', 'McCoy'})[1], 'Kirk', 'get first create list') assertEquals(list_get_last(({'Kirk', 'Spock', 'McCoy'})), 'McCoy', 'get last simple') assertEquals(first_index({'Kirk', 'Spock', 'McCoy'}, list_get_random(({'Kirk', 'Spock', 'McCoy'}))) > 0, true, 'get random simple') assertEquals(({'Kirk', 'Spock', 'McCoy'})[2], 'Spock', 'get # simple') assertEquals(({'Kirk', 'Spock', 'McCoy'})[true and 2 or nil], 'Spock', 'get # order simple') assertEquals(list_get_from_end(({'Kirk', 'Spock', 'McCoy'}), 3), 'Kirk', 'get #-end simple') -- The order for index for #-end is addition because this will catch errors in generators where most perform the operation ... - index. assertEquals(list_get_from_end(({'Kirk', 'Spock', 'McCoy'}), 0 + 3), 'Kirk', 'get #-end order simple') end -- Creates a list for use with the get test. function get_star_wars() number_of_calls = number_of_calls + 1 return {'Kirk', 'Spock', 'McCoy'} end -- Tests the "get" block with a function call. function test_get_lists_complex() list = {'Kirk', 'Spock', 'McCoy'} number_of_calls = 0 assertEquals((get_star_wars())[1], 'Kirk', 'get first complex') check_number_of_calls('get first complex') number_of_calls = 0 assertEquals((true and get_star_wars() or nil)[1], 'Kirk', 'get first order complex') check_number_of_calls('get first order complex') number_of_calls = 0 assertEquals(list_get_last((get_star_wars())), 'McCoy', 'get last complex') check_number_of_calls('get last complex') number_of_calls = 0 assertEquals(list_get_last((true and get_star_wars() or nil)), 'McCoy', 'get last order complex') check_number_of_calls('get last order complex') number_of_calls = 0 assertEquals(first_index(list, list_get_random((get_star_wars()))) > 0, true, 'get random complex') check_number_of_calls('get random complex') number_of_calls = 0 assertEquals(first_index(list, list_get_random((true and get_star_wars() or nil))) > 0, true, 'get random order complex') check_number_of_calls('get random order complex') number_of_calls = 0 assertEquals((get_star_wars())[2], 'Spock', 'get # complex') check_number_of_calls('get # complex') number_of_calls = 0 assertEquals((true and get_star_wars() or nil)[true and 2 or nil], 'Spock', 'get # order complex') check_number_of_calls('get # order complex') number_of_calls = 0 assertEquals(list_get_from_end((get_star_wars()), 3), 'Kirk', 'get #-end complex') check_number_of_calls('get #-end complex') number_of_calls = 0 -- The order for index for #-end is addition because this will catch errors in generators where most perform the operation ... - index. assertEquals(list_get_from_end((true and get_star_wars() or nil), 0 + 3), 'Kirk', 'get #-end order complex') check_number_of_calls('get #-end order complex') end function list_remove_last(t) return table.remove(t, #t) end function list_remove_random(t) return table.remove(t, math.random(#t)) end function list_remove_from_end(t, at) return table.remove(t, #t + 1 - at) end -- Tests the "get and remove" block. function test_getRemove() list = {'Kirk', 'Spock', 'McCoy'} assertEquals(table.remove(list, 1), 'Kirk', 'getremove first') assertEquals(list, {'Spock', 'McCoy'}, 'getremove first list') list = {'Kirk', 'Spock', 'McCoy'} assertEquals(table.remove((true and list or nil), 1), 'Kirk', 'getremove first order') assertEquals(list, {'Spock', 'McCoy'}, 'getremove first order list') list = {'Kirk', 'Spock', 'McCoy'} assertEquals(table.remove(list, #list), 'McCoy', 'getremove last') assertEquals(list, {'Kirk', 'Spock'}, 'getremove last list') list = {'Kirk', 'Spock', 'McCoy'} assertEquals(list_remove_last((true and list or nil)), 'McCoy', 'getremove last order') assertEquals(list, {'Kirk', 'Spock'}, 'getremove last order list') list = {'Kirk', 'Spock', 'McCoy'} assertEquals(first_index(list, table.remove(list, math.random(#list))) == 0, true, 'getremove random') assertEquals(#list, 2, 'getremove random list') list = {'Kirk', 'Spock', 'McCoy'} assertEquals(first_index(list, list_remove_random((true and list or nil))) == 0, true, 'getremove random order') assertEquals(#list, 2, 'getremove random order list') list = {'Kirk', 'Spock', 'McCoy'} assertEquals(table.remove(list, 2), 'Spock', 'getremove #') assertEquals(list, {'Kirk', 'McCoy'}, 'getremove # list') list = {'Kirk', 'Spock', 'McCoy'} assertEquals(table.remove((true and list or nil), true and 2 or nil), 'Spock', 'getremove # order') assertEquals(list, {'Kirk', 'McCoy'}, 'getremove # order list') list = {'Kirk', 'Spock', 'McCoy'} assertEquals(table.remove(list, #list + 1 - 3), 'Kirk', 'getremove #-end') assertEquals(list, {'Spock', 'McCoy'}, 'getremove #-end list') list = {'Kirk', 'Spock', 'McCoy'} -- The order for index for #-end is addition because this will catch errors in generators where most perform the operation ... - index. assertEquals(list_remove_from_end((true and list or nil), 0 + 3), 'Kirk', 'getremove #-end order') assertEquals(list, {'Spock', 'McCoy'}, 'getremove #-end order list') end -- Tests the "remove" block. function test_remove() list = {'Kirk', 'Spock', 'McCoy'} table.remove(list, 1) assertEquals(list, {'Spock', 'McCoy'}, 'remove first list') list = {'Kirk', 'Spock', 'McCoy'} table.remove((true and list or nil), 1) assertEquals(list, {'Spock', 'McCoy'}, 'remove first order list') list = {'Kirk', 'Spock', 'McCoy'} table.remove(list, #list) assertEquals(list, {'Kirk', 'Spock'}, 'remove last list') list = {'Kirk', 'Spock', 'McCoy'} tmp_list = (true and list or nil) table.remove(tmp_list, #tmp_list) assertEquals(list, {'Kirk', 'Spock'}, 'remove last order list') list = {'Kirk', 'Spock', 'McCoy'} table.remove(list, math.random(#list)) assertEquals(#list, 2, 'remove random list') list = {'Kirk', 'Spock', 'McCoy'} tmp_list2 = (true and list or nil) table.remove(tmp_list2, math.random(#tmp_list2)) assertEquals(#list, 2, 'remove random order list') list = {'Kirk', 'Spock', 'McCoy'} table.remove(list, 2) assertEquals(list, {'Kirk', 'McCoy'}, 'remove # list') list = {'Kirk', 'Spock', 'McCoy'} table.remove((true and list or nil), true and 2 or nil) assertEquals(list, {'Kirk', 'McCoy'}, 'remove # order list') list = {'Kirk', 'Spock', 'McCoy'} table.remove(list, #list + 1 - 3) assertEquals(list, {'Spock', 'McCoy'}, 'remove #-end list') list = {'Kirk', 'Spock', 'McCoy'} -- The order for index for #-end is addition because this will catch -- errors in generators where most perform the operation ... - index. tmp_list3 = (true and list or nil) table.remove(tmp_list3, #tmp_list3 + 1 - (0 + 3)) assertEquals(list, {'Spock', 'McCoy'}, 'remove #-end order list') end -- Tests the "set" block. function test_set() list = {'Picard', 'Riker', 'Crusher'} list[1] = 'Jean-Luc' assertEquals(list, {'Jean-Luc', 'Riker', 'Crusher'}, 'set first list') list = {'Picard', 'Riker', 'Crusher'} (true and list or nil)[1] = 'Jean-Luc' assertEquals(list, {'Jean-Luc', 'Riker', 'Crusher'}, 'set first order list') list = {'Picard', 'Riker', 'Crusher'} list[#list] = 'Beverly' assertEquals(list, {'Picard', 'Riker', 'Beverly'}, 'set last list') list = {'Picard', 'Riker', 'Crusher'} tmp_list4 = (true and list or nil) tmp_list4[#tmp_list4] = 'Beverly' assertEquals(list, {'Picard', 'Riker', 'Beverly'}, 'set last order list') list = {'Picard', 'Riker', 'Crusher'} list[math.random(#list)] = 'Data' assertEquals(#list, 3, 'set random list') list = {'Picard', 'Riker', 'Crusher'} tmp_list5 = (true and list or nil) tmp_list5[math.random(#tmp_list5)] = 'Data' assertEquals(#list, 3, 'set random order list') list = {'Picard', 'Riker', 'Crusher'} list[3] = 'Pulaski' assertEquals(list, {'Picard', 'Riker', 'Pulaski'}, 'set # list') list = {'Picard', 'Riker', 'Crusher'} (true and list or nil)[(true and 3 or nil)] = 'Pulaski' assertEquals(list, {'Picard', 'Riker', 'Pulaski'}, 'set # order list') list = {'Picard', 'Riker', 'Crusher'} list[#list + 1 - 1] = 'Pulaski' assertEquals(list, {'Picard', 'Riker', 'Pulaski'}, 'set #-end list') list = {'Picard', 'Riker', 'Crusher'} -- The order for index for #-end is addition because this will catch -- errors in generators where most perform the operation ... - index. tmp_list6 = (true and list or nil) tmp_list6[#tmp_list6 + 1 - (0 + 2)] = 'Pulaski' assertEquals(list, {'Picard', 'Pulaski', 'Crusher'}, 'set #-end order list') end -- Tests the "insert" block. function test_insert() list = {'Picard', 'Riker', 'Crusher'} table.insert(list, 1, 'Data') assertEquals(list, {'Data', 'Picard', 'Riker', 'Crusher'}, 'insert first list') list = {'Picard', 'Riker', 'Crusher'} table.insert((true and list or nil), 1, 'Data') assertEquals(list, {'Data', 'Picard', 'Riker', 'Crusher'}, 'insert first order list') list = {'Picard', 'Riker', 'Crusher'} table.insert(list, #list + 1, 'Data') assertEquals(list, {'Picard', 'Riker', 'Crusher', 'Data'}, 'insert last list') list = {'Picard', 'Riker', 'Crusher'} tmp_list7 = (true and list or nil) table.insert(tmp_list7, #tmp_list7 + 1, 'Data') assertEquals(list, {'Picard', 'Riker', 'Crusher', 'Data'}, 'insert last order list') list = {'Picard', 'Riker', 'Crusher'} table.insert(list, math.random(#list), 'Data') assertEquals(#list, 4, 'insert random list') list = {'Picard', 'Riker', 'Crusher'} tmp_list8 = (true and list or nil) table.insert(tmp_list8, math.random(#tmp_list8), 'Data') assertEquals(#list, 4, 'insert random order list') list = {'Picard', 'Riker', 'Crusher'} table.insert(list, 3, 'Data') assertEquals(list, {'Picard', 'Riker', 'Data', 'Crusher'}, 'insert # list') list = {'Picard', 'Riker', 'Crusher'} table.insert((true and list or nil), (true and 3 or nil), 'Data') assertEquals(list, {'Picard', 'Riker', 'Data', 'Crusher'}, 'insert # order list') list = {'Picard', 'Riker', 'Crusher'} table.insert(list, #list + 1 - 1, 'Data') assertEquals(list, {'Picard', 'Riker', 'Data', 'Crusher'}, 'insert #-end list') list = {'Picard', 'Riker', 'Crusher'} -- The order for index for #-end is addition because this will catch -- errors in generators where most perform the operation ... - index. tmp_list9 = (true and list or nil) table.insert(tmp_list9, #tmp_list9 + 1 - (0 + 2), 'Data') assertEquals(list, {'Picard', 'Data', 'Riker', 'Crusher'}, 'insert #-end order list') end function list_sublist_from_start_from_start(source, at1, at2) local t = {} local start = at1 local finish = at2 for i = start, finish do table.insert(t, source[i]) end return t end function list_sublist_from_end_from_end(source, at1, at2) local t = {} local start = #source + 1 - at1 local finish = #source + 1 - at2 for i = start, finish do table.insert(t, source[i]) end return t end function list_sublist_first_last(source) local t = {} local start = 1 local finish = #source for i = start, finish do table.insert(t, source[i]) end return t end function list_sublist_from_start_from_end(source, at1, at2) local t = {} local start = at1 local finish = #source + 1 - at2 for i = start, finish do table.insert(t, source[i]) end return t end function list_sublist_from_end_from_start(source, at1, at2) local t = {} local start = #source + 1 - at1 local finish = at2 for i = start, finish do table.insert(t, source[i]) end return t end function list_sublist_first_from_start(source, at2) local t = {} local start = 1 local finish = at2 for i = start, finish do table.insert(t, source[i]) end return t end function list_sublist_first_from_end(source, at2) local t = {} local start = 1 local finish = #source + 1 - at2 for i = start, finish do table.insert(t, source[i]) end return t end function list_sublist_from_start_last(source, at1) local t = {} local start = at1 local finish = #source for i = start, finish do table.insert(t, source[i]) end return t end function list_sublist_from_end_last(source, at1) local t = {} local start = #source + 1 - at1 local finish = #source for i = start, finish do table.insert(t, source[i]) end return t end -- Tests the "get sub-list" block with a variable. function test_sublist_simple() list = {'Columbia', 'Challenger', 'Discovery', 'Atlantis', 'Endeavour'} assertEquals(list_sublist_from_start_from_start(list, 2, 3), {'Challenger', 'Discovery'}, 'sublist # simple') assertEquals(list_sublist_from_start_from_start(list, true and 2 or nil, true and 3 or nil), {'Challenger', 'Discovery'}, 'sublist # simple order') assertEquals(list_sublist_from_end_from_end(list, 3, 2), {'Discovery', 'Atlantis'}, 'sublist #-end simple') -- The order for index for #-end is addition because this will catch errors in generators where most perform the operation ... - index. assertEquals(list_sublist_from_end_from_end(list, 0 + 3, 0 + 2), {'Discovery', 'Atlantis'}, 'sublist #-end simple order') assertEquals(list_sublist_first_last(list), list, 'sublist first-last simple') changing_list = {'Columbia', 'Challenger', 'Discovery', 'Atlantis', 'Endeavour'} list_copy = list_sublist_first_last(changing_list) table.remove(changing_list, math.random(#changing_list)) assertEquals(list_copy, list, 'sublist first-last simple copy check') assertEquals(list_sublist_from_start_from_end(list, 2, 2), {'Challenger', 'Discovery', 'Atlantis'}, 'sublist # #-end simple') assertEquals(list_sublist_from_end_from_start(list, 3, 4), {'Discovery', 'Atlantis'}, 'sublist #-end # simple') assertEquals(list_sublist_first_from_start(list, 4), {'Columbia', 'Challenger', 'Discovery', 'Atlantis'}, 'sublist first # simple') assertEquals(list_sublist_first_from_end(list, 4), {'Columbia', 'Challenger'}, 'sublist first #-end simple') assertEquals(list_sublist_from_start_last(list, 4), {'Atlantis', 'Endeavour'}, 'sublist # last simple') assertEquals(list_sublist_from_end_last(list, 4), {'Challenger', 'Discovery', 'Atlantis', 'Endeavour'}, 'sublist #-end last simple') assertEquals(list_sublist_from_start_from_end(list, 1, 1), list, 'sublist all with # #-end simple') assertEquals(list_sublist_from_end_from_start(list, 5, 5), list, 'sublist all with #-end # simple') -- Checks that the whole list is properly retrieved even if the value for start and end is not a simple number. This is especially important in generators where sublist uses [x:length - y] for # #-end. assertEquals(list_sublist_from_start_from_end(list, 0 + 1, 0 + 1), list, 'sublist all with # #-end math simple') end -- Creates a list for use with the sublist test. function get_space_shuttles() number_of_calls = number_of_calls + 1 return {'Columbia', 'Challenger', 'Discovery', 'Atlantis', 'Endeavour'} end -- Tests the "get sub-list" block with a function call. function test_sublist_complex() number_of_calls = 0 assertEquals(list_sublist_from_start_from_start(get_space_shuttles(), 2, 3), {'Challenger', 'Discovery'}, 'sublist # start complex') check_number_of_calls('sublist # start complex') number_of_calls = 0 assertEquals(list_sublist_from_start_from_start(true and get_space_shuttles() or nil, true and 2 or nil, true and 3 or nil), {'Challenger', 'Discovery'}, 'sublist # start order complex') check_number_of_calls('sublist # start order complex') number_of_calls = 0 -- The order for index for #-end is addition because this will catch errors in generators where most perform the operation ... - index. assertEquals(list_sublist_from_end_from_end(get_space_shuttles(), 3, 2), {'Discovery', 'Atlantis'}, 'sublist # end complex') assertEquals(number_of_calls, 1, 'sublist # end complex number of calls') number_of_calls = 0 assertEquals(list_sublist_from_end_from_end(true and get_space_shuttles() or nil, 0 + 3, 0 + 2), {'Discovery', 'Atlantis'}, 'sublist # end order complex') check_number_of_calls('sublist # end order complex') number_of_calls = 0 assertEquals(list_sublist_first_last(get_space_shuttles()), list, 'sublist first-last complex') check_number_of_calls('sublist first-last complex') number_of_calls = 0 assertEquals(list_sublist_from_start_from_end(get_space_shuttles(), 2, 2), {'Challenger', 'Discovery', 'Atlantis'}, 'sublist # #-end complex') check_number_of_calls('sublist # #-end complex') number_of_calls = 0 assertEquals(list_sublist_from_end_from_start(get_space_shuttles(), 3, 4), {'Discovery', 'Atlantis'}, 'sublist #-end # complex') check_number_of_calls('sublist #-end # complex') number_of_calls = 0 assertEquals(list_sublist_first_from_start(get_space_shuttles(), 4), {'Columbia', 'Challenger', 'Discovery', 'Atlantis'}, 'sublist first # complex') check_number_of_calls('sublist first # complex') number_of_calls = 0 assertEquals(list_sublist_first_from_end(get_space_shuttles(), 4), {'Columbia', 'Challenger'}, 'sublist first #-end complex') check_number_of_calls('sublist first #-end complex') number_of_calls = 0 assertEquals(list_sublist_from_start_last(get_space_shuttles(), 4), {'Atlantis', 'Endeavour'}, 'sublist # last complex') check_number_of_calls('sublist # last complex') number_of_calls = 0 assertEquals(list_sublist_from_end_last(get_space_shuttles(), 4), {'Challenger', 'Discovery', 'Atlantis', 'Endeavour'}, 'sublist #-end last simple') check_number_of_calls('sublist #-end last simple') number_of_calls = 0 assertEquals(list_sublist_from_start_from_end(get_space_shuttles(), 1, 1), list, 'sublist all with # #-end complex') check_number_of_calls('sublist all with # #-end complex') number_of_calls = 0 assertEquals(list_sublist_from_end_from_start(get_space_shuttles(), 5, 5), list, 'sublist all with #-end # complex') check_number_of_calls('sublist all with #-end # complex') number_of_calls = 0 -- Checks that the whole list is properly retrieved even if the value for start and end is not a simple number. This is especially important in generators where sublist uses [x:length - y] for # #-end. assertEquals(list_sublist_from_start_from_end(get_space_shuttles(), 0 + 1, 0 + 1), list, 'sublist all with # #-end math complex') check_number_of_calls('sublist all with # #-end math complex') end -- Tests the "join" block. function test_join() list = {'Vulcan', 'Klingon', 'Borg'} assertEquals(table.concat(list, ','), 'Vulcan,Klingon,Borg', 'join') assertEquals(table.concat(true and list or nil, ','), 'Vulcan,Klingon,Borg', 'join order') end function list_string_split(input, delim) local t = {} local pos = 1 while true do next_delim = string.find(input, delim, pos) if next_delim == nil then table.insert(t, string.sub(input, pos)) break else table.insert(t, string.sub(input, pos, next_delim-1)) pos = next_delim + #delim end end return t end -- Tests the "split" block. function test_split() text = 'Vulcan,Klingon,Borg' assertEquals(list_string_split(text, ','), {'Vulcan', 'Klingon', 'Borg'}, 'split') assertEquals(list_string_split(true and text or nil, ','), {'Vulcan', 'Klingon', 'Borg'}, 'split order') end function list_sort(list, typev, direction) local t = {} for n,v in pairs(list) do table.insert(t, v) end local compareFuncs = { NUMERIC = function(a, b) return (tonumber(tostring(a)) or 0) < (tonumber(tostring(b)) or 0) end, TEXT = function(a, b) return tostring(a) < tostring(b) end, IGNORE_CASE = function(a, b) return string.lower(tostring(a)) < string.lower(tostring(b)) end } local compareTemp = compareFuncs[typev] local compare = compareTemp if direction == -1 then compare = function(a, b) return compareTemp(b, a) end end table.sort(t, compare) return t end -- Tests the "alphabetic sort" block. function test_sort_alphabetic() list = {'Vulcan', 'klingon', 'Borg'} assertEquals(list_sort(list,"TEXT", 1), {'Borg', 'Vulcan', 'klingon'}, 'sort alphabetic ascending') assertEquals(list_sort(true and list or nil,"TEXT", 1), {'Borg', 'Vulcan', 'klingon'}, 'sort alphabetic ascending order') end -- Tests the "alphabetic sort ignore case" block. function test_sort_ignoreCase() list = {'Vulcan', 'klingon', 'Borg'} assertEquals(list_sort(list,"IGNORE_CASE", 1), {'Borg', 'klingon', 'Vulcan'}, 'sort ignore case ascending') assertEquals(list_sort(true and list or nil,"IGNORE_CASE", 1), {'Borg', 'klingon', 'Vulcan'}, 'sort ignore case ascending order') end -- Tests the "numeric sort" block. function test_sort_numeric() list = {8, 18, -1} assertEquals(list_sort(list,"NUMERIC", -1), {18, 8, -1}, 'sort numeric descending') assertEquals(list_sort(true and list or nil,"NUMERIC", -1), {18, 8, -1}, 'sort numeric descending order') end function list_reverse(input) local reversed = {} for i = #input, 1, -1 do table.insert(reversed, input[i]) end return reversed end -- Tests the "list reverse" block. function test_lists_reverse() list = {8, 18, -1, 64} assertEquals(list_reverse(list), {64, -1, 18, 8}, 'reverse a copy') assertEquals(list, {8, 18, -1, 64}, 'reverse a copy original') list = {} assertEquals(list_reverse(list), {}, 'empty list') end -- Describe this function... function test_colour_picker() assertEquals('#ff6600', '#ff6600', 'static colour') end function colour_rgb(r, g, b) r = math.floor(math.min(100, math.max(0, r)) * 2.55 + .5) g = math.floor(math.min(100, math.max(0, g)) * 2.55 + .5) b = math.floor(math.min(100, math.max(0, b)) * 2.55 + .5) return string.format("#%02x%02x%02x", r, g, b) end -- Describe this function... function test_rgb() assertEquals(colour_rgb(100, 40, 0), '#ff6600', 'from rgb') end -- Describe this function... function test_colour_random() for count4 = 1, 100 do item = string.format("#%06x", math.random(0, 2^24 - 1)) assertEquals(#item, 7, 'length of random colour string: ' .. item) assertEquals(string.sub(item, 1, 1), '#', 'format of random colour string: ' .. item) for i = 1, 6, 1 do assertEquals(0 ~= firstIndexOf('abcdefABDEF0123456789', text_char_at(item, i + 1)), true, table.concat({'contents of random colour string: ', item, ' at index: ', i + 1})) end end end function colour_blend(colour1, colour2, ratio) local r1 = tonumber(string.sub(colour1, 2, 3), 16) local r2 = tonumber(string.sub(colour2, 2, 3), 16) local g1 = tonumber(string.sub(colour1, 4, 5), 16) local g2 = tonumber(string.sub(colour2, 4, 5), 16) local b1 = tonumber(string.sub(colour1, 6, 7), 16) local b2 = tonumber(string.sub(colour2, 6, 7), 16) local ratio = math.min(1, math.max(0, ratio)) local r = math.floor(r1 * (1 - ratio) + r2 * ratio + .5) local g = math.floor(g1 * (1 - ratio) + g2 * ratio + .5) local b = math.floor(b1 * (1 - ratio) + b2 * ratio + .5) return string.format("#%02x%02x%02x", r, g, b) end -- Describe this function... function test_blend() assertEquals(colour_blend('#ff0000', colour_rgb(100, 40, 0), 0.4), '#ff2900', 'blend') end -- Describe this function... function test_procedure() procedure_1(8, 2) assertEquals(proc_z, 4, 'procedure with global') proc_w = false procedure_2(false) assertEquals(proc_w, true, 'procedure no return') proc_w = false procedure_2(true) assertEquals(proc_w, false, 'procedure return') end -- Describe this function... function procedure_1(proc_x, proc_y) proc_z = proc_x / proc_y end -- Describe this function... function procedure_2(proc_x) if proc_x then return end proc_w = true end -- Describe this function... function test_function() assertEquals(function_1(2, 3), -1, 'function with arguments') assertEquals(func_z, 'side effect', 'function with side effect') func_a = 'unchanged' func_c = 'global' assertEquals(function_2(2), '3global', 'function with global') assertEquals(func_a, 'unchanged', 'function with scope') assertEquals(function_3(true), true, 'function return') assertEquals(function_3(false), false, 'function no return') end -- Describe this function... function function_1(func_x, func_y) func_z = 'side effect' return func_x - func_y end -- Describe this function... function function_2(func_a) func_a = func_a + 1 return func_a .. func_c end -- Describe this function... function function_3(func_a) if func_a then return true end return false end -- Describe this function... function recurse(n) if n > 0 then text = table.concat({recurse(n - 1), n, recurse(n - 1)}) else text = '-' end return text end unittestResults = {} print('\n====================\n\nRunning suite: Logic') assertEquals(true, true, 'True') assertEquals(false, false, 'False') assertEquals(not false, true, 'Not true') assertEquals(not true, false, 'Not false') test_if() test_ifelse() test_equalities() test_and() test_or() test_ternary() print(unittest_report()) unittestResults = nil unittestResults = {} print('\n====================\n\nRunning suite: Loops 1') test_repeat() test_repeat_ext() test_while() test_foreach() print(unittest_report()) unittestResults = nil unittestResults = {} print('\n====================\n\nRunning suite: Loops 2') test_count_loops() test_count_by() print(unittest_report()) unittestResults = nil unittestResults = {} print('\n====================\n\nRunning suite: Loops 3') test_break() test_continue() print(unittest_report()) unittestResults = nil unittestResults = {} print('\n====================\n\nRunning suite: Math') test_arithmetic() test_single() test_trig() test_constant() test_change() test_number_properties() test_round() test_operations_on_list() test_constraint() test_mod() test_random_integer() test_random_fraction() test_atan2() print(unittest_report()) unittestResults = nil unittestResults = {} print('\n====================\n\nRunning suite: Text') test_text_length() test_empty_text() test_create_text() test_append() test_find_text_simple() test_find_text_complex() test_get_text_simple() test_get_text_complex() test_substring_simple() test_substring_complex() test_case() test_trim() test_count_text() test_text_reverse() test_replace() test_multiline() print(unittest_report()) unittestResults = nil unittestResults = {} print('\n====================\n\nRunning suite: Lists') test_create_lists() test_lists_empty() test_lists_length() test_find_lists_simple() test_find_lists_complex() test_get_lists_simple() test_get_lists_create_list() test_get_lists_complex() test_getRemove() test_remove() test_set() test_insert() test_sublist_simple() test_sublist_complex() test_join() test_split() test_sort_alphabetic() test_sort_ignoreCase() test_sort_numeric() test_lists_reverse() print(unittest_report()) unittestResults = nil unittestResults = {} print('\n====================\n\nRunning suite: Colour') test_colour_picker() test_blend() test_rgb() test_colour_random() print(unittest_report()) unittestResults = nil unittestResults = {} print('\n====================\n\nRunning suite: Variables') item = 123 assertEquals(item, 123, 'variable') if2 = 123 assertEquals(if2, 123, 'reserved variable') print(unittest_report()) unittestResults = nil local _ = -- Intentionally non-connected variable. naked unittestResults = {} print('\n====================\n\nRunning suite: Functions') test_procedure() test_function() assertEquals(recurse(3), '-1-2-1-3-1-2-1-', 'test recurse') print(unittest_report()) unittestResults = nil