From b3022898649107ca67dc4448b5a9842756a535f2 Mon Sep 17 00:00:00 2001 From: myk002 Date: Sun, 28 Feb 2021 22:31:44 -0800 Subject: [PATCH 01/10] prep test/main.lua for running live unit tests - make test config controllable via commandline params - enable filtering by mode - fix test filter code (it would misbehave if multiple filters were specified) - allow done_command to have multiple tokens (before it could only be one word) - remove preemptive check for the title screen. it is still checked when a test actually requires that the game is on the title screen --- test/main.lua | 74 +++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 63 insertions(+), 11 deletions(-) diff --git a/test/main.lua b/test/main.lua index be3993fb4..fc61442fa 100644 --- a/test/main.lua +++ b/test/main.lua @@ -2,8 +2,41 @@ local json = require 'json' local script = require 'gui.script' local utils = require 'utils' -local args = {...} -local done_command = args[1] +local help_text = +[[ +Run DFHack tests. + +Usage: + test/main [] + +Options: + -h, --help display this help message and exit. + -n, --nocache don't skip tests marked as completed in test_status.json. + -m, --modes only run tests in the given comma separated list of modes. + valid modes are 'none' and 'title'. if not specified, no + modes are filtered. + -t, --tests only run tests that match one of the comma separated list of + patterns. if not specified, no tests are filtered. + +Examples: + test/main runs all tests that haven't been run before + test/main -n reruns all tests + test/main -nm none reruns tests that don't need the game to be in a + specific mode + test/main -nt quickfort reruns quickfort tests +]] + +local help, nocache, mode_filter, test_filter = false, false, {}, {} +local done_command = utils.processArgsGetopt({...}, { + {'h', 'help', handler=function() help = true end}, + {'n', 'nocache', handler=function() nocache = true end}, + {'m', 'modes', hasArg=true, + handler=function(arg) mode_filter = arg:split(',') end}, + {'t', 'tests', hasArg=true, + handler=function(arg) test_filter = arg:split(',') end}, + }) + +if help then print(help_text) return end local CONFIG_FILE = 'test_config.json' local STATUS_FILE = 'test_status.json' @@ -152,6 +185,10 @@ function load_test_config(config_file) error('Invalid test folder: ' .. config.test_dir) end + -- override config with any params specified on the commandline + if #mode_filter > 0 then config.modes = mode_filter end + if #test_filter > 0 then config.tests = test_filter end + return config end @@ -207,7 +244,7 @@ function get_test_files(test_dir) end function load_test_status() - if dfhack.filesystem.isfile(STATUS_FILE) then + if not nocache and dfhack.filesystem.isfile(STATUS_FILE) then return json.decode_file(STATUS_FILE) end end @@ -218,7 +255,7 @@ end function finish_tests() dfhack.internal.IN_TEST = false - if done_command then + if #done_command > 0 then dfhack.run_command(done_command) end end @@ -305,8 +342,6 @@ function main() } local passed = true - ensure_title_screen() - print('Loading tests') local tests = {} for _, file in ipairs(files) do @@ -316,15 +351,32 @@ function main() end end - print('Filtering tests') - if config.tests then + if config.tests or config.modes then + print('Filtering tests') local orig_length = #tests for i = #tests, 1, -1 do - for _, pattern in pairs(config.tests) do - if not tests[i].name:match(pattern) then - table.remove(tests, i) + local remove = false + if config.modes then + remove = true + -- allow test if it matches any of the given modes + for _, mode in pairs(config.modes) do + if tests[i].config.mode == mode then + remove = false + break + end + end + end + if config.tests and not remove then + remove = true + -- allow test if it matches any of the given patterns + for _, pattern in pairs(config.tests) do + if tests[i].name:match(pattern) then + remove = false + break + end end end + if remove then table.remove(tests, i) end end print('Selected tests: ' .. #tests .. '/' .. orig_length) end From f176310bcdc52e134c00c067bd827b7b12a49543 Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 5 Mar 2021 11:31:40 -0800 Subject: [PATCH 02/10] make table_eq check recursive equality and add tests. to write test the table_eq function itself, I expose it in the text env via expect_raw. if we don't want to do this, alternatives could be: 1. add the test to main.lua itself 2. expose the expect.table_eq function via a test_hooks variable. then test.lua could require main.lua and access the function via the hook. may need to update main.lua a bit to ensure it does not run when it is "require"d --- test/main.lua | 49 +++++++++++++++++++++++++++++++++++++++++++------ test/test.lua | 28 ++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 6 deletions(-) diff --git a/test/main.lua b/test/main.lua index fc61442fa..46e19aba3 100644 --- a/test/main.lua +++ b/test/main.lua @@ -79,21 +79,57 @@ end function expect.ge(a, b, comment) return a >= b, comment, ('%s < %s'):format(a, b) end -function expect.table_eq(a, b, comment) +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 a[k] ~= b[k] then - return false, comment, ('key "%s": %s ~= %s'):format(k, a[k], b[k]) + 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 - return false, comment, ('key "%s": %s ~= %s'):format(k, a[k], b[k]) + 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' or type(b) ~= 'table' then + return false, comment, 'both operands to table_eq must be tables' + 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 @@ -199,6 +235,7 @@ function build_test_env() mode = 'none', }, expect = {}, + expect_raw = expect, delay = delay, require = clean_require, } @@ -342,7 +379,7 @@ function main() } local passed = true - print('Loading tests') + print('Loading tests from ' .. config.test_dir) local tests = {} for _, file in ipairs(files) do if not load_tests(file, tests) then diff --git a/test/test.lua b/test/test.lua index 11f038d66..6b188e766 100644 --- a/test/test.lua +++ b/test/test.lua @@ -1,3 +1,31 @@ function test.internal_in_test() expect.true_(dfhack.internal.IN_TEST) end + +function test.table_eq() + expect.true_(expect_raw.table_eq({}, {})) + expect.true_(expect_raw.table_eq({'a'}, {'a'})) + expect.true_(expect_raw.table_eq({{'a', k='val'}, 'b'}, + {{'a', k='val'}, 'b'})) + + expect.false_(expect_raw.table_eq({}, {''})) + expect.false_(expect_raw.table_eq({''}, {})) + expect.false_(expect_raw.table_eq({'a', {}}, {'a'})) + expect.false_(expect_raw.table_eq({{'a', k='val1'}, 'b'}, + {{'a', k='val2'}, 'b'})) + + local tab = {a='a', b='b'} + expect.true_(expect_raw.table_eq(tab, tab)) + expect.true_(expect_raw.table_eq({tab}, {tab})) + + local tab1, tab2 = {'a'}, {'a'} + tab1.self, tab2.self = tab1, tab2 + expect.true_(expect_raw.table_eq(tab1, tab2)) + + tab1.other, tab2.other = tab2, tab1 + expect.true_(expect_raw.table_eq(tab1, tab2)) + + local tabA, tabB, tabC, tabD = {k='a'}, {k='a'}, {k='a'}, {k='a'} + tabA.next, tabB.next, tabC.next, tabD.next = tabB, tabC, tabD, tabA + expect.true_(expect_raw.table_eq(tabA, tabB)) +end From 6a95308123a3af5df321c427baa7939888d732b5 Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 5 Mar 2021 19:47:24 -0800 Subject: [PATCH 03/10] support setting test_dir on the commandline --- test/main.lua | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/test/main.lua b/test/main.lua index 46e19aba3..142b7301e 100644 --- a/test/main.lua +++ b/test/main.lua @@ -12,6 +12,8 @@ Usage: Options: -h, --help display this help message and exit. -n, --nocache don't skip tests marked as completed in test_status.json. + -d, --test_dir specifies which directory to look in for tests. defaults to + the "hack/scripts/test" folder in your DF installation. -m, --modes only run tests in the given comma separated list of modes. valid modes are 'none' and 'title'. if not specified, no modes are filtered. @@ -24,12 +26,18 @@ Examples: test/main -nm none reruns tests that don't need the game to be in a specific mode test/main -nt quickfort reruns quickfort tests + test/main -nd /path/to/dfhack-scripts/repo/test + runs tests in your in-development branch of the + scripts repo ]] -local help, nocache, mode_filter, test_filter = false, false, {}, {} +local help, nocache, test_dir, mode_filter, test_filter = + false, false, nil, {}, {} local done_command = utils.processArgsGetopt({...}, { {'h', 'help', handler=function() help = true end}, {'n', 'nocache', handler=function() nocache = true end}, + {'d', 'test_dir', hasArg=true, + handler=function(arg) mode_filter = arg:split(',') end}, {'m', 'modes', hasArg=true, handler=function(arg) mode_filter = arg:split(',') end}, {'t', 'tests', hasArg=true, @@ -214,6 +222,11 @@ function load_test_config(config_file) config = json.decode_file(config_file) end + -- override config with any params specified on the commandline + if test_dir then config.test_dir = test_dir end + if #mode_filter > 0 then config.modes = mode_filter end + if #test_filter > 0 then config.tests = test_filter end + if not config.test_dir then config.test_dir = dfhack.getHackPath() .. 'scripts/test' end @@ -221,10 +234,6 @@ function load_test_config(config_file) error('Invalid test folder: ' .. config.test_dir) end - -- override config with any params specified on the commandline - if #mode_filter > 0 then config.modes = mode_filter end - if #test_filter > 0 then config.tests = test_filter end - return config end From 4779dd678d90645d3c0186793d440d5c4c50b2b8 Mon Sep 17 00:00:00 2001 From: myk002 Date: Sat, 6 Mar 2021 05:02:05 -0800 Subject: [PATCH 04/10] fix test_dir param and allow userdata in table_eq --- test/main.lua | 7 ++++--- test/test.lua | 3 +++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/test/main.lua b/test/main.lua index 142b7301e..7ff0f0eee 100644 --- a/test/main.lua +++ b/test/main.lua @@ -37,7 +37,7 @@ local done_command = utils.processArgsGetopt({...}, { {'h', 'help', handler=function() help = true end}, {'n', 'nocache', handler=function() nocache = true end}, {'d', 'test_dir', hasArg=true, - handler=function(arg) mode_filter = arg:split(',') end}, + handler=function(arg) test_dir = arg end}, {'m', 'modes', hasArg=true, handler=function(arg) mode_filter = arg:split(',') end}, {'t', 'tests', hasArg=true, @@ -127,8 +127,9 @@ local function table_eq_recurse(a, b, keys, known_eq) return true end function expect.table_eq(a, b, comment) - if type(a) ~= 'table' or type(b) ~= 'table' then - return false, comment, 'both operands to table_eq must be tables' + 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) diff --git a/test/test.lua b/test/test.lua index 6b188e766..faaaf3265 100644 --- a/test/test.lua +++ b/test/test.lua @@ -8,6 +8,9 @@ function test.table_eq() expect.true_(expect_raw.table_eq({{'a', k='val'}, 'b'}, {{'a', k='val'}, 'b'})) + expect.false_(expect_raw.table_eq(nil, nil)) -- operands must be non-nil + expect.false_(expect_raw.table_eq({}, nil)) + expect.false_(expect_raw.table_eq(nil, {})) expect.false_(expect_raw.table_eq({}, {''})) expect.false_(expect_raw.table_eq({''}, {})) expect.false_(expect_raw.table_eq({'a', {}}, {'a'})) From 3ff3457c105013da669f04d1f13f7134a215998a Mon Sep 17 00:00:00 2001 From: myk002 Date: Sat, 6 Mar 2021 22:07:45 -0800 Subject: [PATCH 05/10] include main test file as a module for testing rewrite main.lua so it can be included as a module rename test.lua to test_test.lua since I plan to rename main.lua to test.lua in a future PR --- test/main.lua | 163 ++++++++++++++++++------------- test/{test.lua => test_test.lua} | 2 + 2 files changed, 98 insertions(+), 67 deletions(-) rename test/{test.lua => test_test.lua} (96%) diff --git a/test/main.lua b/test/main.lua index 7ff0f0eee..2fd2abb66 100644 --- a/test/main.lua +++ b/test/main.lua @@ -1,9 +1,16 @@ +-- DFHack developer test harness +--@ module = true + local json = require 'json' local script = require 'gui.script' local utils = require 'utils' local help_text = -[[ +[====[ + +test/main +========= + Run DFHack tests. Usage: @@ -29,22 +36,7 @@ Examples: test/main -nd /path/to/dfhack-scripts/repo/test runs tests in your in-development branch of the scripts repo -]] - -local help, nocache, test_dir, mode_filter, test_filter = - false, false, nil, {}, {} -local done_command = utils.processArgsGetopt({...}, { - {'h', 'help', handler=function() help = true end}, - {'n', 'nocache', handler=function() nocache = true end}, - {'d', 'test_dir', hasArg=true, - handler=function(arg) test_dir = arg end}, - {'m', 'modes', hasArg=true, - handler=function(arg) mode_filter = arg:split(',') end}, - {'t', 'tests', hasArg=true, - handler=function(arg) test_filter = arg:split(',') end}, - }) - -if help then print(help_text) return end +]====] local CONFIG_FILE = 'test_config.json' local STATUS_FILE = 'test_status.json' @@ -177,12 +169,12 @@ function expect.not_pairs_contains(table, key, comment) return true end -function delay(frames) +local function delay(frames) frames = frames or 1 script.sleep(frames, 'frames') end -function clean_require(module) +local function clean_require(module) -- wrapper around require() - forces a clean load of every module to ensure -- that modules checking for dfhack.internal.IN_TEST at load time behave -- properly @@ -192,7 +184,7 @@ function clean_require(module) return require(module) end -function ensure_title_screen() +local function ensure_title_screen() if df.viewscreen_titlest:is_instance(dfhack.gui.getCurViewscreen()) then return end @@ -217,35 +209,26 @@ local MODE_NAVIGATE_FNS = { title = ensure_title_screen, } -function load_test_config(config_file) +local function load_test_config(config_file) local config = {} if dfhack.filesystem.isfile(config_file) then config = json.decode_file(config_file) end - -- override config with any params specified on the commandline - if test_dir then config.test_dir = test_dir end - if #mode_filter > 0 then config.modes = mode_filter end - if #test_filter > 0 then config.tests = test_filter end - if not config.test_dir then config.test_dir = dfhack.getHackPath() .. 'scripts/test' end - if not dfhack.filesystem.isdir(config.test_dir) then - error('Invalid test folder: ' .. config.test_dir) - end return config end -function build_test_env() +local function build_test_env() local env = { test = utils.OrderedTable(), config = { mode = 'none', }, expect = {}, - expect_raw = expect, delay = delay, require = clean_require, } @@ -279,8 +262,9 @@ function build_test_env() return env, private end -function get_test_files(test_dir) +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 table.insert(files, entry.path) @@ -290,25 +274,25 @@ function get_test_files(test_dir) return files end -function load_test_status() - if not nocache and dfhack.filesystem.isfile(STATUS_FILE) then +local function load_test_status() + if dfhack.filesystem.isfile(STATUS_FILE) then return json.decode_file(STATUS_FILE) end end -function save_test_status(status) +local function save_test_status(status) json.encode_file(status, STATUS_FILE) end -function finish_tests() +local function finish_tests(done_command) dfhack.internal.IN_TEST = false if #done_command > 0 then dfhack.run_command(done_command) end end -function load_tests(file, tests) - local short_filename = file:sub(file:find('test'), -1) +local function load_tests(file, tests) + local short_filename = file:sub((file:find('test') or -4)+5, -1) print('Loading file: ' .. short_filename) local env, env_private = build_test_env() local code, err = loadfile(file, 't', env) @@ -342,7 +326,7 @@ function load_tests(file, tests) return true end -function sort_tests(tests) +local function sort_tests(tests) -- to make sort stable local test_index = utils.invert(tests) table.sort(tests, function(a, b) @@ -354,7 +338,7 @@ function sort_tests(tests) end) end -function run_test(test, status, counts) +local function run_test(test, status, counts) test.private.checks = 0 test.private.checks_ok = 0 counts.tests = counts.tests + 1 @@ -376,28 +360,17 @@ function run_test(test, status, counts) return passed end -function main() - local config = load_test_config(CONFIG_FILE) - local files = get_test_files(config.test_dir) - - local counts = { - tests = 0, - tests_ok = 0, - checks = 0, - checks_ok = 0, - file_errors = 0, - } - local passed = true - - print('Loading tests from ' .. config.test_dir) +local function get_tests(test_files, counts) local tests = {} - for _, file in ipairs(files) do + for _, file in ipairs(test_files) do if not load_tests(file, tests) then - passed = false counts.file_errors = counts.file_errors + 1 end end + return tests +end +local function filter_tests(tests, config) if config.tests or config.modes then print('Filtering tests') local orig_length = #tests @@ -427,18 +400,27 @@ function main() end print('Selected tests: ' .. #tests .. '/' .. orig_length) end - local status = load_test_status() or {} - for i = #tests, 1, -1 do - local test = tests[i] - if not status[test.full_name] then - status[test.full_name] = TestStatus.PENDING - elseif status[test.full_name] ~= TestStatus.PENDING then - print('skipping test: ' .. test.name .. ': state = ' .. status[test.full_name] .. ')') - table.remove(tests, i) + + local status = {} + if not config.nocache then + status = load_test_status() or status + for i = #tests, 1, -1 do + local test = tests[i] + if not status[test.full_name] then + status[test.full_name] = TestStatus.PENDING + elseif status[test.full_name] ~= TestStatus.PENDING then + print(('skipping test: %s: state = %s)'):format( + test.name, status[test.full_name])) + table.remove(tests, i) + end end end + sort_tests(tests) + return status +end +local function run_tests(tests, status, counts) print('Running ' .. #tests .. ' tests') for _, test in pairs(tests) do MODE_NAVIGATE_FNS[test.config.mode]() @@ -453,6 +435,53 @@ function main() print(('%d test files failed to load'):format(counts.file_errors)) end -script.start(function() - dfhack.with_finalize(finish_tests, main) -end) +local function main(args) + local help, nocache, test_dir, mode_filter, test_filter = + false, false, nil, {}, {} + local done_command = utils.processArgsGetopt(args, { + {'h', 'help', handler=function() help = true end}, + {'n', 'nocache', handler=function() nocache = true end}, + {'d', 'test_dir', hasArg=true, + handler=function(arg) test_dir = arg end}, + {'m', 'modes', hasArg=true, + handler=function(arg) mode_filter = arg:split(',') end}, + {'t', 'tests', hasArg=true, + handler=function(arg) test_filter = arg:split(',') end}, + }) + + if help then print(help_text) return end + + local config = load_test_config(CONFIG_FILE) + + -- override config with any params specified on the commandline + if test_dir then config.test_dir = test_dir end + if nocache then config.nocache = true end + if #mode_filter > 0 then config.modes = mode_filter end + if #test_filter > 0 then config.tests = test_filter end + + if not dfhack.filesystem.isdir(config.test_dir) then + qerror(('Invalid test folder: "%s"'):format(config.test_dir)) + end + + local counts = { + tests = 0, + tests_ok = 0, + checks = 0, + checks_ok = 0, + file_errors = 0, + } + + local test_files = get_test_files(config.test_dir) + local tests = get_tests(test_files, counts) + local status = filter_tests(tests, config) + + script.start(function() + dfhack.call_with_finalizer(1, true, + finish_tests, done_command, + run_tests, tests, status, counts) + end) +end + +if not dfhack_flags.module then + main({...}) +end diff --git a/test/test.lua b/test/test_test.lua similarity index 96% rename from test/test.lua rename to test/test_test.lua index faaaf3265..4543375e3 100644 --- a/test/test.lua +++ b/test/test_test.lua @@ -1,3 +1,5 @@ +local expect_raw = reqscript('test/main').expect + function test.internal_in_test() expect.true_(dfhack.internal.IN_TEST) end From 409bfea63e8e45393aa5f737f416532da06c44e8 Mon Sep 17 00:00:00 2001 From: myk002 Date: Sun, 7 Mar 2021 02:02:15 -0800 Subject: [PATCH 06/10] update run-tests.py with new done_command syntax --- 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 f4c173af2..858e86c3b 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 "lua scr.breakdown_level=df.interface_breakdown_types.%s" + test/main lua scr.breakdown_level=df.interface_breakdown_types.%s ''' % ('NONE' if args.no_quit else 'QUIT')) test_config_file = 'test_config.json' From dc5fe71caf186541ec5f2c481ea6015aa812fa4e Mon Sep 17 00:00:00 2001 From: myk002 Date: Sun, 7 Mar 2021 02:51:29 -0800 Subject: [PATCH 07/10] add more help and improve compatibility - explain modes, done command, and test_config.json file format - also be more flexible with how the done command is formatted (can now be passed as either separate tokens or as a single quoted string) - print the filter values when filtering tests --- test/main.lua | 37 +++++++++++++++++++++++++++++++------ travis/run-tests.py | 2 +- 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/test/main.lua b/test/main.lua index 2fd2abb66..d727eda94 100644 --- a/test/main.lua +++ b/test/main.lua @@ -14,20 +14,26 @@ test/main Run DFHack tests. Usage: - test/main [] + + test/main [] [] + +If a post-test command is specified, it will be run after the tests complete. Options: + -h, --help display this help message and exit. -n, --nocache don't skip tests marked as completed in test_status.json. -d, --test_dir specifies which directory to look in for tests. defaults to the "hack/scripts/test" folder in your DF installation. -m, --modes only run tests in the given comma separated list of modes. - valid modes are 'none' and 'title'. if not specified, no - modes are filtered. + valid modes are 'none' (test can be run on any screen) and + 'title' (test must be run on the DF title screen). if not + specified, no modes are filtered. -t, --tests only run tests that match one of the comma separated list of patterns. if not specified, no tests are filtered. Examples: + test/main runs all tests that haven't been run before test/main -n reruns all tests test/main -nm none reruns tests that don't need the game to be in a @@ -36,6 +42,17 @@ Examples: test/main -nd /path/to/dfhack-scripts/repo/test runs tests in your in-development branch of the 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 +arrays. For example: + + { + "test_dir": "/home/myk/src/dfhack-scripts/test", + "nocache": true, + "tests": [ "quickfort" ] + } + ]====] local CONFIG_FILE = 'test_config.json' @@ -372,7 +389,14 @@ end local function filter_tests(tests, config) if config.tests or config.modes then - print('Filtering tests') + for _,filter in ipairs({{'tests', 'name pattern'},{'modes', 'mode'}}) do + if config[filter[1]] and #config[filter[1]] > 0 then + print(string.format('Filtering tests by %s:', filter[2])) + for _,v in ipairs(config[filter[1]]) do + print(string.format(' %s', v)) + end + end + end local orig_length = #tests for i = #tests, 1, -1 do local remove = false @@ -421,7 +445,7 @@ local function filter_tests(tests, config) end local function run_tests(tests, status, counts) - print('Running ' .. #tests .. ' tests') + print(('Running %d tests'):format(#tests)) for _, test in pairs(tests) do MODE_NAVIGATE_FNS[test.config.mode]() local passed = run_test(test, status, counts) @@ -438,7 +462,7 @@ end local function main(args) local help, nocache, test_dir, mode_filter, test_filter = false, false, nil, {}, {} - local done_command = utils.processArgsGetopt(args, { + local other_args = utils.processArgsGetopt(args, { {'h', 'help', handler=function() help = true end}, {'n', 'nocache', handler=function() nocache = true end}, {'d', 'test_dir', hasArg=true, @@ -474,6 +498,7 @@ local function main(args) local test_files = get_test_files(config.test_dir) local tests = get_tests(test_files, counts) local status = filter_tests(tests, config) + local done_command = table.concat(other_args, ' ') script.start(function() dfhack.call_with_finalizer(1, true, diff --git a/travis/run-tests.py b/travis/run-tests.py index 858e86c3b..f4c173af2 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 lua scr.breakdown_level=df.interface_breakdown_types.%s + test/main "lua scr.breakdown_level=df.interface_breakdown_types.%s" ''' % ('NONE' if args.no_quit else 'QUIT')) test_config_file = 'test_config.json' From 98b67e170de13d8227b4abf84a230764ddc00a3f Mon Sep 17 00:00:00 2001 From: myk002 Date: Sun, 7 Mar 2021 08:51:02 -0800 Subject: [PATCH 08/10] allow done_command to be set in test_config.json --- test/main.lua | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/test/main.lua b/test/main.lua index d727eda94..be942a3f8 100644 --- a/test/main.lua +++ b/test/main.lua @@ -15,16 +15,16 @@ Run DFHack tests. Usage: - test/main [] [] + test/main [] [] -If a post-test command is specified, it will be run after the tests complete. +If a done_command is specified, it will be run after the tests complete. Options: -h, --help display this help message and exit. - -n, --nocache don't skip tests marked as completed in test_status.json. -d, --test_dir specifies which directory to look in for tests. defaults to the "hack/scripts/test" folder in your DF installation. + -n, --nocache don't skip tests marked as completed in test_status.json. -m, --modes only run tests in the given comma separated list of modes. valid modes are 'none' (test can be run on any screen) and 'title' (test must be run on the DF title screen). if not @@ -50,7 +50,9 @@ arrays. For example: { "test_dir": "/home/myk/src/dfhack-scripts/test", "nocache": true, - "tests": [ "quickfort" ] + "modes": [ "none" ], + "tests": [ "quickfort", "devel" ], + "done_command": "devel/luacov" } ]====] @@ -464,9 +466,9 @@ local function main(args) false, false, nil, {}, {} local other_args = utils.processArgsGetopt(args, { {'h', 'help', handler=function() help = true end}, - {'n', 'nocache', handler=function() nocache = true end}, {'d', 'test_dir', hasArg=true, handler=function(arg) test_dir = arg end}, + {'n', 'nocache', handler=function() nocache = true end}, {'m', 'modes', hasArg=true, handler=function(arg) mode_filter = arg:split(',') end}, {'t', 'tests', hasArg=true, @@ -475,6 +477,7 @@ local function main(args) if help then print(help_text) return end + local done_command = table.concat(other_args, ' ') local config = load_test_config(CONFIG_FILE) -- override config with any params specified on the commandline @@ -482,6 +485,7 @@ local function main(args) if nocache then config.nocache = true end if #mode_filter > 0 then config.modes = mode_filter end if #test_filter > 0 then config.tests = test_filter end + if #done_command > 0 then config.done_command = done_command end if not dfhack.filesystem.isdir(config.test_dir) then qerror(('Invalid test folder: "%s"'):format(config.test_dir)) @@ -498,11 +502,10 @@ local function main(args) local test_files = get_test_files(config.test_dir) local tests = get_tests(test_files, counts) local status = filter_tests(tests, config) - local done_command = table.concat(other_args, ' ') script.start(function() dfhack.call_with_finalizer(1, true, - finish_tests, done_command, + finish_tests, config.done_command, run_tests, tests, status, counts) end) end From ed21ed85936df9ce6a6b189c00be083e79bf7fc8 Mon Sep 17 00:00:00 2001 From: myk002 Date: Sun, 7 Mar 2021 08:51:33 -0800 Subject: [PATCH 09/10] update changelog --- docs/changelog.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/changelog.txt b/docs/changelog.txt index cece75f72..ee509e9df 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -36,6 +36,9 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## Documentation - Added more client library implementations to the `remote interface docs ` +## 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. + # 0.47.05-r1 ## Fixes From 403ba2b8ea08e45c0fa81b4b69312e3101bd7e0e Mon Sep 17 00:00:00 2001 From: myk002 Date: Sun, 7 Mar 2021 12:06:27 -0800 Subject: [PATCH 10/10] work when there is no done_command --- test/main.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/main.lua b/test/main.lua index be942a3f8..203c1d833 100644 --- a/test/main.lua +++ b/test/main.lua @@ -305,7 +305,7 @@ end local function finish_tests(done_command) dfhack.internal.IN_TEST = false - if #done_command > 0 then + if done_command and #done_command > 0 then dfhack.run_command(done_command) end end