[Tarantool-patches] [PATCH luajit 2/5] test: stop using utils.selfrun in tests

Igor Munkin imun at tarantool.org
Mon Feb 27 12:07:20 MSK 2023


Unfortunately, <utils.selfrun> is too complex to be maintained, so the
corresponding tests are split into two files: the test itself and the
script to be run by the test. As a result of the patch <utils.makecmd>
helper is introduced: it inherits some approaches from <utils.selfrun>,
but it's considered for more general use.

Signed-off-by: Igor Munkin <imun at tarantool.org>
---
 .../gh-4427-ffi-sandwich.test.lua             | 88 +++++++++++--------
 .../gh-4427-ffi-sandwich/script.lua           | 25 ++++++
 .../lj-351-print-tostring-number.test.lua     | 34 +++----
 .../lj-351-print-tostring-number/script.lua   |  9 ++
 .../lj-586-debug-non-string-error.test.lua    |  2 +-
 .../lj-flush-on-trace.test.lua                | 87 ++++++++++--------
 .../lj-flush-on-trace/script.lua              | 23 +++++
 test/tarantool-tests/utils.lua                | 80 +++++++----------
 8 files changed, 200 insertions(+), 148 deletions(-)
 create mode 100644 test/tarantool-tests/gh-4427-ffi-sandwich/script.lua
 create mode 100644 test/tarantool-tests/lj-351-print-tostring-number/script.lua
 create mode 100644 test/tarantool-tests/lj-flush-on-trace/script.lua

diff --git a/test/tarantool-tests/gh-4427-ffi-sandwich.test.lua b/test/tarantool-tests/gh-4427-ffi-sandwich.test.lua
index dd02130c..06985dcd 100644
--- a/test/tarantool-tests/gh-4427-ffi-sandwich.test.lua
+++ b/test/tarantool-tests/gh-4427-ffi-sandwich.test.lua
@@ -3,52 +3,62 @@ local utils = require('utils')
 -- Disabled on *BSD due to #4819.
 utils.skipcond(jit.os == 'BSD', 'Disabled due to #4819')
 
-utils.selfrun(arg, {
-  {
-    arg = {
-      1, -- hotloop (arg[1])
-      1, -- trigger (arg[2])
-    },
-    msg = 'Trace is aborted',
-    res = tostring(3), -- hotloop + trigger + 1
-    test = 'is',
-  },
-  {
-    arg = {
-      1, -- hotloop (arg[1])
-      2, -- trigger (arg[2])
-    },
-    msg = 'Trace is recorded',
-    res = 'Lua VM re%-entrancy is detected while executing the trace',
-    test = 'like',
-  },
-})
-
------ Test payload. ----------------------------------------------
-
-local cfg = {
-  hotloop = arg[1] or 1,
-  trigger = arg[2] or 1,
-}
+local tap = require('tap')
 
-local ffi = require('ffi')
-local ffisandwich = ffi.load('libsandwich')
-ffi.cdef('int increment(struct sandwich *state, int i)')
+local test = tap.test('gh-4427-ffi-sandwich')
+test:plan(2)
 
--- Save the current coroutine and set the value to trigger
--- <increment> call the Lua routine instead of C implementation.
-local sandwich = require('libsandwich')(cfg.trigger)
+-- <makecmd> runs %testname%/script.lua by <LUAJIT_TEST_BINARY>
+-- with the given environment, launch options and CLI arguments.
+local script = utils.makecmd(arg, {
+  -- XXX: Apple tries their best to "protect their users from
+  -- malware". As a result SIP (see the link[1] below) has been
+  -- designed and released. Now, Apple developers are so
+  -- protected, that they can load nothing being not installed in
+  -- the system, since the environment is sanitized before the
+  -- child process is launched. In particular, environment
+  -- variables starting with DYLD_ and LD_ are unset for child
+  -- process. For more info, see the docs[2] below.
+  --
+  -- The environment variable below is used by FFI machinery to
+  -- find the proper shared library.
+  --
+  -- luacheck: push no max comment line length
+  --
+  -- [1]: https://support.apple.com/en-us/HT204899
+  -- [2]: https://developer.apple.com/library/archive/documentation/Security/Conceptual/System_Integrity_Protection_Guide/RuntimeProtections/RuntimeProtections.html
+  --
+  -- luacheck: pop
+  env = { DYLD_LIBRARY_PATH = os.getenv('DYLD_LIBRARY_PATH') },
+  redirect = '2>&1',
+})
 
 -- Depending on trigger and hotloop values the following contexts
 -- are possible:
 -- * if trigger <= hotloop -> trace recording is aborted
 -- * if trigger >  hotloop -> trace is recorded but execution
 --   leads to panic
-jit.opt.start("3", string.format("hotloop=%d", cfg.hotloop))
+local hotloop = 1
+local cases = {
+  abort = {
+    trigger = hotloop,
+    expected = '#4427 still works',
+    test = 'is',
+    message = 'Trace is aborted',
+  },
+  panic = {
+    trigger = hotloop + 1,
+    expected = 'Lua VM re%-entrancy is detected while executing the trace',
+    test = 'like',
+    message = 'Trace is compiled',
+  },
+}
 
-local res
-for i = 0, cfg.trigger + cfg.hotloop do
-  res = ffisandwich.increment(sandwich, i)
+for _, subtest in pairs(cases) do
+  local output = script(hotloop, subtest.trigger)
+  -- XXX: explicitly pass <test> as an argument to <testf>
+  -- to emulate test:is(...), test:like(...), etc.
+  test[subtest.test](test, output, subtest.expected, subtest.message)
 end
--- Check the resulting value if panic didn't occur earlier.
-print(res)
+
+os.exit(test:check() and 0 or 1)
diff --git a/test/tarantool-tests/gh-4427-ffi-sandwich/script.lua b/test/tarantool-tests/gh-4427-ffi-sandwich/script.lua
new file mode 100644
index 00000000..9ecd964e
--- /dev/null
+++ b/test/tarantool-tests/gh-4427-ffi-sandwich/script.lua
@@ -0,0 +1,25 @@
+local hotloop = assert(arg[1], 'hotloop argument is missing')
+local trigger = assert(arg[2], 'trigger argument is missing')
+
+local ffi = require('ffi')
+local ffisandwich = ffi.load('libsandwich')
+ffi.cdef('int increment(struct sandwich *state, int i)')
+
+-- Save the current coroutine and set the value to trigger
+-- <increment> call the Lua routine instead of C implementation.
+local sandwich = require('libsandwich')(trigger)
+
+-- Depending on trigger and hotloop values the following contexts
+-- are possible:
+-- * if trigger <= hotloop -> trace recording is aborted
+-- * if trigger >  hotloop -> trace is recorded but execution
+--   leads to panic
+jit.opt.start("3", string.format("hotloop=%d", hotloop))
+
+local res
+for i = 0, hotloop + trigger do
+  res = ffisandwich.increment(sandwich, i)
+end
+-- Check the resulting value if panic didn't occur earlier.
+assert(res == hotloop + trigger + 1, 'res is calculated correctly')
+io.write('#4427 still works')
diff --git a/test/tarantool-tests/lj-351-print-tostring-number.test.lua b/test/tarantool-tests/lj-351-print-tostring-number.test.lua
index da5b31be..72a9ec2b 100644
--- a/test/tarantool-tests/lj-351-print-tostring-number.test.lua
+++ b/test/tarantool-tests/lj-351-print-tostring-number.test.lua
@@ -1,4 +1,9 @@
-local utils = require('utils')
+local tap = require('tap')
+
+local test = tap.test('lj-351-print-tostring-number')
+test:plan(8)
+
+local script = require('utils').makecmd(arg)
 
 local cases = {
   {typename = 'nil', value = 'nil'},
@@ -15,27 +20,10 @@ local cases = {
   {typename = 'cdata', value = '1ULL'}
 }
 
-local checks = {}
-
-for i, case in pairs(cases) do
-  checks[i] = {
-    arg = {('"%s"'):format(case.value), case.typename},
-    msg = ('%s'):format(case.typename),
-    res = ('__tostring is reloaded for %s'):format(case.typename),
-    test = 'is',
-  }
+for _, subtest in pairs(cases) do
+  local output = script(('"%s"'):format(subtest.value), subtest.typename)
+  test:is(output, ('__tostring is reloaded for %s'):format(subtest.typename),
+          ('subtest is OK for %s type'):format(subtest.typename))
 end
 
-utils.selfrun(arg, checks)
-
------ Test payload. ----------------------------------------------
-
-local test = [[
-  local testvar = %s
-  debug.setmetatable(testvar, {__tostring = function(o)
-    return ('__tostring is reloaded for %s'):format(type(o))
-  end})
-  print(testvar)
-]]
-
-pcall(load(test:format(unpack(arg))))
+os.exit(test:check() and 0 or 1)
diff --git a/test/tarantool-tests/lj-351-print-tostring-number/script.lua b/test/tarantool-tests/lj-351-print-tostring-number/script.lua
new file mode 100644
index 00000000..c3066f49
--- /dev/null
+++ b/test/tarantool-tests/lj-351-print-tostring-number/script.lua
@@ -0,0 +1,9 @@
+local test = [[
+  local testvar = %s
+  debug.setmetatable(testvar, {__tostring = function(o)
+    return ('__tostring is reloaded for %s'):format(type(o))
+  end})
+  print(testvar)
+]]
+
+pcall(load(test:format(unpack(arg))))
diff --git a/test/tarantool-tests/lj-586-debug-non-string-error.test.lua b/test/tarantool-tests/lj-586-debug-non-string-error.test.lua
index f02353fe..dcb730a2 100644
--- a/test/tarantool-tests/lj-586-debug-non-string-error.test.lua
+++ b/test/tarantool-tests/lj-586-debug-non-string-error.test.lua
@@ -8,7 +8,7 @@ test:plan(1)
 -- that testing the debug interactive interface always ends with
 -- sending commands to another instance via stdin. However, the
 -- module with test helpers lacks the suitable routine.
--- `utils.selfrun()` doesn't fit for this, since `debug.debug()`
+-- `utils.makecmd()` doesn't fit for this, since `debug.debug()`
 -- captures `io.stdin` and waits at `fgets()` in debug busy loop.
 -- As it's already mentioned, such tests are not usual, so there
 -- is no need to introduce a new helper to utils module (at least
diff --git a/test/tarantool-tests/lj-flush-on-trace.test.lua b/test/tarantool-tests/lj-flush-on-trace.test.lua
index c46b93f0..3351cc5a 100644
--- a/test/tarantool-tests/lj-flush-on-trace.test.lua
+++ b/test/tarantool-tests/lj-flush-on-trace.test.lua
@@ -3,51 +3,62 @@ local utils = require('utils')
 -- Disabled on *BSD due to #4819.
 utils.skipcond(jit.os == 'BSD', 'Disabled due to #4819')
 
-utils.selfrun(arg, {
-  {
-    arg = {
-      1, -- hotloop (arg[1])
-      1, -- trigger (arg[2])
-    },
-    msg = 'Trace is aborted',
-    res = 'OK',
-    test = 'is',
-  },
-  {
-    arg = {
-      1, -- hotloop (arg[1])
-      2, -- trigger (arg[2])
-    },
-    msg = 'Trace is recorded',
-    res = 'JIT mode change is detected while executing the trace',
-    test = 'like',
-  },
-})
-
------ Test payload. ----------------------------------------------
-
-local cfg = {
-  hotloop = arg[1] or 1,
-  trigger = arg[2] or 1,
-}
+local tap = require('tap')
 
-local ffi = require('ffi')
-local ffiflush = ffi.load('libflush')
-ffi.cdef('void flush(struct flush *state, int i)')
+local test = tap.test('lj-flush-on-trace')
+test:plan(2)
 
--- Save the current coroutine and set the value to trigger
--- <flush> call the Lua routine instead of C implementation.
-local flush = require('libflush')(cfg.trigger)
+-- <makecmd> runs %testname%/script.lua by <LUAJIT_TEST_BINARY>
+-- with the given environment, launch options and CLI arguments.
+local script = utils.makecmd(arg, {
+  -- XXX: Apple tries their best to "protect their users from
+  -- malware". As a result SIP (see the link[1] below) has been
+  -- designed and released. Now, Apple developers are so
+  -- protected, that they can load nothing being not installed in
+  -- the system, since the environment is sanitized before the
+  -- child process is launched. In particular, environment
+  -- variables starting with DYLD_ and LD_ are unset for child
+  -- process. For more info, see the docs[2] below.
+  --
+  -- The environment variable below is used by FFI machinery to
+  -- find the proper shared library.
+  --
+  -- luacheck: push no max comment line length
+  --
+  -- [1]: https://support.apple.com/en-us/HT204899
+  -- [2]: https://developer.apple.com/library/archive/documentation/Security/Conceptual/System_Integrity_Protection_Guide/RuntimeProtections/RuntimeProtections.html
+  --
+  -- luacheck: pop
+  env = { DYLD_LIBRARY_PATH = os.getenv('DYLD_LIBRARY_PATH') },
+  redirect = '2>&1',
+})
 
 -- Depending on trigger and hotloop values the following contexts
 -- are possible:
 -- * if trigger <= hotloop -> trace recording is aborted
 -- * if trigger >  hotloop -> trace is recorded but execution
 --   leads to panic
-jit.opt.start("3", string.format("hotloop=%d", cfg.hotloop))
+local hotloop = 1
+local cases = {
+  abort = {
+    trigger = hotloop,
+    expected = 'LJ flush still works',
+    test = 'is',
+    message = 'Trace is aborted',
+  },
+  panic = {
+    trigger = hotloop + 1,
+    expected = 'JIT mode change is detected while executing the trace',
+    test = 'like',
+    message = 'Trace is compiled',
+  },
+}
 
-for i = 0, cfg.trigger + cfg.hotloop do
-  ffiflush.flush(flush, i)
+for _, subtest in pairs(cases) do
+  local output = script(hotloop, subtest.trigger)
+  -- XXX: explicitly pass <test> as an argument to <testf>
+  -- to emulate test:is(...), test:like(...), etc.
+  test[subtest.test](test, output, subtest.expected, subtest.message)
 end
--- Panic didn't occur earlier.
-print('OK')
+
+os.exit(test:check() and 0 or 1)
diff --git a/test/tarantool-tests/lj-flush-on-trace/script.lua b/test/tarantool-tests/lj-flush-on-trace/script.lua
new file mode 100644
index 00000000..d2c35534
--- /dev/null
+++ b/test/tarantool-tests/lj-flush-on-trace/script.lua
@@ -0,0 +1,23 @@
+local hotloop = assert(arg[1], 'hotloop argument is missing')
+local trigger = assert(arg[2], 'trigger argument is missing')
+
+local ffi = require('ffi')
+local ffiflush = ffi.load('libflush')
+ffi.cdef('void flush(struct flush *state, int i)')
+
+-- Save the current coroutine and set the value to trigger
+-- <flush> call the Lua routine instead of C implementation.
+local flush = require('libflush')(trigger)
+
+-- Depending on trigger and hotloop values the following contexts
+-- are possible:
+-- * if trigger <= hotloop -> trace recording is aborted
+-- * if trigger >  hotloop -> trace is recorded but execution
+--   leads to panic
+jit.opt.start("3", string.format("hotloop=%d", hotloop))
+
+for i = 0, trigger + hotloop do
+  ffiflush.flush(flush, i)
+end
+-- Panic didn't occur earlier.
+io.write('LJ flush still works')
diff --git a/test/tarantool-tests/utils.lua b/test/tarantool-tests/utils.lua
index eb11d40d..8355149b 100644
--- a/test/tarantool-tests/utils.lua
+++ b/test/tarantool-tests/utils.lua
@@ -1,7 +1,6 @@
 local M = {}
 
 local ffi = require('ffi')
-local tap = require('tap')
 local bc = require('jit.bc')
 local bit = require('bit')
 
@@ -44,55 +43,42 @@ function M.luacmd(args)
   return table.concat(args, ' ', idx + 1, -1)
 end
 
-local function unshiftenv(variable, value, sep)
-  local envvar = os.getenv(variable)
-  return ('%s="%s%s"'):format(variable, value,
-                              envvar and ('%s%s'):format(sep, envvar) or '')
+local function makeenv(tabenv)
+  if tabenv == nil then return '' end
+  local flatenv = {}
+  for var, value in pairs(tabenv) do
+    table.insert(flatenv, ('%s=%s'):format(var, value))
+  end
+  return table.concat(flatenv, ' ')
 end
 
-function M.selfrun(arg, checks)
-  -- If TEST_SELFRUN is set, it means the test has been run via
-  -- <io.popen>, so just return from this routine and proceed
-  -- the execution to the test payload, ...
-  if os.getenv('TEST_SELFRUN') then return end
-
-  -- ... otherwise initialize <tap>, setup testing environment
-  -- and run this chunk via <io.popen> for each case in <checks>.
-  -- XXX: The function doesn't return back from this moment. It
-  -- checks whether all assertions are fine and exits.
-
-  local test = tap.test(arg[0]:match('/?(.+)%.test%.lua'))
-
-  test:plan(#checks)
-
-  local libext = package.cpath:match('?.(%a+);')
-  local vars = {
+-- <makecmd> creates a command that runs %testname%/script.lua by
+-- <LUAJIT_TEST_BINARY> with the given environment, launch options
+-- and CLI arguments. The function yields an object (i.e. table)
+-- with the aforementioned parameters. To launch the command just
+-- call the object.
+function M.makecmd(arg, opts)
+  return setmetatable({
     LUABIN = M.luacmd(arg),
-    SCRIPT = arg[0],
-    PATH   = arg[0]:gsub('%.test%.lua', ''),
-    SUFFIX = libext,
-    ENV = table.concat({
-      unshiftenv('LUA_PATH', '<PATH>/?.lua', ';'),
-      unshiftenv('LUA_CPATH', '<PATH>/?.<SUFFIX>', ';'),
-      unshiftenv((libext == 'dylib' and 'DYLD' or 'LD') .. '_LIBRARY_PATH',
-                 '<PATH>', ':'),
-      'TEST_SELFRUN=1',
-    }, ' '),
-  }
-
-  local cmd = string.gsub('<ENV> <LUABIN> 2>&1 <SCRIPT>', '%<(%w+)>', vars)
-
-  for _, ch in pairs(checks) do
-    local testf = test[ch.test]
-    assert(testf, ("tap doesn't provide test.%s function"):format(ch.test))
-    local proc = io.popen((cmd .. (' %s'):rep(#ch.arg)):format(unpack(ch.arg)))
-    local res = proc:read('*all'):gsub('^%s+', ''):gsub('%s+$', '')
-    -- XXX: explicitly pass <test> as an argument to <testf>
-    -- to emulate test:is(...), test:like(...), etc.
-    testf(test, res, ch.res, ch.msg)
-  end
-
-  os.exit(test:check() and 0 or 1)
+    SCRIPT = opts and opts.script or arg[0]:gsub('%.test%.lua$', '/script.lua'),
+    ENV = opts and makeenv(opts.env) or '',
+    REDIRECT = opts and opts.redirect or '',
+  }, {
+    __call = function(self, ...)
+      -- This line just makes the command for <io.popen> by the
+      -- following steps:
+      -- 1. Replace the placeholders with the corresponding values
+      --    given to the command constructor (e.g. script, env)
+      -- 2. Join all CLI arguments given to the __call metamethod
+      -- 3. Concatenate the results of step 1 and step 2 to obtain
+      --    the resulting command.
+      local cmd = ('<ENV> <LUABIN> <REDIRECT> <SCRIPT>'):gsub('%<(%w+)>', self)
+                  .. (' %s'):rep(select('#', ...)):format(...)
+      -- Trim both leading and trailing whitespace from the output
+      -- produced by the child process.
+      return io.popen(cmd):read('*all'):gsub('^%s+', ''):gsub('%s+$', '')
+    end
+  })
 end
 
 function M.skipcond(condition, message)
-- 
2.30.2



More information about the Tarantool-patches mailing list