From 5b12c64cba06cd74a7d05c46c38dd63cc9fd020d Mon Sep 17 00:00:00 2001 From: myk002 Date: Wed, 23 Jun 2021 13:59:39 -0700 Subject: [PATCH 1/8] add new string function: wrap() refactored and improved from the implementation in quickfort's dialog.lua --- library/lua/dfhack.lua | 37 +++++++++++++++++++++++++++++++++++++ test/library/string.lua | 24 ++++++++++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 test/library/string.lua diff --git a/library/lua/dfhack.lua b/library/lua/dfhack.lua index d1e664af7..bca2cc895 100644 --- a/library/lua/dfhack.lua +++ b/library/lua/dfhack.lua @@ -369,6 +369,43 @@ function string:endswith(suffix) return self:sub(-#suffix) == suffix or #suffix == 0 end +-- Inserts newlines into a string so no individual line exceeds the given width. +-- Lines are split at space-separated word boundaries. Any existing newlines are +-- kept in place. If a single word is longer than width, it is split over +-- multiple lines. +function string:wrap(width) + local wrapped_text = {} + for line in self:gmatch('[^\n]*') do + local line_start_pos = 1 + local wrapped_line = line:gsub( + '%s*()(%S+)()', + function(start_pos, word, end_pos) + -- word fits within the current line + if end_pos - line_start_pos <= width then return end + -- word needs to go on the next line, but is not itself longer + -- than the specified width + if #word <= width then + line_start_pos = start_pos + return '\n' .. word + end + -- word is too long to fit on one line and needs to be split up + local num_chars, str = 0, start_pos == 1 and '' or '\n' + repeat + local word_frag = word:sub(num_chars + 1, num_chars + width) + str = str .. word_frag + num_chars = num_chars + #word_frag + if num_chars < #word then + str = str .. '\n' + end + line_start_pos = start_pos + num_chars + until end_pos - line_start_pos <= width + return str .. word:sub(num_chars + 1) + end) + table.insert(wrapped_text, wrapped_line) + end + return table.concat(wrapped_text, '\n') +end + -- String conversions function dfhack.persistent:__tostring() diff --git a/test/library/string.lua b/test/library/string.lua new file mode 100644 index 000000000..fdb278d40 --- /dev/null +++ b/test/library/string.lua @@ -0,0 +1,24 @@ +-- tests string functions added by dfhack.lua + +function test.startswith() + expect.true_(('abcd'):startswith('')) + expect.true_(('abcd'):startswith('abc')) + expect.false_(('abcd'):startswith('bcd')) +end + +function test.endswith() + expect.true_(('abcd'):endswith('')) + expect.true_(('abcd'):endswith('bcd')) + expect.false_(('abcd'):endswith('abc')) +end + +function test.wrap() + expect.eq('hello world', ('hello world'):wrap(20)) + expect.eq('hello world', ('hello world'):wrap(20)) + expect.eq('hello world\nhow are you?',('hello world how are you?'):wrap(12)) + expect.eq('hello\nworld', ('hello world'):wrap(5)) + expect.eq('hello\nworld', ('hello world'):wrap(5)) + expect.eq('hello\nworld', ('hello world'):wrap(8)) + expect.eq('hel\nlo\nwor\nld', ('hello world'):wrap(3)) + expect.eq('hel\nloo\nwor\nldo', ('helloo worldo'):wrap(3)) +end From 3ca80c271dd22cec1d2bcbd2f6b4bbf84a33907d Mon Sep 17 00:00:00 2001 From: myk002 Date: Wed, 23 Jun 2021 14:01:23 -0700 Subject: [PATCH 2/8] update changelog --- docs/changelog.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/changelog.txt b/docs/changelog.txt index ac57e53aa..2d61b593f 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -51,6 +51,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - `embark-assistant`: slightly improved performance of surveying and improved code a little ## Lua +- new string utility function: ``string:wrap(width)`` wraps a string at space-separated word boundaries - ``gui.Painter``: fixed error when calling ``viewport()`` method - `reveal`: now exposes ``unhideFlood(pos)`` functionality to Lua - ``utils.processArgsGetopt()``: now returns negative numbers (e.g. ``-10``) in the list of positional parameters instead of treating it as an option string equivalent to ``-1 -0`` From 67840c033c167cff97b0730fa31b5a3341c8ffa1 Mon Sep 17 00:00:00 2001 From: myk002 Date: Wed, 23 Jun 2021 14:04:16 -0700 Subject: [PATCH 3/8] add quotes around printed unequal values so you can see confusing hidden trailing spaces --- library/lua/test_util/expect.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/lua/test_util/expect.lua b/library/lua/test_util/expect.lua index deca8c44f..e52b7ef9e 100644 --- a/library/lua/test_util/expect.lua +++ b/library/lua/test_util/expect.lua @@ -24,7 +24,7 @@ function expect.nil_(value, comment) end function expect.eq(a, b, comment) - return a == b, comment, ('%s ~= %s'):format(a, b) + return a == b, comment, ('"%s" ~= "%s"'):format(a, b) end function expect.ne(a, b, comment) From 9c7cb473ce9bc61030360dabcb21a993f6bffffa Mon Sep 17 00:00:00 2001 From: myk002 Date: Wed, 23 Jun 2021 14:53:02 -0700 Subject: [PATCH 4/8] add delay_until test function --- ci/test.lua | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/ci/test.lua b/ci/test.lua index 07d297e3d..eb077767f 100644 --- a/ci/test.lua +++ b/ci/test.lua @@ -83,6 +83,19 @@ local function delay(frames) script.sleep(frames, 'frames') end +-- Will call the predicate_fn every frame until it returns true. If it fails to +-- return true before timeout_frames have elapsed, throws an error. If +-- timeout_frames is not specified, defaults to 100. +local function delay_until(predicate_fn, timeout_frames) + timeout_frames = tonumber(timeout_frames) or 100 + repeat + delay() + if predicate_fn() then return end + timeout_frames = timeout_frames - 1 + until timeout_frames < 0 + error('timed out while waiting for predicate to return true') +end + 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 @@ -275,6 +288,7 @@ local function build_test_env() expect = {}, mock = mock, delay = delay, + delay_until = delay_until, require = clean_require, reqscript = clean_reqscript, } From 7793adb888ea90c72870ee4851261f75ef975c4f Mon Sep 17 00:00:00 2001 From: myk002 Date: Wed, 23 Jun 2021 14:55:42 -0700 Subject: [PATCH 5/8] update changelog --- docs/changelog.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/changelog.txt b/docs/changelog.txt index ac57e53aa..1690f8a25 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -69,6 +69,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - The ``test/main`` command to invoke the test harness has been renamed to just ``test`` - DFHack unit tests must now match any output expected to be printed via ``dfhack.printerr()`` - Fortress mode is now supported for unit tests (allowing tests that require a fortress map to be loaded) - note that these tests are skipped by continuous integration for now, pending a suitable test fortress +- Unit tests can now use ``delay_until(predicate_fn, timeout_frames)`` to delay until a condition is met # 0.47.05-r1 From bc4e00b24293118c6a8353d01f60deb4bdec9a7e Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 24 Jun 2021 00:39:03 -0400 Subject: [PATCH 6/8] Print more complete tracebacks on test check failures Any library functions written in Lua, like `dfhack.with_finalize()`, would cause the traceback to end even if there were more stack frames in the test file. --- ci/test.lua | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/ci/test.lua b/ci/test.lua index 07d297e3d..085913bd9 100644 --- a/ci/test.lua +++ b/ci/test.lua @@ -245,7 +245,6 @@ local function wrap_expect(func, private) orig_printerr('Check failed! ' .. (msg or '(no message)')) -- Generate a stack trace with all function calls in the same file as the caller to expect.*() -- (this produces better stack traces when using helpers in tests) - -- Skip any frames corresponding to C calls, which could be pcall() / with_finalize() local frame = 2 local caller_src while true do @@ -254,10 +253,9 @@ local function wrap_expect(func, private) if not caller_src then caller_src = info.short_src end - if info.what == 'Lua' then - if info.short_src ~= caller_src then - break - end + -- Skip any frames corresponding to C calls, or Lua functions defined in another file + -- these could include pcall(), with_finalize(), etc. + if info.what == 'Lua' and info.short_src == caller_src then orig_printerr((' at %s:%d'):format(info.short_src, info.currentline)) end frame = frame + 1 From 0297a5b1b8ea5492c99cbd214b37dcfd97f16bbe Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 24 Jun 2021 00:42:50 -0400 Subject: [PATCH 7/8] Check some more cases, especially empty strings --- test/library/string.lua | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test/library/string.lua b/test/library/string.lua index fdb278d40..58bd2d6e4 100644 --- a/test/library/string.lua +++ b/test/library/string.lua @@ -4,12 +4,20 @@ function test.startswith() expect.true_(('abcd'):startswith('')) expect.true_(('abcd'):startswith('abc')) expect.false_(('abcd'):startswith('bcd')) + expect.false_(('abcd'):startswith('abcde')) + + expect.true_((''):startswith('')) + expect.false_((''):startswith('a')) end function test.endswith() expect.true_(('abcd'):endswith('')) expect.true_(('abcd'):endswith('bcd')) expect.false_(('abcd'):endswith('abc')) + expect.false_(('abcd'):endswith('zabcd')) + + expect.true_((''):endswith('')) + expect.false_((''):endswith('a')) end function test.wrap() @@ -21,4 +29,5 @@ function test.wrap() expect.eq('hello\nworld', ('hello world'):wrap(8)) expect.eq('hel\nlo\nwor\nld', ('hello world'):wrap(3)) expect.eq('hel\nloo\nwor\nldo', ('helloo worldo'):wrap(3)) + expect.eq('', (''):wrap(10)) end From 1b167e7898af41d5c99c2f20414592b0d5fc6798 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 24 Jun 2021 01:15:15 -0400 Subject: [PATCH 8/8] Also add quotes in expect.ne() Extension of #1886 --- library/lua/test_util/expect.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/lua/test_util/expect.lua b/library/lua/test_util/expect.lua index e52b7ef9e..9a8d16903 100644 --- a/library/lua/test_util/expect.lua +++ b/library/lua/test_util/expect.lua @@ -28,7 +28,7 @@ function expect.eq(a, b, comment) end function expect.ne(a, b, comment) - return a ~= b, comment, ('%s == %s'):format(a, b) + return a ~= b, comment, ('"%s" == "%s"'):format(a, b) end function expect.lt(a, b, comment)