From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from [87.239.111.99] (localhost [127.0.0.1]) by dev.tarantool.org (Postfix) with ESMTP id F305AC99984; Thu, 4 Apr 2024 13:27:21 +0300 (MSK) DKIM-Filter: OpenDKIM Filter v2.11.0 dev.tarantool.org F305AC99984 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=tarantool.org; s=dev; t=1712226442; bh=OmFc6xk/IEPIB3O+jdxrXhvxytfjqAVxLctEjOcomt8=; h=Date:To:References:In-Reply-To:Subject:List-Id:List-Unsubscribe: List-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To: From; b=PtRQO8q0Vw5IA8WqLdjDNEpsymvf4WhnB0CPb50XQVaKaAerkxEmphuEQbG+POr9u Qs7khTbIdoScX6GEnW2HXS46oK64uF8mvyVDjUH1RgI2XNYQSMY/jP+4lIkw/QXOn4 j67Ls7xQgIloJfMzrqiVdZ5x03wRnHefMI5xCiUk= Received: from smtp33.i.mail.ru (smtp33.i.mail.ru [95.163.41.74]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by dev.tarantool.org (Postfix) with ESMTPS id AC23BC99984 for ; Thu, 4 Apr 2024 13:27:20 +0300 (MSK) DKIM-Filter: OpenDKIM Filter v2.11.0 dev.tarantool.org AC23BC99984 Received: by smtp33.i.mail.ru with esmtpa (envelope-from ) id 1rsKJT-00000009sSl-3Dfk; Thu, 04 Apr 2024 13:27:20 +0300 Message-ID: Date: Thu, 4 Apr 2024 13:27:19 +0300 MIME-Version: 1.0 User-Agent: Mozilla Thunderbird To: Maxim Kokryashkin , tarantool-patches@dev.tarantool.org, skaplun@tarantool.org References: <121161805fff1e9b5855f1092bc16c24af3da3de.1712182830.git.m.kokryashkin@tarantool.org> Content-Language: en-US In-Reply-To: <121161805fff1e9b5855f1092bc16c24af3da3de.1712182830.git.m.kokryashkin@tarantool.org> Content-Type: text/plain; charset=UTF-8; format=flowed Content-Transfer-Encoding: 8bit X-Mailru-Src: smtp X-4EC0790: 10 X-7564579A: 646B95376F6C166E X-77F55803: 4F1203BC0FB41BD96441E77B1D9F2D07E967FE4D98E11CAA76760FEACDE6AFA9182A05F5380850401581EADF27B42CAF5D1BE6A8D71B10A500274AB894C92A172D664D3A2FCDDD15CE3F7EDB596DBB80 X-7FA49CB5: FF5795518A3D127A4AD6D5ED66289B5278DA827A17800CE78524CA6C6209E9ADEA1F7E6F0F101C67BD4B6F7A4D31EC0BCC500DACC3FED6E28638F802B75D45FF8AA50765F790063763305FB9D3ECC1898638F802B75D45FF36EB9D2243A4F8B5A6FCA7DBDB1FC311F39EFFDF887939037866D6147AF826D800E354884F04B0C46AADF6562818F4F83130E23E1950C73ACC7F00164DA146DAFE8445B8C89999728AA50765F7900637F3E38EE449E3E2AE389733CBF5DBD5E9C8A9BA7A39EFB766F5D81C698A659EA7CC7F00164DA146DA9985D098DBDEAEC8062BEEFFB5F8EA3EF6B57BC7E6449061A352F6E88A58FB86F5D81C698A659EA73AA81AA40904B5D9A18204E546F3947C4CB6874B0BCFF0B803F1AB874ED890284AD6D5ED66289B523666184CF4C3C14F6136E347CC761E07725E5C173C3A84C3F0685002530F4075BA3038C0950A5D36B5C8C57E37DE458B330BD67F2E7D9AF16D1867E19FE14079C09775C1D3CA48CFE478A468B35FE7671DD303D21008E298D5E8D9A59859A8B6D082881546D9349175ECD9A6C639B01B78DA827A17800CE7742996B5449390BC731C566533BA786AA5CC5B56E945C8DA X-C1DE0DAB: 0D63561A33F958A5AC0E67F240ABB2FB5002B1117B3ED696BE3DC1F1F79EDAB492B673A2F5DDD7E7823CB91A9FED034534781492E4B8EEADD0953842B444AAC3BDAD6C7F3747799A X-C8649E89: 1C3962B70DF3F0ADE00A9FD3E00BEEDF3FED46C3ACD6F73ED3581295AF09D3DF87807E0823442EA2ED31085941D9CD0AF7F820E7B07EA4CF19294CE34E1B1D099C779056A02BBA58321A741DC18CE868735AF873FCFA4797A4B23375CB3901BDE2B85D705C0AC83B4AA589C05209739F4D0852948B3F2D4301EF4E916DC7ADCD5F4332CA8FE04980913E6812662D5F2AB9AF64DB4688768036DF5FE9C0001AF333F2C28C22F508233FCF178C6DD14203 X-D57D3AED: 3ZO7eAau8CL7WIMRKs4sN3D3tLDjz0dLbV79QFUyzQ2Ujvy7cMT6pYYqY16iZVKkSc3dCLJ7zSJH7+u4VD18S7Vl4ZUrpaVfd2+vE6kuoey4m4VkSEu530nj6fImhcD4MUrOEAnl0W826KZ9Q+tr5ycPtXkTV4k65bRjmOUUP8cvGozZ33TWg5HZplvhhXbhDGzqmQDTd6OAevLeAnq3Ra9uf7zvY2zzsIhlcp/Y7m53TZgf2aB4JOg4gkr2biojXDAMXEVBmzujWBFbIZISMw== X-Mailru-Sender: 520A125C2F17F0B1A9638AD358559B594A59FDDE57CBE6415D1BE6A8D71B10A500274AB894C92A17B7CBEF92542CD7C8795FA72BAB74744FC77752E0C033A69EA16A481184E8BB1C9B38E6EA4F046BE03A5DB60FBEB33A8A0DA7A0AF5A3A8387 X-Mras: Ok Subject: Re: [Tarantool-patches] [PATCH luajit v6 2/2] test: add tests for debugging extensions X-BeenThere: tarantool-patches@dev.tarantool.org X-Mailman-Version: 2.1.34 Precedence: list List-Id: Tarantool development patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , From: Sergey Bronnikov via Tarantool-patches Reply-To: Sergey Bronnikov Errors-To: tarantool-patches-bounces@dev.tarantool.org Sender: "Tarantool-patches" Hi, Max thanks for the patch. See my comments below: On 4/4/24 01:21, Maxim Kokryashkin wrote: > From: Maksim Kokryashkin > > This patch adds tests for LuaJIT debugging > extensions for lldb and gdb. > --- > .flake8rc | 4 + > test/CMakeLists.txt | 1 + > .../CMakeLists.txt | 80 ++++++ > .../debug-extension-tests.py | 250 ++++++++++++++++++ > 4 files changed, 335 insertions(+) > 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/test/CMakeLists.txt b/test/CMakeLists.txt > index 19726f5a..a3b48939 100644 > --- a/test/CMakeLists.txt > +++ b/test/CMakeLists.txt > @@ -148,6 +148,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) > > # Each testsuite has its own CMake target, but combining these > # target into a single one is not desired, because each target > diff --git a/test/LuaJIT-debug-extensions-tests/CMakeLists.txt b/test/LuaJIT-debug-extensions-tests/CMakeLists.txt > new file mode 100644 > index 00000000..9ac626ec > --- /dev/null > +++ b/test/LuaJIT-debug-extensions-tests/CMakeLists.txt > @@ -0,0 +1,80 @@ > +SET(TEST_SUITE_NAME "LuaJIT-dbg-extension-tests") > +add_test_suite_target(LuaJIT-dbg-extension-tests > + LABELS ${TEST_SUITE_NAME} > + DEPENDS ${LUAJIT_TEST_BINARY} > +) > + > +# 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" > + ) > + 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" > + ) > + 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" > + ) > + 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(test_title "test/${TEST_SUITE_NAME}/gdb") > + set(GDB_TEST_ENV ${DEBUGGER_TEST_ENV} "DEBUGGER_COMMAND=${GDB}") > + add_test(NAME "${test_title}" > + COMMAND ${PYTHON_EXECUTABLE} ${TEST_SCRIPT_PATH} > + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} > + ) > + set_tests_properties("${test_title}" PROPERTIES > + ENVIRONMENT "${GDB_TEST_ENV}" > + LABELS ${TEST_SUITE_NAME} > + DEPENDS LuaJIT-dbg-extension-tests-deps > + ) > +else() > + message(WARNING "`gdb' is not found, so LuaJIT-gdb-extension-tests is dummy") > +endif() > + > +find_program(LLDB lldb) > +if(LLDB) > + set(test_title "test/${TEST_SUITE_NAME}/lldb") I would use a real path as a test title like we do in other testsuites (PUC Rio Lua and LuaJIT tests are exception in this rule). For these test we have two flavors, so I suggest to create symlinks: ~/sources/MRG/tarantool/third_party/luajit/test/LuaJIT-debug-extensions-tests$ ls -la total 20 drwxrwxr-x  2 sergeyb sergeyb 4096 Apr  4 13:15 . drwxrwxr-x 10 sergeyb sergeyb 4096 Apr  4 12:44 .. -rw-rw-r--  1 sergeyb sergeyb 2451 Apr  4 12:44 CMakeLists.txt -rw-rw-r--  1 sergeyb sergeyb 6787 Apr  4 12:44 debug-extension-tests.py lrwxrwxrwx  1 sergeyb sergeyb   24 Apr  4 13:15 gdb-debug-extension-tests.py -> debug-extension-tests.py lrwxrwxrwx  1 sergeyb sergeyb   24 Apr  4 13:15 lldb-debug-extension-tests.py -> debug-extension-tests.py And generate CMake tests for these files: --- a/test/LuaJIT-debug-extensions-tests/CMakeLists.txt +++ b/test/LuaJIT-debug-extensions-tests/CMakeLists.txt @@ -41,14 +41,11 @@ set(DEBUGGER_TEST_ENV "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(test_title "test/${TEST_SUITE_NAME}/gdb") +  set(test_title "test/${TEST_SUITE_NAME}/gdb-debug-extension-tests.py")    set(GDB_TEST_ENV ${DEBUGGER_TEST_ENV} "DEBUGGER_COMMAND=${GDB}") +  set(TEST_SCRIPT_PATH ${CMAKE_CURRENT_SOURCE_DIR}/gdb-debug-extension-tests.py)    add_test(NAME "${test_title}"      COMMAND ${PYTHON_EXECUTABLE} ${TEST_SCRIPT_PATH}      WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} @@ -64,8 +61,9 @@ endif()  find_program(LLDB lldb)  if(LLDB) -  set(test_title "test/${TEST_SUITE_NAME}/lldb") +  set(test_title "test/${TEST_SUITE_NAME}/lldb-debug-extension-tests.py")    set(LLDB_TEST_ENV ${DEBUGGER_TEST_ENV} "DEBUGGER_COMMAND=${LLDB}") +  set(TEST_SCRIPT_PATH ${CMAKE_CURRENT_SOURCE_DIR}/lldb-debug-extension-tests.py)    add_test(NAME "test/${TEST_SUITE_NAME}/lldb"      COMMAND ${PYTHON_EXECUTABLE} ${TEST_SCRIPT_PATH}      WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} In CTest test titles looks as a real file names: $ ctest -L LuaJIT-dbg-extension-tests Test project /home/sergeyb/sources/MRG/tarantool/third_party/luajit/build/gc64     Start 212: LuaJIT-dbg-extension-tests-deps 1/2 Test #212: LuaJIT-dbg-extension-tests-deps ................................   Passed    0.01 sec     Start 213: test/LuaJIT-dbg-extension-tests/gdb-debug-extension-tests.py 2/2 Test #213: test/LuaJIT-dbg-extension-tests/gdb-debug-extension-tests.py ... Passed    2.11 sec 100% tests passed, 0 tests failed out of 2 Label Time Summary: LuaJIT-dbg-extension-tests    =   2.12 sec*proc (2 tests) Total Test time (real) =   2.14 sec What do you think? > + set(LLDB_TEST_ENV ${DEBUGGER_TEST_ENV} "DEBUGGER_COMMAND=${LLDB}") > + add_test(NAME "test/${TEST_SUITE_NAME}/lldb" > + COMMAND ${PYTHON_EXECUTABLE} ${TEST_SCRIPT_PATH} > + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} > + ) > + set_tests_properties("${test_title}" PROPERTIES > + ENVIRONMENT "${LLDB_TEST_ENV}" > + LABELS ${TEST_SUITE_NAME} > + DEPENDS LuaJIT-dbg-extension-tests-deps > + ) > +else() > + message(WARNING "`lldb' is not found, so LuaJIT-lldb-extension-tests is dummy") > +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..6ef87473 > --- /dev/null > +++ b/test/LuaJIT-debug-extensions-tests/debug-extension-tests.py > @@ -0,0 +1,250 @@ > +# 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) > + timer = Timer(TIMEOUT, process.kill) > + timer.start() > + stdout, _ = 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') > + else: > + process = subprocess.run(cmd, capture_output=True, timeout=TIMEOUT) > + return process.stdout.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 = filter_debugger_output(execute_process(process_cmd)) > + cmd_file.close() > + script_file.close() > + > + def check(self): > + if LEGACY: > + self.assertRegexpMatches(self.output, self.pattern.strip()) > + else: > + self.assertRegex(self.output, self.pattern.strip()) > + > + > +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)