appmap 2.1.0__tar.gz → 2.1.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {appmap-2.1.0 → appmap-2.1.1}/PKG-INFO +1 -1
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/env.py +2 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/event.py +12 -10
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/importer.py +5 -3
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/recorder.py +2 -1
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/conftest.py +46 -5
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/flask/flaskapp.py +12 -1
- appmap-2.1.1/_appmap/test/data/flask/test_app.py +26 -0
- appmap-2.1.1/_appmap/test/data/flask-instrumented/appmap.yml +4 -0
- appmap-2.1.1/_appmap/test/data/flask-instrumented/flaskapp.py +30 -0
- appmap-2.1.1/_appmap/test/data/flask-instrumented/test_app.py +31 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/helpers.py +22 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/test_command.py +2 -2
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/test_configuration.py +11 -11
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/test_django.py +0 -3
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/test_django_simplelazyobject.py +2 -1
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/test_events.py +4 -4
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/test_fastapi.py +5 -1
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/test_flask.py +107 -9
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/test_params.py +1 -1
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/test_recording.py +4 -5
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/test_test_frameworks.py +2 -38
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/web_framework.py +11 -15
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/web_framework.py +24 -13
- {appmap-2.1.0 → appmap-2.1.1}/appmap/django.py +11 -3
- {appmap-2.1.0 → appmap-2.1.1}/appmap/fastapi.py +11 -3
- {appmap-2.1.0 → appmap-2.1.1}/appmap/flask.py +51 -22
- appmap-2.1.1/appmap.pth +1 -0
- {appmap-2.1.0 → appmap-2.1.1}/pyproject.toml +3 -1
- appmap-2.1.0/_appmap/test/data/flask/test_app.py +0 -14
- {appmap-2.1.0 → appmap-2.1.1}/LICENSE +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/README.md +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/__init__.py +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/configuration.py +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/django.py +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/fastapi.py +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/flask.py +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/generation.py +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/instrument.py +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/labels.py +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/metadata.py +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/noappmap.py +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/py_version_check.py +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/recording.py +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/remote_recording.py +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/singleton.py +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/__init__.py +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/appmap_test_base.py +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/bin/server_runner +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/appmap-all-paths-malformed.yml +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/appmap-broken.yml +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/appmap-class.yml +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/appmap-empty-path.yml +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/appmap-exclude-fn.yml +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/appmap-func.yml +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/appmap-malformed-path.yml +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/appmap-no-pyyaml.yml +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/appmap.yml +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/appmap_testing/__init__.py +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/appmap_testing/django_simplelazyobject.py +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/config/src/package/__init__.py +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/config/test/__init__.py +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/config/test/foo.py +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/config-exclude/.hide/hidden_mod/__init__.py +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/config-exclude/node_modules/node_mod/__init__.py +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/config-exclude/src/package/__init__.py +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/config-exclude/test/__init__.py +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/config-exclude/venv/venv_mod/__init__.py +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/config-up/appmap.yml +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/config-up/project/p1/__init__.py +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/config-up/project/p2/sub1/__init__.py +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/django/appmap.yml +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/django/djangoapp/__init__.py +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/django/djangoapp/djangoapp.py +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/django/djangoapp/hello_world.html +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/django/djangoapp/middleware.py +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/django/djangoapp/settings.py +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/django/djangoapp/settings_dev.py +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/django/djangoapp/urls.py +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/django/init/sitecustomize.py +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/django/manage.py +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/django/pytest.ini +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/django/test/__init__.py +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/django/test/test_app.py +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/django/test/test_unittest_setup.py +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/example_class.py +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/expected.appmap.json +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/fastapi/appmap.yml +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/fastapi/fastapiapp/__init__.py +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/fastapi/fastapiapp/main.py +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/fastapi/init/sitecustomize.py +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/fastapi/test_app.py +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/flask/appmap.yml +0 -0
- {appmap-2.1.0/_appmap/test/data/trial → appmap-2.1.1/_appmap/test/data/flask}/init/sitecustomize.py +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/flask/templates/test.html +0 -0
- {appmap-2.1.0/_appmap/test/data/flask → appmap-2.1.1/_appmap/test/data/flask-instrumented}/init/sitecustomize.py +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/package1/__init__.py +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/package1/package2/__init__.py +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/package1/package2/__main__.py +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/package1/package2/mod1.py +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/params.py +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/pytest/appmap.yml +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/pytest/expected/pytest-numpy1.appmap.json +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/pytest/expected/pytest-numpy2.appmap.json +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/pytest/expected/status_errored.metadata.json +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/pytest/expected/status_failed.metadata.json +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/pytest/expected/status_xfailed.metadata.json +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/pytest/simple.py +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/pytest/test_noappmap.py +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/pytest/test_simple.py +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/remote.appmap.json +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/trial/.gitignore +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/trial/appmap.yml +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/trial/expected/pytest.appmap.json +0 -0
- {appmap-2.1.0/_appmap/test/data/unittest → appmap-2.1.1/_appmap/test/data/trial}/init/sitecustomize.py +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/trial/test/__init__.py +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/trial/test/test_deferred.py +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/unittest/appmap.yml +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/unittest/expected/pytest.appmap.json +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/unittest/expected/status_errored.metadata.json +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/unittest/expected/status_failed.metadata.json +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/unittest/expected/status_xfailed.metadata.json +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/unittest/expected/status_xsucceeded.metadata.json +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/unittest/expected/unittest.appmap.json +0 -0
- /appmap-2.1.0/appmap.pth → /appmap-2.1.1/_appmap/test/data/unittest/init/sitecustomize.py +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/unittest/simple/__init__.py +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/unittest/simple/test_noappmap.py +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/unittest/simple/test_simple.py +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/normalize.py +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/test_describe_value.py +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/test_env.py +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/test_generation.py +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/test_http.py +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/test_importer.py +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/test_labels.py +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/test_metadata.py +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/test_properties.py +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/test_runner.py +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/test_sqlalchemy.py +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/test_util.py +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/testing_framework.py +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/trace_logger.py +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/unittest.py +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/_appmap/utils.py +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/appmap/__init__.py +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/appmap/command/__init__.py +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/appmap/command/appmap_agent_init.py +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/appmap/command/appmap_agent_status.py +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/appmap/command/appmap_agent_validate.py +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/appmap/command/runner.py +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/appmap/http.py +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/appmap/labeling/__init__.py +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/appmap/labeling/crypto.yml +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/appmap/labeling/django.yml +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/appmap/labeling/flask.yml +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/appmap/labeling/formats.yml +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/appmap/labeling/http.yml +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/appmap/labeling/jobs.yml +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/appmap/labeling/jwt.yml +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/appmap/labeling/logger.yml +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/appmap/labeling/random.yml +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/appmap/pytest.py +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/appmap/sqlalchemy.py +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/appmap/unittest.py +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/appmap/uvicorn.py +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/vendor/_appmap/wrapt/LICENSE +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/vendor/_appmap/wrapt/__init__.py +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/vendor/_appmap/wrapt/arguments.py +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/vendor/_appmap/wrapt/decorators.py +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/vendor/_appmap/wrapt/importer.py +0 -0
- {appmap-2.1.0 → appmap-2.1.1}/vendor/_appmap/wrapt/wrappers.py +0 -0
|
@@ -39,6 +39,8 @@ class Env(metaclass=SingletonMeta):
|
|
|
39
39
|
|
|
40
40
|
self.log_file_creation_failed = False
|
|
41
41
|
self._configure_logging()
|
|
42
|
+
# This uses _APPMAP, rather than APPMAP, to control whether instrumentation is enabled. The
|
|
43
|
+
# tests use this split to make it easier to control recording.
|
|
42
44
|
enabled = self._env.get("_APPMAP", "false")
|
|
43
45
|
self._enabled = enabled is None or enabled.lower() != "false"
|
|
44
46
|
|
|
@@ -94,7 +94,8 @@ def _describe_schema(name, val, depth, max_depth):
|
|
|
94
94
|
if islist:
|
|
95
95
|
elts = [(None, v) for v in val]
|
|
96
96
|
schema_key = "items"
|
|
97
|
-
|
|
97
|
+
else:
|
|
98
|
+
assert isdict
|
|
98
99
|
elts = val.items()
|
|
99
100
|
schema_key = "properties"
|
|
100
101
|
|
|
@@ -479,18 +480,19 @@ class HttpResponseEvent(ReturnEvent):
|
|
|
479
480
|
|
|
480
481
|
def __init__(self, status_code, headers=None, **kwargs):
|
|
481
482
|
super().__init__(**kwargs)
|
|
483
|
+
self.response = {}
|
|
484
|
+
self.update(status_code, headers)
|
|
482
485
|
|
|
483
|
-
|
|
486
|
+
def update(self, status_code, headers):
|
|
487
|
+
if status_code is not None:
|
|
488
|
+
self.response.update({"status_code": status_code})
|
|
484
489
|
|
|
485
490
|
if headers is not None:
|
|
486
|
-
|
|
487
|
-
{
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
}
|
|
491
|
-
)
|
|
492
|
-
|
|
493
|
-
self.response = compact_dict(response)
|
|
491
|
+
if "Content-Type" in headers:
|
|
492
|
+
self.response.update({"mime_type": headers.get("Content-Type")})
|
|
493
|
+
updated_headers = dict(headers)
|
|
494
|
+
if len(updated_headers) > 0:
|
|
495
|
+
self.response.update({"headers": updated_headers})
|
|
494
496
|
|
|
495
497
|
|
|
496
498
|
# pylint: disable=too-few-public-methods
|
|
@@ -205,15 +205,17 @@ class Importer:
|
|
|
205
205
|
for prop_name, (prop, prop_fns) in properties.items():
|
|
206
206
|
instrumented_fns = {}
|
|
207
207
|
for k, (fn, auxtype) in prop_fns.items():
|
|
208
|
-
if fn is None:
|
|
208
|
+
if fn is None or getattr(fn, "_appmap_wrapped", None):
|
|
209
209
|
continue
|
|
210
210
|
filterableFn = FilterableFn(filterable, fn, fn, auxtype)
|
|
211
211
|
new_fn = cls.instrument_function(fn.__name__, filterableFn, selected_functions)
|
|
212
212
|
if new_fn != fn:
|
|
213
213
|
new_fn = wrapt.FunctionWrapper(fn, new_fn)
|
|
214
|
+
setattr(new_fn, "_appmap_wrapped", True)
|
|
214
215
|
instrumented_fns[k] = new_fn
|
|
215
|
-
instrumented_fns
|
|
216
|
-
|
|
216
|
+
if len(instrumented_fns) > 0:
|
|
217
|
+
instrumented_fns["doc"] = prop.__doc__
|
|
218
|
+
setattr(filterable.obj, prop_name, property(**instrumented_fns))
|
|
217
219
|
|
|
218
220
|
# Import Config here, to avoid circular top-level imports.
|
|
219
221
|
from .configuration import Config # pylint: disable=import-outside-toplevel
|
|
@@ -114,7 +114,8 @@ class Recorder(ABC):
|
|
|
114
114
|
def check_time(cls, event_time):
|
|
115
115
|
if _MAX_TIME is None:
|
|
116
116
|
return
|
|
117
|
-
|
|
117
|
+
delta = event_time - cls.get_current()._start_time # pylint: disable=protected-access
|
|
118
|
+
if delta > _MAX_TIME:
|
|
118
119
|
raise AppMapSessionTooLong(f"Session exceeded {_MAX_TIME} seconds")
|
|
119
120
|
|
|
120
121
|
@classmethod
|
|
@@ -2,7 +2,7 @@ import importlib
|
|
|
2
2
|
import os
|
|
3
3
|
import socket
|
|
4
4
|
import sys
|
|
5
|
-
from
|
|
5
|
+
from shutil import copytree
|
|
6
6
|
from functools import partial, partialmethod
|
|
7
7
|
from pathlib import Path
|
|
8
8
|
from typing import Any
|
|
@@ -14,7 +14,7 @@ from xprocess import ProcessStarter
|
|
|
14
14
|
|
|
15
15
|
import _appmap
|
|
16
16
|
import appmap
|
|
17
|
-
from _appmap.test.web_framework import TEST_HOST
|
|
17
|
+
from _appmap.test.web_framework import TEST_HOST
|
|
18
18
|
from appmap import generation
|
|
19
19
|
|
|
20
20
|
from .. import utils
|
|
@@ -98,7 +98,7 @@ def git_directory_fixture(tmp_path_factory):
|
|
|
98
98
|
|
|
99
99
|
@pytest.fixture(name="git")
|
|
100
100
|
def tmp_git(git_directory, tmp_path):
|
|
101
|
-
|
|
101
|
+
copytree(git_directory, str(tmp_path), dirs_exist_ok=True)
|
|
102
102
|
return utils.git(cwd=tmp_path)
|
|
103
103
|
|
|
104
104
|
|
|
@@ -198,14 +198,55 @@ def server_starter(info, name, cmd, pattern, env=None):
|
|
|
198
198
|
|
|
199
199
|
return _starter
|
|
200
200
|
|
|
201
|
+
@pytest.fixture(name="server_port")
|
|
202
|
+
def server_port_fixture(worker_id):
|
|
203
|
+
if worker_id == "master":
|
|
204
|
+
offset = "0"
|
|
205
|
+
else:
|
|
206
|
+
offset = worker_id[2:]
|
|
207
|
+
return 8000 + int(offset)
|
|
208
|
+
|
|
201
209
|
|
|
202
210
|
@pytest.fixture(name="server_base")
|
|
203
|
-
def server_base_fixture(request):
|
|
211
|
+
def server_base_fixture(request, server_port):
|
|
204
212
|
marker = request.node.get_closest_marker("server")
|
|
205
213
|
debug = marker.kwargs.get("debug", False)
|
|
206
214
|
server_env = os.environ.copy()
|
|
207
215
|
server_env.update(marker.kwargs.get("env", {}))
|
|
208
216
|
|
|
209
|
-
info = ServerInfo(debug=debug, host=TEST_HOST, port=
|
|
217
|
+
info = ServerInfo(debug=debug, host=TEST_HOST, port=server_port, env=server_env)
|
|
210
218
|
info.factory = partial(server_starter, info)
|
|
211
219
|
return info
|
|
220
|
+
|
|
221
|
+
@pytest.fixture(name="testdir")
|
|
222
|
+
def testdir_fixture(request, data_dir, pytester, monkeypatch):
|
|
223
|
+
# We need to set environment variables to control how tests are run. This will only work
|
|
224
|
+
# properly if pytester runs pytest in a subprocess.
|
|
225
|
+
assert (
|
|
226
|
+
pytester._method == "subprocess" # pylint:disable=protected-access
|
|
227
|
+
), "must run pytest in a subprocess"
|
|
228
|
+
|
|
229
|
+
# The init subdirectory contains a sitecustomize.py file that
|
|
230
|
+
# imports the appmap module. This simulates the way a real
|
|
231
|
+
# installation works, performing the same function as the the
|
|
232
|
+
# appmap.pth file that gets put in site-packages.
|
|
233
|
+
monkeypatch.setenv("PYTHONPATH", "init")
|
|
234
|
+
|
|
235
|
+
# Make sure _APPMAP isn't in the environment, to test that recording-by-default is working as
|
|
236
|
+
# expected. Individual test cases may set it as necessary.
|
|
237
|
+
monkeypatch.delenv("_APPMAP", raising=False)
|
|
238
|
+
|
|
239
|
+
marker = request.node.get_closest_marker("example_dir")
|
|
240
|
+
test_type = "unittest" if marker is None else marker.args[0]
|
|
241
|
+
pytester.copy_example(test_type)
|
|
242
|
+
|
|
243
|
+
pytester.expected = data_dir / test_type / "expected"
|
|
244
|
+
pytester.test_type = test_type
|
|
245
|
+
|
|
246
|
+
# this is so test_type can be overriden in test cases
|
|
247
|
+
def output_dir():
|
|
248
|
+
return pytester.path / "tmp" / "appmap" / pytester.test_type
|
|
249
|
+
|
|
250
|
+
pytester.output = output_dir
|
|
251
|
+
|
|
252
|
+
return pytester
|
|
@@ -6,8 +6,9 @@ testing of record-by-default.
|
|
|
6
6
|
"""
|
|
7
7
|
# pylint: disable=missing-function-docstring
|
|
8
8
|
|
|
9
|
-
from flask import Flask, make_response
|
|
9
|
+
from flask import Flask, make_response, request
|
|
10
10
|
from markupsafe import escape
|
|
11
|
+
import werkzeug
|
|
11
12
|
|
|
12
13
|
app = Flask(__name__)
|
|
13
14
|
|
|
@@ -50,3 +51,13 @@ def show_org_user_posts(org, username):
|
|
|
50
51
|
@app.route("/exception")
|
|
51
52
|
def raise_exception():
|
|
52
53
|
raise Exception("An exception")
|
|
54
|
+
|
|
55
|
+
@app.post("/do_post")
|
|
56
|
+
def do_post():
|
|
57
|
+
_ = request.get_json()
|
|
58
|
+
return "Got post request"
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@app.errorhandler(werkzeug.exceptions.BadRequest)
|
|
62
|
+
def handle_bad_request(e):
|
|
63
|
+
return "That's a bad request!", 400
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from flaskapp import app
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
@pytest.fixture(name="client")
|
|
6
|
+
def test_client():
|
|
7
|
+
with app.test_client() as c: # pylint: disable=no-member
|
|
8
|
+
yield c
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def test_request(client):
|
|
12
|
+
response = client.get("/")
|
|
13
|
+
|
|
14
|
+
assert response.status_code == 200
|
|
15
|
+
|
|
16
|
+
def test_not_found(client):
|
|
17
|
+
response = client.get("/not_found")
|
|
18
|
+
|
|
19
|
+
assert response.status_code == 404
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def test_errorhandler(client):
|
|
23
|
+
response = client.post("/do_post", content_type="application/json")
|
|
24
|
+
|
|
25
|
+
assert response.status_code == 400
|
|
26
|
+
assert response.text == "That's a bad request!"
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Rudimentary Flask application for testing.
|
|
3
|
+
"""
|
|
4
|
+
# pylint: disable=missing-function-docstring
|
|
5
|
+
|
|
6
|
+
import werkzeug
|
|
7
|
+
from appmap.flask import AppmapFlask
|
|
8
|
+
from flask import Flask, request
|
|
9
|
+
|
|
10
|
+
app = Flask(__name__)
|
|
11
|
+
AppmapFlask(app).init_app()
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@app.route("/")
|
|
15
|
+
def hello_world():
|
|
16
|
+
return "Hello, World!"
|
|
17
|
+
|
|
18
|
+
@app.route("/exception")
|
|
19
|
+
def raise_exception():
|
|
20
|
+
raise Exception("An exception")
|
|
21
|
+
|
|
22
|
+
@app.post("/do_post")
|
|
23
|
+
def do_post():
|
|
24
|
+
_ = request.get_json()
|
|
25
|
+
return "Got post request"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@app.errorhandler(werkzeug.exceptions.BadRequest)
|
|
29
|
+
def handle_bad_request(e):
|
|
30
|
+
return "That's a bad request!", 400
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from flaskapp import app
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
@pytest.fixture(name="client")
|
|
6
|
+
def test_client():
|
|
7
|
+
with app.test_client() as c: # pylint: disable=no-member
|
|
8
|
+
yield c
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def test_request(client):
|
|
12
|
+
response = client.get("/")
|
|
13
|
+
|
|
14
|
+
assert response.status_code == 200
|
|
15
|
+
|
|
16
|
+
def test_exception(client):
|
|
17
|
+
response = client.get("/exception")
|
|
18
|
+
|
|
19
|
+
assert response.status_code == 500
|
|
20
|
+
|
|
21
|
+
def test_not_found(client):
|
|
22
|
+
response = client.get("/not_found")
|
|
23
|
+
|
|
24
|
+
assert response.status_code == 404
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def test_errorhandler(client):
|
|
28
|
+
response = client.post("/do_post", content_type="application/json")
|
|
29
|
+
|
|
30
|
+
assert response.status_code == 400
|
|
31
|
+
assert response.text == "That's a bad request!"
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
"""Test helpers"""
|
|
2
2
|
|
|
3
3
|
|
|
4
|
+
import importlib.metadata
|
|
5
|
+
|
|
6
|
+
from packaging import version as pkg_version
|
|
7
|
+
|
|
8
|
+
|
|
4
9
|
class DictIncluding(dict):
|
|
5
10
|
"""A dict that on comparison just checks whether the other dict includes
|
|
6
11
|
all of its items. Any extra ones are ignored.
|
|
@@ -26,3 +31,20 @@ class HeadersIncluding(dict):
|
|
|
26
31
|
if v is None:
|
|
27
32
|
return False
|
|
28
33
|
return True
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def package_version(pkg):
|
|
37
|
+
return pkg_version.parse(importlib.metadata.version(pkg))
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def check_call_stack(events):
|
|
41
|
+
"""Ensure that the call stack in events has balanced call and return events"""
|
|
42
|
+
stack = []
|
|
43
|
+
for e in events:
|
|
44
|
+
if e.get("event") == "call":
|
|
45
|
+
stack.append(e)
|
|
46
|
+
elif e.get("event") == "return":
|
|
47
|
+
assert len(stack) > 0, "return without call"
|
|
48
|
+
call = stack.pop()
|
|
49
|
+
assert call.get("id") == e.get("parent_id")
|
|
50
|
+
assert len(stack) == 0, "leftover events"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import json
|
|
2
2
|
import re
|
|
3
|
-
from
|
|
3
|
+
from shutil import copytree
|
|
4
4
|
from importlib.metadata import version
|
|
5
5
|
|
|
6
6
|
import pytest
|
|
@@ -14,7 +14,7 @@ from .helpers import DictIncluding
|
|
|
14
14
|
@pytest.fixture(name="_cmd_setup")
|
|
15
15
|
def _cmd_setup(request, git, data_dir, monkeypatch):
|
|
16
16
|
repo_root = git.cwd
|
|
17
|
-
|
|
17
|
+
copytree(data_dir / request.param, str(repo_root), dirs_exist_ok=True)
|
|
18
18
|
monkeypatch.chdir(repo_root)
|
|
19
19
|
|
|
20
20
|
# pylint: disable=protected-access
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
# pylint: disable=missing-function-docstring
|
|
3
3
|
|
|
4
4
|
from contextlib import contextmanager
|
|
5
|
-
from
|
|
5
|
+
from shutil import copytree
|
|
6
6
|
from pathlib import Path
|
|
7
7
|
from textwrap import dedent
|
|
8
8
|
|
|
@@ -154,7 +154,7 @@ class DefaultHelpers:
|
|
|
154
154
|
class TestDefaultConfig(DefaultHelpers):
|
|
155
155
|
def test_created(self, git, data_dir, monkeypatch):
|
|
156
156
|
repo_root = git.cwd
|
|
157
|
-
|
|
157
|
+
copytree(data_dir / "config", str(repo_root), dirs_exist_ok=True)
|
|
158
158
|
monkeypatch.chdir(repo_root)
|
|
159
159
|
|
|
160
160
|
# pylint: disable=protected-access
|
|
@@ -163,7 +163,7 @@ class TestDefaultConfig(DefaultHelpers):
|
|
|
163
163
|
self.check_default_config(repo_root.name)
|
|
164
164
|
|
|
165
165
|
def test_created_outside_repo(self, data_dir, tmpdir, monkeypatch):
|
|
166
|
-
|
|
166
|
+
copytree(data_dir / "config", str(tmpdir), dirs_exist_ok=True)
|
|
167
167
|
monkeypatch.chdir(tmpdir)
|
|
168
168
|
|
|
169
169
|
# pylint: disable=protected-access
|
|
@@ -180,7 +180,7 @@ class TestDefaultConfig(DefaultHelpers):
|
|
|
180
180
|
assert not appmap.enabled()
|
|
181
181
|
|
|
182
182
|
def test_exclusions(self, data_dir, tmpdir, mocker, monkeypatch):
|
|
183
|
-
|
|
183
|
+
copytree(data_dir / "config-exclude", str(tmpdir), dirs_exist_ok=True)
|
|
184
184
|
monkeypatch.chdir(tmpdir)
|
|
185
185
|
mocker.patch(
|
|
186
186
|
"_appmap.configuration._get_sys_prefix",
|
|
@@ -193,7 +193,7 @@ class TestDefaultConfig(DefaultHelpers):
|
|
|
193
193
|
|
|
194
194
|
def test_created_if_missing_and_enabled(self, git, data_dir, monkeypatch, tmpdir):
|
|
195
195
|
repo_root = git.cwd
|
|
196
|
-
|
|
196
|
+
copytree(data_dir / "config", str(repo_root), dirs_exist_ok=True)
|
|
197
197
|
monkeypatch.chdir(repo_root)
|
|
198
198
|
|
|
199
199
|
path = Path(repo_root / "appmap.yml")
|
|
@@ -213,7 +213,7 @@ class TestDefaultConfig(DefaultHelpers):
|
|
|
213
213
|
|
|
214
214
|
def test_not_created_if_missing_and_not_enabled(self, git, data_dir, monkeypatch):
|
|
215
215
|
repo_root = git.cwd
|
|
216
|
-
|
|
216
|
+
copytree(data_dir / "config", str(repo_root), dirs_exist_ok=True)
|
|
217
217
|
monkeypatch.chdir(repo_root)
|
|
218
218
|
|
|
219
219
|
path = Path(repo_root / "appmap.yml")
|
|
@@ -229,7 +229,7 @@ class TestDefaultConfig(DefaultHelpers):
|
|
|
229
229
|
class TestEmpty(DefaultHelpers):
|
|
230
230
|
@pytest.fixture(autouse=True)
|
|
231
231
|
def setup_config(self, data_dir, monkeypatch, tmpdir):
|
|
232
|
-
|
|
232
|
+
copytree(data_dir / "config", str(tmpdir), dirs_exist_ok=True)
|
|
233
233
|
monkeypatch.chdir(tmpdir)
|
|
234
234
|
|
|
235
235
|
@contextmanager
|
|
@@ -269,7 +269,7 @@ class TestSearchConfig:
|
|
|
269
269
|
# pylint: disable=too-many-arguments
|
|
270
270
|
|
|
271
271
|
def test_config_in_parent_folder(self, data_dir, tmpdir, monkeypatch):
|
|
272
|
-
|
|
272
|
+
copytree(data_dir / "config-up", str(tmpdir), dirs_exist_ok=True)
|
|
273
273
|
wd = tmpdir / "project" / "p1"
|
|
274
274
|
monkeypatch.chdir(wd)
|
|
275
275
|
|
|
@@ -279,8 +279,8 @@ class TestSearchConfig:
|
|
|
279
279
|
assert str(Env.current.output_dir).endswith(str(tmpdir / "tmp" / "appmap"))
|
|
280
280
|
|
|
281
281
|
def _init_repo(self, data_dir, tmpdir, git_directory, repo_root, appmapdir):
|
|
282
|
-
|
|
283
|
-
|
|
282
|
+
copytree(data_dir / "config-up", str(tmpdir), dirs_exist_ok=True)
|
|
283
|
+
copytree(git_directory, str(repo_root), dirs_exist_ok=True)
|
|
284
284
|
with open(appmapdir / "appmap.yml", "w+", encoding="utf-8") as f:
|
|
285
285
|
f.writelines(
|
|
286
286
|
dedent("""
|
|
@@ -325,7 +325,7 @@ class TestSearchConfig:
|
|
|
325
325
|
assert Env.current.enabled
|
|
326
326
|
|
|
327
327
|
def test_config_not_found_in_path_hierarchy(self, data_dir, tmpdir, monkeypatch):
|
|
328
|
-
|
|
328
|
+
copytree(data_dir / "config-up", str(tmpdir), dirs_exist_ok=True)
|
|
329
329
|
wd = tmpdir / "project" / "p1"
|
|
330
330
|
monkeypatch.chdir(wd)
|
|
331
331
|
|
|
@@ -171,9 +171,6 @@ def test_exception(client, events, monkeypatch):
|
|
|
171
171
|
|
|
172
172
|
assert events[1].event == "return"
|
|
173
173
|
assert events[1].parent_id == events[0].id
|
|
174
|
-
assert events[1].exceptions == [
|
|
175
|
-
DictIncluding({"class": "builtins.RuntimeError", "message": "An error"})
|
|
176
|
-
]
|
|
177
174
|
|
|
178
175
|
|
|
179
176
|
@pytest.mark.appmap_enabled(env={"APPMAP_RECORD_REQUESTS": "false"})
|
|
@@ -16,7 +16,8 @@ def test_recording_simplelazyobject_does_not_evaluate():
|
|
|
16
16
|
doesn't cause incorrect premature evaluation.
|
|
17
17
|
"""
|
|
18
18
|
with appmap.Recording():
|
|
19
|
-
import appmap_testing.django_simplelazyobject as ecds # pylint: disable=import-outside-toplevel
|
|
19
|
+
import appmap_testing.django_simplelazyobject as ecds # pylint: disable=import-outside-toplevel, import-error
|
|
20
|
+
|
|
20
21
|
ecds.lazy()
|
|
21
22
|
|
|
22
23
|
# if we're here and the exception wasn't thrown, we're good
|
|
@@ -45,7 +45,7 @@ class TestEvents:
|
|
|
45
45
|
def test_recursion_protection(self):
|
|
46
46
|
r = appmap.Recording()
|
|
47
47
|
with r:
|
|
48
|
-
from example_class import ExampleClass
|
|
48
|
+
from example_class import ExampleClass # pylint: disable=import-outside-toplevel
|
|
49
49
|
|
|
50
50
|
ExampleClass().instance_method()
|
|
51
51
|
|
|
@@ -56,7 +56,7 @@ class TestEvents:
|
|
|
56
56
|
def test_when_str_raises(self, mocker):
|
|
57
57
|
r = appmap.Recording()
|
|
58
58
|
with r:
|
|
59
|
-
from example_class import ExampleClass
|
|
59
|
+
from example_class import ExampleClass # pylint: disable=import-outside-toplevel
|
|
60
60
|
|
|
61
61
|
param = mocker.Mock()
|
|
62
62
|
param.__str__ = mocker.Mock(side_effect=Exception)
|
|
@@ -72,7 +72,7 @@ class TestEvents:
|
|
|
72
72
|
def test_when_both_raise(self, mocker):
|
|
73
73
|
r = appmap.Recording()
|
|
74
74
|
with r:
|
|
75
|
-
from example_class import ExampleClass
|
|
75
|
+
from example_class import ExampleClass # pylint: disable=import-outside-toplevel
|
|
76
76
|
|
|
77
77
|
param = mocker.Mock()
|
|
78
78
|
param.__str__ = mocker.Mock(side_effect=Exception)
|
|
@@ -88,7 +88,7 @@ class TestEvents:
|
|
|
88
88
|
Env.current.set("APPMAP_DISPLAY_PARAMS", "false")
|
|
89
89
|
r = appmap.Recording()
|
|
90
90
|
with r:
|
|
91
|
-
from example_class import ExampleClass
|
|
91
|
+
from example_class import ExampleClass # pylint: disable=import-outside-toplevel
|
|
92
92
|
|
|
93
93
|
param = mocker.MagicMock()
|
|
94
94
|
|
|
@@ -24,6 +24,10 @@ class TestRecordRequests(_TestRecordRequests):
|
|
|
24
24
|
|
|
25
25
|
@pytest.mark.app(remote_enabled=True)
|
|
26
26
|
class TestRemoteRecording(_TestRemoteRecording):
|
|
27
|
+
def __init__(self):
|
|
28
|
+
self.expected_thread_id = None
|
|
29
|
+
self.expected_content_type = None
|
|
30
|
+
|
|
27
31
|
def setup_method(self):
|
|
28
32
|
self.expected_thread_id = 1
|
|
29
33
|
self.expected_content_type = "application/json"
|
|
@@ -39,7 +43,7 @@ def fastapi_app(data_dir, monkeypatch, request):
|
|
|
39
43
|
|
|
40
44
|
Env.current.set("APPMAP_CONFIG", data_dir / "fastapi" / "appmap.yml")
|
|
41
45
|
|
|
42
|
-
from fastapiapp import main # pyright: ignore[reportMissingImports]
|
|
46
|
+
from fastapiapp import main # pyright: ignore[reportMissingImports] pylint: disable=import-error,import-outside-toplevel
|
|
43
47
|
|
|
44
48
|
importlib.reload(main)
|
|
45
49
|
|