Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 29 additions & 6 deletions build/tested.lua
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,25 @@ local assert_table = require("tested.assert_table")

local tested = { tests = {}, run_only_tests = false }

local function extract_fn_and_options(fn_or_options, fn)
local function validate_options(test_name, options, test_src)
local error_prefix = test_src .. " in \"" .. test_name .. "\": "
if options.expected ~= nil then
if type(options.expected) ~= "string" then
error(error_prefix .. "options.expected takes in a 'string', but received '" .. type(options.expected) .. "'", 0)
end
if not (options.expected == "FAIL" or options.expected == "EXCEPTION" or options.expected == "UNKNOWN") then
error(error_prefix .. "options.expected should be one of 'FAIL', 'EXCEPTION', or 'UNKNOWN'", 0)
end
end

if options.run_when ~= nil then
if type(options.run_when) ~= "boolean" then
error(error_prefix .. "options.run_when takes in a 'boolean', but received '" .. type(options.run_when) .. "'", 0)
end
end
end

local function extract_fn_and_options(test_name, fn_or_options, fn, test_src)
local options = {}
if type(fn_or_options) == "function" then
fn = fn_or_options
Expand All @@ -14,37 +32,42 @@ local function extract_fn_and_options(fn_or_options, fn)
fn = fn
end

validate_options(test_name, options, test_src or "?")

return fn, options
end


function tested.test(name, fn_or_options, fn)
local func, options = extract_fn_and_options(fn_or_options, fn)
local test_src = debug.getinfo(2, "S").short_src
local func, options = extract_fn_and_options(name, fn_or_options, fn, test_src)
table.insert(tested.tests, { name = name, fn = func, options = options, kind = "test" })
end

function tested.skip(name, fn_or_options, fn)
local func, options = extract_fn_and_options(fn_or_options, fn)
local test_src = debug.getinfo(2, "S").short_src
local func, options = extract_fn_and_options(name, fn_or_options, fn, test_src)
table.insert(tested.tests, { name = name, fn = func, options = options, kind = "skip" })
end

function tested.only(name, fn_or_options, fn)
local func, options = extract_fn_and_options(fn_or_options, fn)
local test_src = debug.getinfo(2, "S").short_src
local func, options = extract_fn_and_options(name, fn_or_options, fn, test_src)
table.insert(tested.tests, { name = name, fn = func, options = options, kind = "only" })
end

function tested.assert(assertion)
local errors = {}
if assertion.expected == nil then table.insert(errors, "'expected'") end
if assertion.actual == nil then table.insert(errors, "'actual'") end
assert(#errors == 0, "The assertion table must include 'expected' and 'actual' whose values cannot be 'nil'. Missing (or 'nil') fields: " .. table.concat(errors, ", "))
if #errors ~= 0 then error("The assertion table must include 'expected' and 'actual' whose values cannot be 'nil'. Missing (or 'nil') fields: " .. table.concat(errors, ", "), 0) end
if assertion.given and type(assertion.given) ~= "string" then
table.insert(errors, "In assertion, 'given' should be a 'string'. It appears to be a '" .. type(assertion.given) .. "' with value: '" .. tostring(assertion.given))
end
if assertion.should and type(assertion.should) ~= "string" then
table.insert(errors, "In assertion, 'should' should be a 'string'. It appears to be a '" .. type(assertion.should) .. "' with value: " .. tostring(assertion.should))
end
assert(#errors == 0, table.concat(errors, ". "))
if #errors ~= 0 then error(table.concat(errors, ". "), 0) end
local expected_type = type(assertion.expected)
local actual_type = type(assertion.actual)

Expand Down
14 changes: 7 additions & 7 deletions build/tested/main.lua
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ local function validate_args(args)
for _, path in ipairs(args.paths) do
local info, err = lfs.attributes(path)
if err then error("The file or directory '" .. path .. "' does not appear to exist. Unable to run tests") end
assert(info.mode == "directory" or info.mode == "file", "tested requires the paths passed in to be a directory or file")
if not (info.mode == "directory" or info.mode == "file") then error("tested requires the paths passed in to be a directory or file", 0) end
if info.mode == "directory" then table.insert(args.test_directories, path) end
if info.mode == "file" then table.insert(args.test_files, path) end
end
Expand All @@ -144,14 +144,14 @@ local function load_result_formatter(args)
logger:info("Unable to load as module, attempting to load from filepath")
local info, err = lfs.attributes(args.custom_formatter)
if err then error("Unable to load custom formatter, the file/module '" .. args.custom_formatter .. "' could not be loaded.") end
assert(info.mode == "file", "The custom formatter should point to a file, but currently appears to be a: " .. info.mode)
if info.mode ~= "file" then error("The custom formatter should point to a file, but currently appears to be a: " .. info.mode, 0) end
formatter = file_loader.load_file(args.custom_formatter)
end

if formatter then
assert(formatter.header and type(formatter.header) == "function", "Custom formatter must include a 'header', 'results', and 'summary' section. Missing 'header'.")
assert(formatter.results and type(formatter.results) == "function", "Custom formatter must include a 'header', 'results', and 'summary' section. Missing 'results'.")
assert(formatter.summary and type(formatter.summary) == "function", "Custom formatter must include a 'header', 'results', and 'summary' section. Missing 'summary'.")
if not (formatter.header and type(formatter.header) == "function") then error("Custom formatter must include a 'header', 'results', and 'summary' section. Missing 'header'.", 0) end
if not (formatter.results and type(formatter.results) == "function") then error("Custom formatter must include a 'header', 'results', and 'summary' section. Missing 'results'.", 0) end
if not (formatter.summary and type(formatter.summary) == "function") then error("Custom formatter must include a 'header', 'results', and 'summary' section. Missing 'summary'.", 0) end
return formatter
else
error("Unable to load custom formatter from: " .. args.custom_formatter)
Expand All @@ -172,7 +172,7 @@ local function register_format_handler(handlers)

local info, err = lfs.attributes(handler)
if err then error("Unable to load format handler, the file/module '" .. handler .. "' was not able to be loaded.") end
assert(info.mode == "file", "The custom format loader should point to a file, but currently appears to be a: " .. info.mode)
if info.mode ~= "file" then error("The custom format loader should point to a file, but currently appears to be a: " .. info.mode, 0) end

file_loader.load_and_register_handler(handler)
end
Expand Down Expand Up @@ -293,7 +293,7 @@ local function main()


local test_files = get_all_test_files(args)
assert(#test_files > 0, "Unable to find any tests to run in: " .. table.concat(args.paths, ", "))
if #test_files == 0 then error("Unable to find any tests to run in: " .. table.concat(args.paths, ", "), 0) end


formatter.header(TESTED_VERSION, args.paths)
Expand Down
4 changes: 3 additions & 1 deletion build/tested/test_runner.lua
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ function test_runner.run_with_cleanup(file_loader, test_file, options)
for package_name, _ in pairs(package.loaded) do pre_test_loaded_packages[package_name] = true end

local test_module = file_loader.load_file(test_file)
assert(type(test_module) == "table" and type(test_module.tests) == "table" and type(test_module.run_only_tests) == "boolean", "It does not appear that '" .. test_file .. "' returns the 'tested' module")
if not (type(test_module) == "table" and type(test_module.tests) == "table" and type(test_module.run_only_tests) == "boolean") then
error(test_file .. ": does not 'return tested' at end of file - unable to run tests", 0)
end

local test_results = test_module:run(test_file, options)

Expand Down
35 changes: 29 additions & 6 deletions src/tested.tl
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,25 @@ local assert_table = require("tested.assert_table")

local tested: types.Tested = { tests = {}, run_only_tests = false }

local function extract_fn_and_options(fn_or_options: function() | types.TestedOptions, fn?: function()): function(), types.TestedOptions
local function validate_options(test_name: string, options: types.TestedOptions, test_src: string)
local error_prefix = test_src .. " in \"" .. test_name .. "\": "
if options.expected ~= nil then
if type(options.expected) ~= "string" then
error(error_prefix .. "options.expected takes in a 'string', but received '" .. type(options.expected) .. "'", 0)
end
if not (options.expected == "FAIL" or options.expected == "EXCEPTION" or options.expected == "UNKNOWN") then
error(error_prefix .. "options.expected should be one of 'FAIL', 'EXCEPTION', or 'UNKNOWN'", 0)
end
end

if options.run_when ~= nil then
if type(options.run_when) ~= "boolean" then
error(error_prefix .. "options.run_when takes in a 'boolean', but received '" .. type(options.run_when) .. "'", 0)
end
end
end

local function extract_fn_and_options(test_name: string, fn_or_options: function() | types.TestedOptions, fn?: function(), test_src?: string): function(), types.TestedOptions
local options: types.TestedOptions = {}
if type(fn_or_options) == "function" then
fn = fn_or_options as function()
Expand All @@ -14,37 +32,42 @@ local function extract_fn_and_options(fn_or_options: function() | types.TestedOp
fn = fn
end

validate_options(test_name, options, test_src or "?")

return fn, options
end

-- teal currently doesn't support polymorphism, so we gotta kinda handle it ourselves
function tested.test(name: string, fn_or_options: function() | types.TestedOptions, fn?: function())
local func, options = extract_fn_and_options(fn_or_options, fn)
local test_src = debug.getinfo(2, "S").short_src
local func, options = extract_fn_and_options(name, fn_or_options, fn, test_src)
table.insert(tested.tests, {name=name, fn=func, options=options, kind="test"})
end

function tested.skip(name: string, fn_or_options: function() | types.TestedOptions, fn?: function())
local func, options = extract_fn_and_options(fn_or_options, fn)
local test_src = debug.getinfo(2, "S").short_src
local func, options = extract_fn_and_options(name, fn_or_options, fn, test_src)
table.insert(tested.tests, {name=name, fn=func, options=options, kind="skip"})
end

function tested.only(name: string, fn_or_options: function() | types.TestedOptions, fn?: function())
local func, options = extract_fn_and_options(fn_or_options, fn)
local test_src = debug.getinfo(2, "S").short_src
local func, options = extract_fn_and_options(name, fn_or_options, fn, test_src)
table.insert(tested.tests, {name=name, fn=func, options=options, kind="only"})
end

function tested.assert<T>(assertion: types.Assertion<T>): boolean, string
local errors = {}
if assertion.expected == nil then table.insert(errors, "'expected'") end
if assertion.actual == nil then table.insert(errors, "'actual'") end
assert(#errors == 0, "The assertion table must include 'expected' and 'actual' whose values cannot be 'nil'. Missing (or 'nil') fields: " .. table.concat(errors, ", "))
if #errors ~= 0 then error("The assertion table must include 'expected' and 'actual' whose values cannot be 'nil'. Missing (or 'nil') fields: " .. table.concat(errors, ", "), 0) end
if assertion.given and type(assertion.given) ~= "string" then
table.insert(errors, "In assertion, 'given' should be a 'string'. It appears to be a '" .. type(assertion.given) .. "' with value: '" .. tostring(assertion.given))
end
if assertion.should and type(assertion.should) ~= "string" then
table.insert(errors, "In assertion, 'should' should be a 'string'. It appears to be a '" .. type(assertion.should) .. "' with value: " .. tostring(assertion.should))
end
assert(#errors == 0, table.concat(errors, ". "))
if #errors ~= 0 then error(table.concat(errors, ". "), 0) end
local expected_type = type(assertion.expected)
local actual_type = type(assertion.actual)

Expand Down
14 changes: 7 additions & 7 deletions src/tested/main.tl
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ local function validate_args(args: CLIOptions)
for _, path in ipairs(args.paths) do
local info, err = lfs.attributes(path)
if err then error("The file or directory '" .. path .. "' does not appear to exist. Unable to run tests") end
assert(info.mode == "directory" or info.mode == "file", "tested requires the paths passed in to be a directory or file")
if not (info.mode == "directory" or info.mode == "file") then error("tested requires the paths passed in to be a directory or file", 0) end
if info.mode == "directory" then table.insert(args.test_directories, path) end
if info.mode == "file" then table.insert(args.test_files, path) end
end
Expand All @@ -144,14 +144,14 @@ local function load_result_formatter(args: CLIOptions): types.ResultFormatter
logger:info("Unable to load as module, attempting to load from filepath")
local info, err = lfs.attributes(args.custom_formatter)
if err then error("Unable to load custom formatter, the file/module '" .. args.custom_formatter .. "' could not be loaded.") end
assert(info.mode == "file", "The custom formatter should point to a file, but currently appears to be a: " .. info.mode)
if info.mode ~= "file" then error("The custom formatter should point to a file, but currently appears to be a: " .. info.mode, 0) end
formatter = file_loader.load_file(args.custom_formatter) as types.ResultFormatter
end

if formatter then
assert(formatter.header and type(formatter.header) == "function", "Custom formatter must include a 'header', 'results', and 'summary' section. Missing 'header'.")
assert(formatter.results and type(formatter.results) == "function", "Custom formatter must include a 'header', 'results', and 'summary' section. Missing 'results'.")
assert(formatter.summary and type(formatter.summary) == "function", "Custom formatter must include a 'header', 'results', and 'summary' section. Missing 'summary'.")
if not (formatter.header and type(formatter.header) == "function") then error("Custom formatter must include a 'header', 'results', and 'summary' section. Missing 'header'.", 0) end
if not (formatter.results and type(formatter.results) == "function") then error("Custom formatter must include a 'header', 'results', and 'summary' section. Missing 'results'.", 0) end
if not (formatter.summary and type(formatter.summary) == "function") then error("Custom formatter must include a 'header', 'results', and 'summary' section. Missing 'summary'.", 0) end
return formatter
else
error("Unable to load custom formatter from: " .. args.custom_formatter)
Expand All @@ -172,7 +172,7 @@ local function register_format_handler(handlers: {string})

local info, err = lfs.attributes(handler)
if err then error("Unable to load format handler, the file/module '" .. handler .."' was not able to be loaded.") end
assert(info.mode == "file", "The custom format loader should point to a file, but currently appears to be a: " .. info.mode)
if info.mode ~= "file" then error("The custom format loader should point to a file, but currently appears to be a: " .. info.mode, 0) end

file_loader.load_and_register_handler(handler)
end
Expand Down Expand Up @@ -293,7 +293,7 @@ local function main()

-- finding all the files
local test_files = get_all_test_files(args)
assert(#test_files > 0, "Unable to find any tests to run in: " .. table.concat(args.paths, ", "))
if #test_files == 0 then error("Unable to find any tests to run in: " .. table.concat(args.paths, ", "), 0) end

-- running the tests
formatter.header(TESTED_VERSION, args.paths)
Expand Down
4 changes: 3 additions & 1 deletion src/tested/test_runner.tl
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ function test_runner.run_with_cleanup(file_loader: types.FileLoader, test_file:
for package_name, _ in pairs(package.loaded) do pre_test_loaded_packages[package_name] = true end

local test_module = file_loader.load_file(test_file) as types.Tested
assert(type(test_module) == "table" and type(test_module.tests) == "table" and type(test_module.run_only_tests) == "boolean", "It does not appear that '" .. test_file .."' returns the 'tested' module")
if not (type(test_module) == "table" and type(test_module.tests) == "table" and type(test_module.run_only_tests) == "boolean") then
error(test_file ..": does not 'return tested' at end of file - unable to run tests", 0)
end

local test_results = test_module:run(test_file, options)

Expand Down
Loading