Tarantool development patches archive
 help / color / mirror / Atom feed
From: Maxim Kokryashkin via Tarantool-patches <tarantool-patches@dev.tarantool.org>
To: tarantool-patches@dev.tarantool.org, skaplun@tarantool.org,
	sergeyb@tarantool.org, imun@tarantool.org
Cc: Maksim Kokryashkin <max.kokryashkin@gmail.com>
Subject: [Tarantool-patches] [PATCH luajit v5 2/2] test: add tests for debugging extensions
Date: Wed,  6 Dec 2023 17:14:56 +0300	[thread overview]
Message-ID: <dcc57f3b7fce3775eb92b5f3fc80dac2091afeac.1701872037.git.m.kokryashkin@tarantool.org> (raw)
In-Reply-To: <cover.1701872037.git.m.kokryashkin@tarantool.org>

From: Maksim Kokryashkin <max.kokryashkin@gmail.com>

This patch adds tests for LuaJIT debugging
extensions for lldb and gdb.
---
 .flake8rc                                     |   4 +
 src/luajit_dbg.py                             |  11 +-
 test/CMakeLists.txt                           |   3 +
 .../CMakeLists.txt                            | 102 +++++++
 .../debug-extension-tests.py                  | 262 ++++++++++++++++++
 5 files changed, 376 insertions(+), 6 deletions(-)
 create mode 100644 test/LuaJIT-debug-extensions-tests/CMakeLists.txt
 create mode 100644 test/LuaJIT-debug-extensions-tests/debug-extension-tests.py

diff --git a/.flake8rc b/.flake8rc
index 13e6178f..6766ed41 100644
--- a/.flake8rc
+++ b/.flake8rc
@@ -3,3 +3,7 @@ extend-ignore =
   # XXX: Suppress F821, since we have autogenerated names for
   # 'ptr' type complements in luajit_lldb.py.
   F821
+per-file-ignores =
+  # XXX: Flake considers regexp special characters to be
+  # escape sequences.
+  test/LuaJIT-debug-extensions-tests/debug-extension-tests.py:W605
diff --git a/src/luajit_dbg.py b/src/luajit_dbg.py
index e79b7c32..a42d8f25 100644
--- a/src/luajit_dbg.py
+++ b/src/luajit_dbg.py
@@ -40,14 +40,13 @@ class Debugger(object):
             lib = None
             try:
                 lib = import_module(name)
-            except ImportError:
+                if healthcheck(lib):
+                    setattr(self, name.upper(), True)
+                    globals()[name] = lib
+                    self.name = name
+            except Exception:
                 continue
 
-            if healthcheck(lib):
-                setattr(self, name.upper(), True)
-                globals()[name] = lib
-                self.name = name
-
         assert self.LLDB != self.GDB
 
     def setup_target(self, debugger):
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index 8afc42df..83d4bfc7 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -79,6 +79,7 @@ add_subdirectory(PUC-Rio-Lua-5.1-tests)
 add_subdirectory(lua-Harness-tests)
 add_subdirectory(tarantool-c-tests)
 add_subdirectory(tarantool-tests)
+add_subdirectory(LuaJIT-debug-extensions-tests)
 
 add_custom_target(${PROJECT_NAME}-test DEPENDS
   LuaJIT-tests
@@ -86,6 +87,8 @@ add_custom_target(${PROJECT_NAME}-test DEPENDS
   lua-Harness-tests
   tarantool-c-tests
   tarantool-tests
+  LuaJIT-lldb-extension-tests
+  LuaJIT-gdb-extension-tests
 )
 
 if(LUAJIT_USE_TEST)
diff --git a/test/LuaJIT-debug-extensions-tests/CMakeLists.txt b/test/LuaJIT-debug-extensions-tests/CMakeLists.txt
new file mode 100644
index 00000000..78eafd02
--- /dev/null
+++ b/test/LuaJIT-debug-extensions-tests/CMakeLists.txt
@@ -0,0 +1,102 @@
+add_custom_target(LuaJIT-gdb-extension-tests
+  DEPENDS ${LUAJIT_TEST_BINARY}
+)
+
+add_custom_target(LuaJIT-lldb-extension-tests
+  DEPENDS ${LUAJIT_TEST_BINARY}
+)
+
+set(DUMMY_GDB_MSG "LuaJIT-gdb-extension-tests is a dummy target")
+set(DUMMY_LLDB_MSG "LuaJIT-lldb-extension-tests is a dummy target")
+
+# Debug info is required for testing of extensions.
+if(NOT (CMAKE_BUILD_TYPE MATCHES Debug))
+  message(WARNING
+    "not a DEBUG build, LuaJIT-lldb-extension-tests and "
+    "LuaJIT-gdb-extension-tests are dummy"
+  )
+  add_custom_command(TARGET LuaJIT-gdb-extension-tests
+    COMMAND ${CMAKE_COMMAND} -E cmake_echo_color --red ${DUMMY_GDB_MSG}
+  )
+  add_custom_command(TARGET LuaJIT-lldb-extension-tests
+    COMMAND ${CMAKE_COMMAND} -E cmake_echo_color --red ${DUMMY_LLDB_MSG}
+  )
+  return()
+endif()
+
+# MacOS asks for permission to debug a process even when the
+# machine is set into development mode. To solve the issue,
+# it is required to add relevant users to the `_developer` user
+# group in MacOS. Disabled for now.
+if(CMAKE_SYSTEM_NAME STREQUAL "Darwin" AND DEFINED ENV{CI})
+  message(WARNING
+    "Interactive debugging is unavailable for macOS CI builds,"
+    "LuaJIT-lldb-extension-tests is dummy"
+  )
+  add_custom_command(TARGET LuaJIT-lldb-extension-tests
+    COMMAND ${CMAKE_COMMAND} -E cmake_echo_color --red ${DUMMY_LLDB_MSG}
+  )
+  return()
+endif()
+
+find_package(PythonInterp)
+if(NOT PYTHONINTERP_FOUND)
+  message(WARNING
+    "`python` is not found, LuaJIT-lldb-extension-tests and "
+    "LuaJIT-gdb-extension-tests are dummy"
+  )
+  add_custom_command(TARGET LuaJIT-gdb-extension-tests
+    COMMAND ${CMAKE_COMMAND} -E cmake_echo_color --red ${DUMMY_GDB_MSG}
+  )
+  add_custom_command(TARGET LuaJIT-lldb-extension-tests
+    COMMAND ${CMAKE_COMMAND} -E cmake_echo_color --red ${DUMMY_LLDB_MSG}
+  )
+  return()
+endif()
+
+set(DEBUGGER_TEST_ENV
+  "LUAJIT_TEST_BINARY=${LUAJIT_TEST_BINARY}"
+  # Suppresses __pycache__ generation.
+  "PYTHONDONTWRITEBYTECODE=1"
+  "DEBUGGER_EXTENSION_PATH=${PROJECT_SOURCE_DIR}/src/luajit_dbg.py"
+)
+
+set(TEST_SCRIPT_PATH
+  ${PROJECT_SOURCE_DIR}/test/LuaJIT-debug-extensions-tests/debug-extension-tests.py
+)
+
+find_program(GDB gdb)
+if(GDB)
+  set(GDB_TEST_ENV ${DEBUGGER_TEST_ENV}
+    "DEBUGGER_COMMAND=${GDB}"
+  )
+  add_custom_command(TARGET LuaJIT-gdb-extension-tests
+    COMMENT "Running luajit_dbg.py tests with gdb"
+    COMMAND
+    ${GDB_TEST_ENV} ${PYTHON_EXECUTABLE} ${TEST_SCRIPT_PATH}
+    WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
+  )
+else()
+  message(WARNING "`gdb' is not found, so LuaJIT-gdb-extension-tests is dummy")
+  add_custom_command(TARGET LuaJIT-gdb-extension-tests
+    COMMAND ${CMAKE_COMMAND} -E cmake_echo_color --red ${DUMMY_GDB_MSG}
+  )
+endif()
+
+find_program(LLDB lldb)
+if(LLDB)
+  set(LLDB_TEST_ENV ${DEBUGGER_TEST_ENV}
+    "DEBUGGER_COMMAND=${LLDB}"
+  )
+  add_custom_command(TARGET LuaJIT-lldb-extension-tests
+    COMMENT "Running luajit_dbg.py tests with lldb"
+    COMMAND
+    ${LLDB_TEST_ENV} ${PYTHON_EXECUTABLE} ${TEST_SCRIPT_PATH}
+    WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
+  )
+else()
+  message(WARNING "`lldb' is not found, so LuaJIT-lldb-extension-tests is dummy")
+  add_custom_command(TARGET LuaJIT-lldb-extension-tests
+    COMMAND ${CMAKE_COMMAND} -E cmake_echo_color --red ${DUMMY_LLDB_MSG}
+  )
+endif()
diff --git a/test/LuaJIT-debug-extensions-tests/debug-extension-tests.py b/test/LuaJIT-debug-extensions-tests/debug-extension-tests.py
new file mode 100644
index 00000000..818247ca
--- /dev/null
+++ b/test/LuaJIT-debug-extensions-tests/debug-extension-tests.py
@@ -0,0 +1,262 @@
+# This file provides tests for LuaJIT debug extensions for lldb and gdb.
+import os
+import re
+import subprocess
+import sys
+import tempfile
+import unittest
+
+from threading import Timer
+
+LEGACY = re.match(r'^2\.', sys.version)
+
+LUAJIT_BINARY = os.environ['LUAJIT_TEST_BINARY']
+EXTENSION = os.environ['DEBUGGER_EXTENSION_PATH']
+DEBUGGER = os.environ['DEBUGGER_COMMAND']
+LLDB = 'lldb' in DEBUGGER
+TIMEOUT = 10
+
+RUN_CMD_FILE = '-s' if LLDB else '-x'
+INFERIOR_ARGS = '--' if LLDB else '--args'
+PROCESS_RUN = 'process launch' if LLDB else 'r'
+LOAD_EXTENSION = (
+    'command script import {ext}' if LLDB else 'source {ext}'
+).format(ext=EXTENSION)
+
+
+def persist(data):
+    tmp = tempfile.NamedTemporaryFile(mode='w')
+    tmp.write(data)
+    tmp.flush()
+    return tmp
+
+
+def execute_process(cmd, timeout=TIMEOUT):
+    if LEGACY:
+        # XXX: The Python 2.7 version of `subprocess.Popen` doesn't have a
+        # timeout option, so the required functionality was implemented via
+        # `threading.Timer`.
+        process = subprocess.Popen(
+            cmd,
+            stdout=subprocess.PIPE,
+            stderr=subprocess.PIPE,
+        )
+        timer = Timer(TIMEOUT, process.kill)
+        timer.start()
+        stdout, stderr = process.communicate()
+        timer.cancel()
+
+        # XXX: If the timeout is exceeded and the process is killed by the
+        # timer, then the return code is non-zero, and we are going to blow up.
+        assert process.returncode == 0
+        return stdout.decode('ascii'), stderr.decode('ascii')
+    else:
+        process = subprocess.run(cmd, capture_output=True, timeout=TIMEOUT)
+        return process.stdout.decode('ascii'), process.stderr.decode('ascii')
+
+
+def filter_debugger_output(output):
+    descriptor = '(lldb)' if LLDB else '(gdb)'
+    return ''.join(
+        filter(
+            lambda line: not line.startswith(descriptor),
+            output.splitlines(True),
+        ),
+    )
+
+
+class TestCaseBase(unittest.TestCase):
+    @classmethod
+    def construct_cmds(cls):
+        return '\n'.join([
+            'b {loc}'.format(loc=cls.location),
+            PROCESS_RUN,
+            'n',
+            LOAD_EXTENSION,
+            cls.extension_cmds.strip(),
+            'q',
+        ])
+
+    @classmethod
+    def setUpClass(cls):
+        cmd_file = persist(cls.construct_cmds())
+        script_file = persist(cls.lua_script)
+        process_cmd = [
+            DEBUGGER,
+            RUN_CMD_FILE,
+            cmd_file.name,
+            INFERIOR_ARGS,
+            LUAJIT_BINARY,
+            script_file.name,
+        ]
+        cls.output, cls.stderr = execute_process(process_cmd)
+        cls.output = filter_debugger_output(cls.output)
+        cmd_file.close()
+        script_file.close()
+
+    def check(self):
+        if self.stderr != '':
+            raise self.failureException(
+                'Non-empty stderr: {}'.format(self.stderr),
+            )
+
+        pattern = self.pattern.strip()
+        match = re.search(pattern, self.output)
+
+        if not match:
+            fmt = '\nEXPECTED PATTERN:\n{}\n\nGOT OUTPUT:\n{}'
+            raise self.failureException(fmt.format(pattern, self.output))
+
+
+class TestLoad(TestCaseBase):
+    extension_cmds = ''
+    location = 'lj_cf_print'
+    lua_script = 'print(1)'
+    pattern = (
+        'lj-tv command intialized\n'
+        'lj-state command intialized\n'
+        'lj-arch command intialized\n'
+        'lj-gc command intialized\n'
+        'lj-str command intialized\n'
+        'lj-tab command intialized\n'
+        'lj-stack command intialized\n'
+        'LuaJIT debug extension is successfully loaded\n'
+    )
+
+
+class TestLJArch(TestCaseBase):
+    extension_cmds = 'lj-arch'
+    location = 'lj_cf_print'
+    lua_script = 'print(1)'
+    pattern = (
+        'LJ_64: (True|False), '
+        'LJ_GC64: (True|False), '
+        'LJ_DUALNUM: (True|False)'
+    )
+
+
+class TestLJState(TestCaseBase):
+    extension_cmds = 'lj-state'
+    location = 'lj_cf_print'
+    lua_script = 'print(1)'
+    pattern = (
+        'VM state: [A-Z]+\n'
+        'GC state: [A-Z]+\n'
+        'JIT state: [A-Z]+\n'
+    )
+
+
+class TestLJGC(TestCaseBase):
+    extension_cmds = 'lj-gc'
+    location = 'lj_cf_print'
+    lua_script = 'print(1)'
+    pattern = (
+        'GC stats: [A-Z]+\n'
+        '\ttotal: \d+\n'
+        '\tthreshold: \d+\n'
+        '\tdebt: \d+\n'
+        '\testimate: \d+\n'
+        '\tstepmul: \d+\n'
+        '\tpause: \d+\n'
+        '\tsweepstr: \d+/\d+\n'
+        '\troot: \d+ objects\n'
+        '\tgray: \d+ objects\n'
+        '\tgrayagain: \d+ objects\n'
+        '\tweak: \d+ objects\n'
+        '\tmmudata: \d+ objects\n'
+    )
+
+
+class TestLJStack(TestCaseBase):
+    extension_cmds = 'lj-stack'
+    location = 'lj_cf_print'
+    lua_script = 'print(1)'
+    pattern = (
+        '-+ Red zone:\s+\d+ slots -+\n'
+        '(0x[a-zA-Z0-9]+\s+\[(S|\s)(B|\s)(T|\s)(M|\s)\] VALUE: nil\n?)*\n'
+        '-+ Stack:\s+\d+ slots -+\n'
+        '(0x[A-Za-z0-9]+(:0x[A-Za-z0-9]+)?\s+'
+        '\[(S|\s)(B|\s)(T|\s)(M|\s)\].*\n?)+\n'
+    )
+
+
+class TestLJTV(TestCaseBase):
+    location = 'lj_cf_print'
+    lua_script = 'print(1)'
+    extension_cmds = (
+        'lj-tv L->base\n'
+        'lj-tv L->base + 1\n'
+        'lj-tv L->base + 2\n'
+        'lj-tv L->base + 3\n'
+        'lj-tv L->base + 4\n'
+        'lj-tv L->base + 5\n'
+        'lj-tv L->base + 6\n'
+        'lj-tv L->base + 7\n'
+        'lj-tv L->base + 8\n'
+        'lj-tv L->base + 9\n'
+        'lj-tv L->base + 10\n'
+        'lj-tv L->base + 11\n'
+    )
+
+    lua_script = (
+        'local ffi = require("ffi")\n'
+        'print(\n'
+        '  nil,\n'
+        '  false,\n'
+        '  true,\n'
+        '  "hello",\n'
+        '  {1},\n'
+        '  1,\n'
+        '  1.1,\n'
+        '  coroutine.create(function() end),\n'
+        '  ffi.new("int*"),\n'
+        '  function() end,\n'
+        '  print,\n'
+        '  require\n'
+        ')\n'
+    )
+
+    pattern = (
+        'nil\n'
+        'false\n'
+        'true\n'
+        'string \"hello\" @ 0x[a-zA-Z0-9]+\n'
+        'table @ 0x[a-zA-Z0-9]+ \(asize: \d+, hmask: 0x[a-zA-Z0-9]+\)\n'
+        '(number|integer) .*1.*\n'
+        'number 1.1\d+\n'
+        'thread @ 0x[a-zA-Z0-9]+\n'
+        'cdata @ 0x[a-zA-Z0-9]+\n'
+        'Lua function @ 0x[a-zA-Z0-9]+, [0-9]+ upvalues, .+:[0-9]+\n'
+        'fast function #[0-9]+\n'
+        'C function @ 0x[a-zA-Z0-9]+\n'
+    )
+
+
+class TestLJStr(TestCaseBase):
+    extension_cmds = 'lj-str fname'
+    location = 'lj_cf_dofile'
+    lua_script = 'pcall(dofile("name"))'
+    pattern = 'String: .* \[\d+ bytes\] with hash 0x[a-zA-Z0-9]+'
+
+
+class TestLJTab(TestCaseBase):
+    extension_cmds = 'lj-tab t'
+    location = 'lj_cf_unpack'
+    lua_script = 'unpack({1; a = 1})'
+    pattern = (
+        'Array part: 3 slots\n'
+        '0x[a-zA-Z0-9]+: \[0\]: nil\n'
+        '0x[a-zA-Z0-9]+: \[1\]: .+ 1\n'
+        '0x[a-zA-Z0-9]+: \[2\]: nil\n'
+        'Hash part: 2 nodes\n'
+        '0x[a-zA-Z0-9]+: { string "a" @ 0x[a-zA-Z0-9]+ } => '
+        '{ .+ 1 }; next = 0x0\n'
+        '0x[a-zA-Z0-9]+: { nil } => { nil }; next = 0x0\n'
+    )
+
+
+for test_cls in TestCaseBase.__subclasses__():
+    test_cls.test = lambda self: self.check()
+
+if __name__ == '__main__':
+    unittest.main(verbosity=2)
-- 
2.43.0


  parent reply	other threads:[~2023-12-06 14:16 UTC|newest]

Thread overview: 7+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2023-12-06 14:14 [Tarantool-patches] [PATCH luajit v5 0/2] debug: generalized extension Maxim Kokryashkin via Tarantool-patches
2023-12-06 14:14 ` [Tarantool-patches] [PATCH luajit v5 1/2] " Maxim Kokryashkin via Tarantool-patches
2023-12-19  8:01   ` Sergey Bronnikov via Tarantool-patches
2023-12-06 14:14 ` Maxim Kokryashkin via Tarantool-patches [this message]
2023-12-19  8:16   ` [Tarantool-patches] [PATCH luajit v5 2/2] test: add tests for debugging extensions Sergey Bronnikov via Tarantool-patches
2023-12-19 14:34     ` Maxim Kokryashkin via Tarantool-patches
2023-12-20 12:23       ` Sergey Bronnikov via Tarantool-patches

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=dcc57f3b7fce3775eb92b5f3fc80dac2091afeac.1701872037.git.m.kokryashkin@tarantool.org \
    --to=tarantool-patches@dev.tarantool.org \
    --cc=imun@tarantool.org \
    --cc=max.kokryashkin@gmail.com \
    --cc=sergeyb@tarantool.org \
    --cc=skaplun@tarantool.org \
    --subject='Re: [Tarantool-patches] [PATCH luajit v5 2/2] test: add tests for debugging extensions' \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox