diff --git a/stubs/gdb/@tests/stubtest_allowlist.txt b/stubs/gdb/@tests/stubtest_allowlist.txt new file mode 100644 index 000000000..c9a0a0f91 --- /dev/null +++ b/stubs/gdb/@tests/stubtest_allowlist.txt @@ -0,0 +1,119 @@ +# TODO: Update types in stub +# This list includes everything to allow initial stubtests to run in gdb's environment +gdb.BP_NONE +gdb.Breakpoint.stop +gdb.BreakpointEvent +gdb.ClearObjFilesEvent +gdb.Command.complete +gdb.Command.invoke +gdb.ConnectionEvent +gdb.ContinueEvent +gdb.DUMMY_FRAME +gdb.Event +gdb.EventRegistry +gdb.ExitedEvent +gdb.FRAME_UNWIND_FIRST_ERROR +gdb.FinishBreakpoint.out_of_scope +gdb.Function.invoke +gdb.GdbExitingEvent +gdb.GdbSetPythonDirectory +gdb.HOST_CONFIG +gdb.Inferior.connection +gdb.Inferior.thread_from_thread_handle +gdb.InferiorCallPostEvent +gdb.InferiorCallPreEvent +gdb.InferiorDeletedEvent +gdb.InferiorThread.details +gdb.Instruction +gdb.LazyString +gdb.LineTable.__next__ +gdb.LineTable.is_valid +gdb.LineTable.source_lines +gdb.LineTable.source_lnes +gdb.LineTableIterator +gdb.MICommand.invoke +gdb.Membuf +gdb.MemoryChangedEvent +gdb.NewInferiorEvent +gdb.NewObjFileEvent +gdb.NewThreadEvent +gdb.Objfile.frame_unwinders +gdb.Objfile.lookup_static_method +gdb.Objfile.lookup_static_symbol +gdb.Objfile.xmethods +gdb.Parameter.get_set_string +gdb.Parameter.get_show_string +gdb.Progspace.frame_unwinders +gdb.Progspace.xmethods +gdb.Record +gdb.RecordFunctionSegment +gdb.RecordGap +gdb.RecordInstruction +gdb.RegisterChangedEvent +gdb.RegisterDescriptor +gdb.RegisterDescriptorIterator +gdb.RegisterGroup +gdb.RegisterGroupsIterator +gdb.RemoteTargetConnection +gdb.SYMBOL_FUNCTIONS_DOMAIN +gdb.SYMBOL_TYPES_DOMAIN +gdb.SYMBOL_VARIABLES_DOMAIN +gdb.SignalEvent +gdb.StopEvent +gdb.TARGET_CONFIG +gdb.ThreadEvent +gdb.TuiWindow +gdb.Type.__contains__ +gdb.Type.get +gdb.Type.has_key +gdb.Type.is_scalar +gdb.Type.is_signed +gdb.Type.items +gdb.Type.iteritems +gdb.Type.iterkeys +gdb.Type.itervalues +gdb.Type.keys +gdb.Type.values +gdb.TypeIterator +gdb.Unwinder +gdb.Value.rvalue_reference_value +gdb.frame_filters +gdb.frame_unwinders +gdb.packages +gdb.prompt_hook +gdb.type_printers +gdb.xmethods +gdb.events +gdb.printing.RegexpCollectionPrettyPrinter.RegexpSubprinter +gdb.printing.add_builtin_pretty_printer +gdb.prompt.prompt_help +gdb.prompt.prompt_substitutions +gdb.types.TypePrinter.instantiate +gdb.unwinder.Unwinder +gdb.xmethod.SimpleXMethodMatcher +gdb.xmethod.XMethodMatcher.match +gdb.xmethod.XMethodWorker.__call__ +gdb.xmethod.XMethodWorker.get_arg_types +gdb.xmethod.XMethodWorker.get_result_type +gdb.FrameDecorator +gdb.FrameIterator +gdb.command +gdb.command.explore +gdb.command.frame_filters +gdb.command.pretty_printers +gdb.command.prompt +gdb.command.type_printers +gdb.command.unwinders +gdb.command.xmethods +gdb.frames +gdb.function +gdb.function.as_string +gdb.function.caller_is +gdb.function.strfns +gdb.printer +gdb.printer.bound_registers +gdb.printing.basestring +gdb.printing.long +gdb.xmethod.basestring +gdb.xmethod.long +gdb.styling diff --git a/stubs/gdb/METADATA.toml b/stubs/gdb/METADATA.toml index dc8b117e3..d3c20cd21 100644 --- a/stubs/gdb/METADATA.toml +++ b/stubs/gdb/METADATA.toml @@ -11,6 +11,5 @@ extra_description = """\ """ [tool.stubtest] -# Since the "gdb" Python package is available only inside GDB, it is not -# possible to install it through pip, so stub tests cannot install it. -skip = true +platforms = ["linux"] +apt_dependencies = ["gdb"] diff --git a/tests/stubtest_third_party.py b/tests/stubtest_third_party.py index 9fa21b2da..fc8d51b75 100755 --- a/tests/stubtest_third_party.py +++ b/tests/stubtest_third_party.py @@ -73,6 +73,12 @@ def run_stubtest( # TODO: Maybe find a way to cache these in CI dists_to_install = [dist_req, get_mypy_req()] dists_to_install.extend(requirements.external_pkgs) # Internal requirements are added to MYPYPATH + + # Since the "gdb" Python package is available only inside GDB, it is not + # possible to install it through pip, so stub tests cannot install it. + if dist_name == "gdb": + dists_to_install[:] = dists_to_install[1:] + pip_cmd = [pip_exe, "install", *dists_to_install] try: subprocess.run(pip_cmd, check=True, capture_output=True) @@ -118,6 +124,10 @@ def run_stubtest( if not setup_uwsgi_stubtest_command(dist, venv_dir, stubtest_cmd): return False + if dist_name == "gdb": + if not setup_gdb_stubtest_command(venv_dir, stubtest_cmd): + return False + try: subprocess.run(stubtest_cmd, env=stubtest_env, check=True, capture_output=True) except subprocess.CalledProcessError as e: @@ -153,6 +163,88 @@ def run_stubtest( return True +def setup_gdb_stubtest_command(venv_dir: Path, stubtest_cmd: list[str]) -> bool: + """ + Use wrapper scripts to run stubtest inside gdb. + The wrapper script is used to pass the arguments to the gdb script. + """ + if sys.platform == "win32": + print_error("gdb is not supported on Windows") + return False + + try: + gdb_version = subprocess.check_output(["gdb", "--version"], text=True, stderr=subprocess.STDOUT) + except FileNotFoundError: + print_error("gdb is not installed") + return False + if "Python scripting is not supported in this copy of GDB" in gdb_version: + print_error("Python scripting is not supported in this copy of GDB") + return False + + gdb_script = venv_dir / "gdb_stubtest.py" + wrapper_script = venv_dir / "gdb_wrapper.py" + gdb_script_contents = dedent( + f""" + import json + import os + import site + import sys + import traceback + + from glob import glob + + # Add the venv site-packages to sys.path. gdb doesn't use the virtual environment. + # Taken from https://github.com/pwndbg/pwndbg/blob/83d8d95b576b749e888f533ce927ad5a77fb957b/gdbinit.py#L37 + site_pkgs_path = glob(os.path.join({str(venv_dir)!r}, "lib/*/site-packages"))[0] + site.addsitedir(site_pkgs_path) + + exit_code = 1 + try: + # gdb wraps stdout and stderr without a .fileno + # colored output in mypy tries to access .fileno() + sys.stdout.fileno = sys.__stdout__.fileno + sys.stderr.fileno = sys.__stderr__.fileno + + from mypy.stubtest import main + + sys.argv = json.loads(os.environ.get("STUBTEST_ARGS")) + exit_code = main() + except Exception: + traceback.print_exc() + finally: + gdb.execute(f"quit {{exit_code}}") + """ + ) + gdb_script.write_text(gdb_script_contents) + + wrapper_script_contents = dedent( + f""" + import json + import os + import subprocess + import sys + + stubtest_env = os.environ | {{"STUBTEST_ARGS": json.dumps(sys.argv)}} + gdb_cmd = [ + "gdb", + "--quiet", + "--nx", + "--batch", + "--command", + {str(gdb_script)!r}, + ] + r = subprocess.run(gdb_cmd, env=stubtest_env) + sys.exit(r.returncode) + """ + ) + wrapper_script.write_text(wrapper_script_contents) + + # replace "-m mypy.stubtest" in stubtest_cmd with the path to our wrapper script + assert stubtest_cmd[1:3] == ["-m", "mypy.stubtest"] + stubtest_cmd[1:3] = [str(wrapper_script)] + return True + + def setup_uwsgi_stubtest_command(dist: Path, venv_dir: Path, stubtest_cmd: list[str]) -> bool: """Perform some black magic in order to run stubtest inside uWSGI.