diff --git a/_appmap/importer.py b/_appmap/importer.py index ab33b427..93c6bcb0 100644 --- a/_appmap/importer.py +++ b/_appmap/importer.py @@ -173,12 +173,15 @@ def instrument_functions(filterable, selected_functions=None): logger.debug(" functions %s", functions) for fn_name, static_fn, fn in functions: - if selected_functions and fn_name not in selected_functions: - continue - new_fn = cls.filter_chain.wrap(FilterableFn(filterable, fn, static_fn)) - if fn != new_fn: - wrapt.wrap_function_wrapper(filterable.obj, fn_name, new_fn) + # Only instrument the function if it was specifically called out for the package + # (e.g. because it should be labeled), or it's included by the filters + filterableFn = FilterableFn(filterable, fn, static_fn) + matched = cls.filter_chain.filter(filterableFn) + if (selected_functions and fn_name in selected_functions) or matched: + new_fn = cls.filter_chain.wrap(filterableFn) + if fn != new_fn: + wrapt.wrap_function_wrapper(filterable.obj, fn_name, new_fn) # Import Config here, to avoid circular top-level imports. from .configuration import Config # pylint: disable=import-outside-toplevel diff --git a/_appmap/test/conftest.py b/_appmap/test/conftest.py index 4bccae1f..a9f5385b 100644 --- a/_appmap/test/conftest.py +++ b/_appmap/test/conftest.py @@ -1,4 +1,5 @@ import importlib +import sys from distutils.dir_util import copy_tree from functools import partialmethod @@ -65,6 +66,10 @@ def pytest_runtest_setup(item): # Reload it to make sure it's instrumented, or not, as set in appmap.yml. importlib.reload(yaml) + # Remove the example_class module, so it will get reinstrumented the next time it's needed. We + # should find a way to do this more generically, i.e. for any modules loaded by a test case. + sys.modules.pop("example_class", None) + @pytest.fixture(scope="session", name="git_directory") def git_directory_fixture(tmp_path_factory): @@ -100,6 +105,7 @@ def _dj_autoclear_mailbox() -> None: @pytest.fixture(name="verify_example_appmap") def fixture_verify_appmap(monkeypatch): + def _generate(check_fn, method_name): monkeypatch.setattr( generation.FuncEntry, @@ -110,7 +116,9 @@ def _generate(check_fn, method_name): rec = appmap.Recording() with rec: # pylint: disable=import-outside-toplevel, import-error - from example_class import ExampleClass + from example_class import ( # pyright: ignore[reportMissingImports] + ExampleClass, + ) # pylint: enable=import-outside-toplevel, import-error diff --git a/_appmap/test/data/appmap-exclude-fn.yml b/_appmap/test/data/appmap-exclude-fn.yml new file mode 100644 index 00000000..a317cc9b --- /dev/null +++ b/_appmap/test/data/appmap-exclude-fn.yml @@ -0,0 +1,7 @@ + +name: TestApp +packages: +- path: example_class + exclude: + - ExampleClass.another_method +- path: package1.package2.Mod1Class diff --git a/_appmap/test/data/appmap-no-pyyaml.yml b/_appmap/test/data/appmap-no-pyyaml.yml new file mode 100644 index 00000000..5cbfd963 --- /dev/null +++ b/_appmap/test/data/appmap-no-pyyaml.yml @@ -0,0 +1,13 @@ +name: TestApp +packages: +# note: this rule needs to go first, before the more general rule +- path: example_class.Super + shallow: true +- path: example_class +- path: appmap_testing +- path: package1 +labels: + serialization: yaml.dump + example-label: + - example_class.ExampleClass.test_exception + - yaml.dump diff --git a/_appmap/test/test_importer.py b/_appmap/test/test_importer.py index 4559bd13..88edab8b 100644 --- a/_appmap/test/test_importer.py +++ b/_appmap/test/test_importer.py @@ -2,7 +2,10 @@ import sys +import pytest + from _appmap.importer import Importer, wrap_exec_module +from _appmap.wrapt.wrappers import BoundFunctionWrapper def test_exec_module_protection(monkeypatch): @@ -28,3 +31,18 @@ def do_import(*args, **kwargs): # pylint: disable=unused-argument f() assert True + + +@pytest.mark.appmap_enabled(config="appmap-exclude-fn.yml") +@pytest.mark.usefixtures("with_data_dir") +def test_excluded(verify_example_appmap): + def check_imports(*_): + # pylint: disable=import-outside-toplevel, import-error + from example_class import ExampleClass # pyright: ignore[reportMissingImports] + + # pylint: enable=import-outside-toplevel, import-error + + assert isinstance(ExampleClass.instance_method, BoundFunctionWrapper) + assert not isinstance(ExampleClass.another_method, BoundFunctionWrapper) + + verify_example_appmap(check_imports, "instance_method") diff --git a/_appmap/test/test_labels.py b/_appmap/test/test_labels.py index 624217e1..a5a11887 100644 --- a/_appmap/test/test_labels.py +++ b/_appmap/test/test_labels.py @@ -40,6 +40,7 @@ def check_labels(*_): verify_example_appmap(check_labels, "instance_method") + @pytest.mark.appmap_enabled(config="appmap-no-pyyaml.yml") def test_mod_instrumented_by_preset(self, verify_example_appmap): def check_labels(*_): import yaml # pylint: disable=import-outside-toplevel