From 5f0018317736ba56e5958c119bfb9a4b17c40963 Mon Sep 17 00:00:00 2001 From: myk002 Date: Mon, 22 Mar 2021 10:15:32 -0700 Subject: [PATCH 1/4] move test harness out of test dir and install to test.lua in top-level scripts directory instead of test/main.lua --- CMakeLists.txt | 1 + test/main.lua => ci/test.lua | 0 docs/changelog.txt | 1 + test/{test_test.lua => test.lua} | 0 4 files changed, 2 insertions(+) rename test/main.lua => ci/test.lua (100%) rename test/{test_test.lua => test.lua} (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 800f74e37..db4149630 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -503,6 +503,7 @@ if(BUILD_TESTS) endif() install(DIRECTORY ${dfhack_SOURCE_DIR}/test DESTINATION ${DFHACK_DATA_DESTINATION}/scripts) + install(FILES ci/test.lua DESTINATION ${DFHACK_DATA_DESTINATION}/scripts) endif() # Packaging with CPack! diff --git a/test/main.lua b/ci/test.lua similarity index 100% rename from test/main.lua rename to ci/test.lua diff --git a/docs/changelog.txt b/docs/changelog.txt index ee509e9df..fa862eecd 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -38,6 +38,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## Internals - The DFHack test harness is now much easier to use for iterative development. Configuration can now be specified on the commandline, there are more test filter options, and the test harness can now easily rerun tests that have been run before. +- The ``test/main`` command to invoke the test harness has been renamed to just ``test`` # 0.47.05-r1 diff --git a/test/test_test.lua b/test/test.lua similarity index 100% rename from test/test_test.lua rename to test/test.lua From e157c20fc28870b146ea1495eefd1b3a73ca2a09 Mon Sep 17 00:00:00 2001 From: myk002 Date: Mon, 22 Mar 2021 10:23:31 -0700 Subject: [PATCH 2/4] use new test harness script path --- travis/run-tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/travis/run-tests.py b/travis/run-tests.py index 8f20a8efa..400a7141d 100755 --- a/travis/run-tests.py +++ b/travis/run-tests.py @@ -73,7 +73,7 @@ with open(test_init_file, 'w') as f: f.write(''' devel/dump-rpc dfhack-rpc.txt :lua dfhack.internal.addScriptPath(dfhack.getHackPath()) - test/main --resume "lua scr.breakdown_level=df.interface_breakdown_types.%s" + test --resume "lua scr.breakdown_level=df.interface_breakdown_types.%s" ''' % ('NONE' if args.no_quit else 'QUIT')) test_config_file = 'test_config.json' From a9192ebe123a4016dc1b11ec65ec3edfbd13d86f Mon Sep 17 00:00:00 2001 From: myk002 Date: Mon, 22 Mar 2021 11:09:44 -0700 Subject: [PATCH 3/4] update docs with new script path --- ci/test.lua | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/ci/test.lua b/ci/test.lua index 6d3d09653..ed19a6f09 100644 --- a/ci/test.lua +++ b/ci/test.lua @@ -8,14 +8,14 @@ local utils = require 'utils' local help_text = [====[ -test/main -========= +test +==== Run DFHack tests. Usage: - test/main [] [] + test [] [] If a done_command is specified, it will be run after the tests complete. @@ -35,14 +35,13 @@ Options: Examples: - test/main runs all tests - test/main -r runs all tests that haven't been run before - test/main -m none runs tests that don't need the game to be in a - specific mode - test/main -t quickfort runs quickfort tests - test/main -d /path/to/dfhack-scripts/repo/test - runs tests in your in-development branch of the - scripts repo + test runs all tests + test -r runs all tests that haven't been run before + test -m none runs tests that don't need the game to be in a + specific mode + test -t quickfort runs quickfort tests + test -d /path/to/dfhack-scripts/repo/test + runs tests in your dev scripts repo Default values for the options may be set in a file named test_config.json in your DF folder. Options with comma-separated values should be written as json @@ -285,7 +284,7 @@ local function get_test_files(test_dir) local files = {} print('Loading tests from ' .. test_dir) for _, entry in ipairs(dfhack.filesystem.listdir_recursive(test_dir)) do - if not entry.isdir and not entry.path:match('main.lua') then + if not entry.isdir then table.insert(files, entry.path) end end From 62776f5568fa8ba0bdd77d901593117e23fa1144 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 24 Mar 2021 00:48:52 -0400 Subject: [PATCH 4/4] Move `expect` functions to a separate file This allows tests to test these functions without needing to include the test wrapper directly (now ci/test.lua, formerly test/main.lua). Hopefully this location is also more stable, similar to other libraries that are already tested. --- ci/test.lua | 122 +----------------------------- library/lua/test/expect.lua | 145 ++++++++++++++++++++++++++++++++++++ test/test.lua | 2 +- 3 files changed, 147 insertions(+), 122 deletions(-) create mode 100644 library/lua/test/expect.lua diff --git a/ci/test.lua b/ci/test.lua index 821117983..91f39310b 100644 --- a/ci/test.lua +++ b/ci/test.lua @@ -1,6 +1,7 @@ -- DFHack developer test harness --@ module = true +local expect = require 'test.expect' local json = require 'json' local script = require 'gui.script' local utils = require 'utils' @@ -66,127 +67,6 @@ local TestStatus = { local VALID_MODES = utils.invert{'none', 'title', 'fortress'} -expect = {} -function expect.true_(value, comment) - return not not value, comment, 'expected true, got ' .. tostring(value) -end -function expect.false_(value, comment) - return not value, comment, 'expected false, got ' .. tostring(value) -end -function expect.fail(comment) - return false, comment or 'check failed, no reason provided' -end -function expect.nil_(value, comment) - return value == nil, comment, 'expected nil, got ' .. tostring(value) -end -function expect.eq(a, b, comment) - return a == b, comment, ('%s ~= %s'):format(a, b) -end -function expect.ne(a, b, comment) - return a ~= b, comment, ('%s == %s'):format(a, b) -end -function expect.lt(a, b, comment) - return a < b, comment, ('%s >= %s'):format(a, b) -end -function expect.le(a, b, comment) - return a <= b, comment, ('%s > %s'):format(a, b) -end -function expect.gt(a, b, comment) - return a > b, comment, ('%s <= %s'):format(a, b) -end -function expect.ge(a, b, comment) - return a >= b, comment, ('%s < %s'):format(a, b) -end -local function table_eq_recurse(a, b, keys, known_eq) - if a == b then return true end - local checked = {} - for k,v in pairs(a) do - if type(a[k]) == 'table' then - if known_eq[a[k]] and known_eq[a[k]][b[k]] then goto skip end - table.insert(keys, tostring(k)) - if type(b[k]) ~= 'table' then - return false, keys, {tostring(a[k]), tostring(b[k])} - end - if not known_eq[a[k]] then known_eq[a[k]] = {} end - for eq_tab,_ in pairs(known_eq[a[k]]) do - known_eq[eq_tab][b[k]] = true - end - known_eq[a[k]][b[k]] = true - if not known_eq[b[k]] then known_eq[b[k]] = {} end - for eq_tab,_ in pairs(known_eq[b[k]]) do - known_eq[eq_tab][a[k]] = true - end - known_eq[b[k]][a[k]] = true - local matched, keys_at_diff, diff = - table_eq_recurse(a[k], b[k], keys, known_eq) - if not matched then return false, keys_at_diff, diff end - keys[#keys] = nil - elseif a[k] ~= b[k] then - table.insert(keys, tostring(k)) - return false, keys, {tostring(a[k]), tostring(b[k])} - end - ::skip:: - checked[k] = true - end - for k in pairs(b) do - if not checked[k] then - table.insert(keys, tostring(k)) - return false, keys, {tostring(a[k]), tostring(b[k])} - end - end - return true -end -function expect.table_eq(a, b, comment) - if (type(a) ~= 'table' and type(a) ~= 'userdata') or - (type(b) ~= 'table' and type(b) ~= 'userdata') then - return false, comment, 'operands to table_eq must be tables or userdata' - end - local keys, known_eq = {}, {} - local matched, keys_at_diff, diff = table_eq_recurse(a, b, keys, known_eq) - if matched then return true end - local keystr = '['..keys_at_diff[1]..']' - for i=2,#keys_at_diff do keystr = keystr..'['..keys_at_diff[i]..']' end - return false, comment, - ('key %s: "%s" ~= "%s"'):format(keystr, diff[1], diff[2]) -end -function expect.error(func, ...) - local ok, ret = pcall(func, ...) - if ok then - return false, 'no error raised by function call' - else - return true - end -end -function expect.error_match(func, matcher, ...) - local ok, err = pcall(func, ...) - if ok then - return false, 'no error raised by function call' - elseif type(matcher) == 'string' then - if not tostring(err):match(matcher) then - return false, ('error "%s" did not match "%s"'):format(err, matcher) - end - elseif not matcher(err) then - return false, ('error "%s" did not satisfy matcher'):format(err) - end - return true -end -function expect.pairs_contains(table, key, comment) - for k, v in pairs(table) do - if k == key then - return true - end - end - return false, comment, ('could not find key "%s" in table'):format(key) -end -function expect.not_pairs_contains(table, key, comment) - for k, v in pairs(table) do - if k == key then - return false, comment, ('found key "%s" in table'):format(key) - end - end - return true -end - local function delay(frames) frames = frames or 1 script.sleep(frames, 'frames') diff --git a/library/lua/test/expect.lua b/library/lua/test/expect.lua new file mode 100644 index 000000000..bdecdbafa --- /dev/null +++ b/library/lua/test/expect.lua @@ -0,0 +1,145 @@ +-- Internal implementations of expect.*() functions for tests + +-- NOTE: do not use this module in tests directly! The test wrapper (ci/test.lua) +-- wraps these functions to track which tests have passed/failed, and calling +-- these functions directly will not work as expected. + + +local expect = mkmodule('test.expect') + +function expect.true_(value, comment) + return not not value, comment, 'expected true, got ' .. tostring(value) +end + +function expect.false_(value, comment) + return not value, comment, 'expected false, got ' .. tostring(value) +end + +function expect.fail(comment) + return false, comment or 'check failed, no reason provided' +end + +function expect.nil_(value, comment) + return value == nil, comment, 'expected nil, got ' .. tostring(value) +end + +function expect.eq(a, b, comment) + return a == b, comment, ('%s ~= %s'):format(a, b) +end + +function expect.ne(a, b, comment) + return a ~= b, comment, ('%s == %s'):format(a, b) +end + +function expect.lt(a, b, comment) + return a < b, comment, ('%s >= %s'):format(a, b) +end + +function expect.le(a, b, comment) + return a <= b, comment, ('%s > %s'):format(a, b) +end + +function expect.gt(a, b, comment) + return a > b, comment, ('%s <= %s'):format(a, b) +end + +function expect.ge(a, b, comment) + return a >= b, comment, ('%s < %s'):format(a, b) +end + +local function table_eq_recurse(a, b, keys, known_eq) + if a == b then return true end + local checked = {} + for k,v in pairs(a) do + if type(a[k]) == 'table' then + if known_eq[a[k]] and known_eq[a[k]][b[k]] then goto skip end + table.insert(keys, tostring(k)) + if type(b[k]) ~= 'table' then + return false, keys, {tostring(a[k]), tostring(b[k])} + end + if not known_eq[a[k]] then known_eq[a[k]] = {} end + for eq_tab,_ in pairs(known_eq[a[k]]) do + known_eq[eq_tab][b[k]] = true + end + known_eq[a[k]][b[k]] = true + if not known_eq[b[k]] then known_eq[b[k]] = {} end + for eq_tab,_ in pairs(known_eq[b[k]]) do + known_eq[eq_tab][a[k]] = true + end + known_eq[b[k]][a[k]] = true + local matched, keys_at_diff, diff = + table_eq_recurse(a[k], b[k], keys, known_eq) + if not matched then return false, keys_at_diff, diff end + keys[#keys] = nil + elseif a[k] ~= b[k] then + table.insert(keys, tostring(k)) + return false, keys, {tostring(a[k]), tostring(b[k])} + end + ::skip:: + checked[k] = true + end + for k in pairs(b) do + if not checked[k] then + table.insert(keys, tostring(k)) + return false, keys, {tostring(a[k]), tostring(b[k])} + end + end + return true +end + +function expect.table_eq(a, b, comment) + if (type(a) ~= 'table' and type(a) ~= 'userdata') or + (type(b) ~= 'table' and type(b) ~= 'userdata') then + return false, comment, 'operands to table_eq must be tables or userdata' + end + local keys, known_eq = {}, {} + local matched, keys_at_diff, diff = table_eq_recurse(a, b, keys, known_eq) + if matched then return true end + local keystr = '['..keys_at_diff[1]..']' + for i=2,#keys_at_diff do keystr = keystr..'['..keys_at_diff[i]..']' end + return false, comment, + ('key %s: "%s" ~= "%s"'):format(keystr, diff[1], diff[2]) +end + +function expect.error(func, ...) + local ok, ret = pcall(func, ...) + if ok then + return false, 'no error raised by function call' + else + return true + end +end + +function expect.error_match(func, matcher, ...) + local ok, err = pcall(func, ...) + if ok then + return false, 'no error raised by function call' + elseif type(matcher) == 'string' then + if not tostring(err):match(matcher) then + return false, ('error "%s" did not match "%s"'):format(err, matcher) + end + elseif not matcher(err) then + return false, ('error "%s" did not satisfy matcher'):format(err) + end + return true +end + +function expect.pairs_contains(table, key, comment) + for k, v in pairs(table) do + if k == key then + return true + end + end + return false, comment, ('could not find key "%s" in table'):format(key) +end + +function expect.not_pairs_contains(table, key, comment) + for k, v in pairs(table) do + if k == key then + return false, comment, ('found key "%s" in table'):format(key) + end + end + return true +end + +return expect diff --git a/test/test.lua b/test/test.lua index 4543375e3..c2b083d38 100644 --- a/test/test.lua +++ b/test/test.lua @@ -1,4 +1,4 @@ -local expect_raw = reqscript('test/main').expect +local expect_raw = require('test.expect') function test.internal_in_test() expect.true_(dfhack.internal.IN_TEST)