From 0d56e3bebecb7008f0baa7eaccf3dc9b2a39e360 Mon Sep 17 00:00:00 2001 From: Mark Pulford Date: Sun, 8 May 2011 02:23:57 +0930 Subject: Implement data driven test framework - Add run_test(): Test a function and verify its output - Add run_test_group(): Execute a test batch - Add serialise_value(): Serialise a Lua value into the Lua syntax - Add file_save() helper function - Add NaN comparison support to compare_values() --- tests/bench.lua | 15 +++-- tests/common.lua | 168 ++++++++++++++++++++++++++++++++++++++++++++++++------- tests/test.lua | 4 ++ 3 files changed, 158 insertions(+), 29 deletions(-) diff --git a/tests/bench.lua b/tests/bench.lua index b5b1a77..847365b 100755 --- a/tests/bench.lua +++ b/tests/bench.lua @@ -3,11 +3,11 @@ -- Simple JSON benchmark. -- -- Your Mileage May Vary. +-- +-- Mark Pulford require "common" local json = require "cjson" ---local json = require "json" ---local json = require "dkjson" function bench_file(filename) local data_json = file_load(filename) @@ -28,12 +28,11 @@ function bench_file(filename) return benchmark(tests, 5000, 5) end -i = 1 -while arg[i] do - local results = {} - results[arg[i]] = bench_file(arg[i]) - dump_value(results) - i = i + 1 +for i = 1, #arg do + local results = bench_file(arg[i]) + for k, v in pairs(results) do + print(string.format("%s: %s: %d", arg[i], k, v)) + end end -- vi:ai et sw=4 ts=4: diff --git a/tests/common.lua b/tests/common.lua index 219de3f..9a7ed19 100644 --- a/tests/common.lua +++ b/tests/common.lua @@ -1,31 +1,94 @@ require "cjson" require "posix" -function dump_value(value, indent) - if indent == nil then - indent = "" +-- Misc routines to assist with CJSON testing +-- +-- Mark Pulford + +-- Determine with a Lua table can be treated as an array. +-- Explicitly returns "not an array" for very sparse arrays. +-- Returns: +-- -1 Not an array +-- 0 Empty table +-- >0 Highest index in the array +function is_array(table) + local max = 0 + local count = 0 + for k, v in pairs(table) do + if type(k) == "number" then + if k > max then max = k end + count = count + 1 + else + return -1 + end + end + if max > count * 2 then + return -1 end - if value == cjson.null then - value = "" + return max +end + +function serialise_table(value, indent) + local spacing, spacing2, indent2 + if indent then + spacing = "\n" .. indent + spacing2 = spacing .. " " + indent2 = indent .. " " + else + spacing, spacing2, indent2 = " ", " ", false end - if type(value) == "string" or type(value) == "number" or - type(value) == "boolean" then - print(indent .. tostring(value)) - elseif type(value) == "table" then - local count = 0 - for k, v in pairs(value) do - dump_value(v, indent .. k .. ": ") - count = count + 1 + local max = is_array(value) + + local comma = false + local fragment = { "{" .. spacing2 } + if max > 0 then + -- Serialise array + for i = 1, max do + if comma then + table.insert(fragment, "," .. spacing2) + end + table.insert(fragment, serialise_value(value[i], indent2)) + comma = true end - if count == 0 then - print(indent .. ": ") + elseif max < 0 then + -- Serialise table + for k, v in pairs(value) do + if comma then + table.insert(fragment, "," .. spacing2) + end + table.insert(fragment, string.format( + "[%s] = %s", serialise_value(k, indent2), + serialise_value(v, indent2)) + ) + comma = true end + end + table.insert(fragment, spacing .. "}") + + return table.concat(fragment) +end + +function serialise_value(value, indent) + if indent == nil then indent = "" end + + if value == cjson.null then + return "cjson.null" + elseif type(value) == "string" then + return string.format("%q", value) + elseif type(value) == "nil" or type(value) == "number" or + type(value) == "boolean" then + return tostring(value) + elseif type(value) == "table" then + return serialise_table(value, indent) else - print(indent .. "<" .. type(value) .. ">") + return "\"<" .. type(value) .. ">\"" end +end +function dump_value(value) + print(serialise_value(value)) end function file_load(filename) @@ -39,6 +102,15 @@ function file_load(filename) return data end +function file_save(filename, data) + local file, err = io.open(filename, "w") + if file == nil then + error("Unable to write " .. filename) + end + file:write(data) + file:close() +end + function gettimeofday() local tv_sec, tv_usec = posix.gettimeofday() @@ -82,29 +154,83 @@ function compare_values(val1, val2) if type1 ~= type2 then return false end + + -- Check for NaN + if type1 == "number" and val1 ~= val1 and val2 ~= val2 then + return true + end + if type1 ~= "table" then return val1 == val2 end - local val1_keys = {} - -- Note all the keys in val1 need to be checked + + -- check_keys stores all the keys that must be checked in val2 + local check_keys = {} for k, _ in pairs(val1) do check_keys[k] = true end + for k, v in pairs(val2) do if not check_keys[k] then - -- Key didn't exist in val1 return false end - if not compare_value(val1[k], val2[k]) then + + if not compare_values(val1[k], val2[k]) then return false end + check_keys[k] = nil end for k, _ in pairs(check_keys) do - -- Not the same if any keys left to check + -- Not the same if any keys from val1 were not found in val2 return false end return true end +function run_test(testname, func, input, should_work, output) + local function status_line(name, status, value) + local statusmap = { [true] = ":success", [false] = ":error" } + if status ~= nil then + name = name .. statusmap[status] + end + print(string.format("[%s] %s", name, serialise_value(value, false))) + end + + local result = { pcall(func, unpack(input)) } + local success = table.remove(result, 1) + + local correct = false + if success == should_work and compare_values(result, output) then + correct = true + end + + local teststatus = { [true] = "PASS", [false] = "FAIL" } + print("==> Test " .. testname .. ": " .. teststatus[correct]) + + status_line("Input", nil, input) + if not correct then + status_line("Expected", should_work, output) + end + status_line("Received", success, result) + print() + + return correct, result +end + +function run_test_group(testgroup, tests) + for k, v in ipairs(tests) do + if type(v) == "function" then + -- Useful for changing configuration during a batch + msg = v() + print(string.format("==> Config %s [%d]: %s", testgroup, k, msg)) + print() + elseif type(v) == "table" then + run_test(testgroup .. " [" .. k .. "]", unpack(v)) + else + error("Testgroup can only contain functions and tables") + end + end +end + -- vi:ai et sw=4 ts=4: diff --git a/tests/test.lua b/tests/test.lua index 3c8c404..51f29c4 100755 --- a/tests/test.lua +++ b/tests/test.lua @@ -1,5 +1,9 @@ #!/usr/bin/env lua +-- CJSON tests +-- +-- Mark Pulford + require "common" local json = require "cjson" -- cgit v1.2.3-55-g6feb