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.
Files changed (171) hide show
  1. {appmap-2.1.0 → appmap-2.1.1}/PKG-INFO +1 -1
  2. {appmap-2.1.0 → appmap-2.1.1}/_appmap/env.py +2 -0
  3. {appmap-2.1.0 → appmap-2.1.1}/_appmap/event.py +12 -10
  4. {appmap-2.1.0 → appmap-2.1.1}/_appmap/importer.py +5 -3
  5. {appmap-2.1.0 → appmap-2.1.1}/_appmap/recorder.py +2 -1
  6. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/conftest.py +46 -5
  7. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/flask/flaskapp.py +12 -1
  8. appmap-2.1.1/_appmap/test/data/flask/test_app.py +26 -0
  9. appmap-2.1.1/_appmap/test/data/flask-instrumented/appmap.yml +4 -0
  10. appmap-2.1.1/_appmap/test/data/flask-instrumented/flaskapp.py +30 -0
  11. appmap-2.1.1/_appmap/test/data/flask-instrumented/test_app.py +31 -0
  12. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/helpers.py +22 -0
  13. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/test_command.py +2 -2
  14. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/test_configuration.py +11 -11
  15. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/test_django.py +0 -3
  16. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/test_django_simplelazyobject.py +2 -1
  17. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/test_events.py +4 -4
  18. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/test_fastapi.py +5 -1
  19. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/test_flask.py +107 -9
  20. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/test_params.py +1 -1
  21. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/test_recording.py +4 -5
  22. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/test_test_frameworks.py +2 -38
  23. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/web_framework.py +11 -15
  24. {appmap-2.1.0 → appmap-2.1.1}/_appmap/web_framework.py +24 -13
  25. {appmap-2.1.0 → appmap-2.1.1}/appmap/django.py +11 -3
  26. {appmap-2.1.0 → appmap-2.1.1}/appmap/fastapi.py +11 -3
  27. {appmap-2.1.0 → appmap-2.1.1}/appmap/flask.py +51 -22
  28. appmap-2.1.1/appmap.pth +1 -0
  29. {appmap-2.1.0 → appmap-2.1.1}/pyproject.toml +3 -1
  30. appmap-2.1.0/_appmap/test/data/flask/test_app.py +0 -14
  31. {appmap-2.1.0 → appmap-2.1.1}/LICENSE +0 -0
  32. {appmap-2.1.0 → appmap-2.1.1}/README.md +0 -0
  33. {appmap-2.1.0 → appmap-2.1.1}/_appmap/__init__.py +0 -0
  34. {appmap-2.1.0 → appmap-2.1.1}/_appmap/configuration.py +0 -0
  35. {appmap-2.1.0 → appmap-2.1.1}/_appmap/django.py +0 -0
  36. {appmap-2.1.0 → appmap-2.1.1}/_appmap/fastapi.py +0 -0
  37. {appmap-2.1.0 → appmap-2.1.1}/_appmap/flask.py +0 -0
  38. {appmap-2.1.0 → appmap-2.1.1}/_appmap/generation.py +0 -0
  39. {appmap-2.1.0 → appmap-2.1.1}/_appmap/instrument.py +0 -0
  40. {appmap-2.1.0 → appmap-2.1.1}/_appmap/labels.py +0 -0
  41. {appmap-2.1.0 → appmap-2.1.1}/_appmap/metadata.py +0 -0
  42. {appmap-2.1.0 → appmap-2.1.1}/_appmap/noappmap.py +0 -0
  43. {appmap-2.1.0 → appmap-2.1.1}/_appmap/py_version_check.py +0 -0
  44. {appmap-2.1.0 → appmap-2.1.1}/_appmap/recording.py +0 -0
  45. {appmap-2.1.0 → appmap-2.1.1}/_appmap/remote_recording.py +0 -0
  46. {appmap-2.1.0 → appmap-2.1.1}/_appmap/singleton.py +0 -0
  47. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/__init__.py +0 -0
  48. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/appmap_test_base.py +0 -0
  49. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/bin/server_runner +0 -0
  50. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/appmap-all-paths-malformed.yml +0 -0
  51. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/appmap-broken.yml +0 -0
  52. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/appmap-class.yml +0 -0
  53. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/appmap-empty-path.yml +0 -0
  54. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/appmap-exclude-fn.yml +0 -0
  55. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/appmap-func.yml +0 -0
  56. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/appmap-malformed-path.yml +0 -0
  57. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/appmap-no-pyyaml.yml +0 -0
  58. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/appmap.yml +0 -0
  59. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/appmap_testing/__init__.py +0 -0
  60. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/appmap_testing/django_simplelazyobject.py +0 -0
  61. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/config/src/package/__init__.py +0 -0
  62. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/config/test/__init__.py +0 -0
  63. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/config/test/foo.py +0 -0
  64. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/config-exclude/.hide/hidden_mod/__init__.py +0 -0
  65. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/config-exclude/node_modules/node_mod/__init__.py +0 -0
  66. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/config-exclude/src/package/__init__.py +0 -0
  67. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/config-exclude/test/__init__.py +0 -0
  68. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/config-exclude/venv/venv_mod/__init__.py +0 -0
  69. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/config-up/appmap.yml +0 -0
  70. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/config-up/project/p1/__init__.py +0 -0
  71. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/config-up/project/p2/sub1/__init__.py +0 -0
  72. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/django/appmap.yml +0 -0
  73. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/django/djangoapp/__init__.py +0 -0
  74. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/django/djangoapp/djangoapp.py +0 -0
  75. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/django/djangoapp/hello_world.html +0 -0
  76. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/django/djangoapp/middleware.py +0 -0
  77. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/django/djangoapp/settings.py +0 -0
  78. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/django/djangoapp/settings_dev.py +0 -0
  79. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/django/djangoapp/urls.py +0 -0
  80. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/django/init/sitecustomize.py +0 -0
  81. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/django/manage.py +0 -0
  82. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/django/pytest.ini +0 -0
  83. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/django/test/__init__.py +0 -0
  84. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/django/test/test_app.py +0 -0
  85. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/django/test/test_unittest_setup.py +0 -0
  86. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/example_class.py +0 -0
  87. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/expected.appmap.json +0 -0
  88. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/fastapi/appmap.yml +0 -0
  89. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/fastapi/fastapiapp/__init__.py +0 -0
  90. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/fastapi/fastapiapp/main.py +0 -0
  91. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/fastapi/init/sitecustomize.py +0 -0
  92. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/fastapi/test_app.py +0 -0
  93. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/flask/appmap.yml +0 -0
  94. {appmap-2.1.0/_appmap/test/data/trial → appmap-2.1.1/_appmap/test/data/flask}/init/sitecustomize.py +0 -0
  95. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/flask/templates/test.html +0 -0
  96. {appmap-2.1.0/_appmap/test/data/flask → appmap-2.1.1/_appmap/test/data/flask-instrumented}/init/sitecustomize.py +0 -0
  97. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/package1/__init__.py +0 -0
  98. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/package1/package2/__init__.py +0 -0
  99. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/package1/package2/__main__.py +0 -0
  100. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/package1/package2/mod1.py +0 -0
  101. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/params.py +0 -0
  102. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/pytest/appmap.yml +0 -0
  103. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/pytest/expected/pytest-numpy1.appmap.json +0 -0
  104. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/pytest/expected/pytest-numpy2.appmap.json +0 -0
  105. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/pytest/expected/status_errored.metadata.json +0 -0
  106. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/pytest/expected/status_failed.metadata.json +0 -0
  107. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/pytest/expected/status_xfailed.metadata.json +0 -0
  108. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/pytest/simple.py +0 -0
  109. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/pytest/test_noappmap.py +0 -0
  110. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/pytest/test_simple.py +0 -0
  111. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/remote.appmap.json +0 -0
  112. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/trial/.gitignore +0 -0
  113. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/trial/appmap.yml +0 -0
  114. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/trial/expected/pytest.appmap.json +0 -0
  115. {appmap-2.1.0/_appmap/test/data/unittest → appmap-2.1.1/_appmap/test/data/trial}/init/sitecustomize.py +0 -0
  116. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/trial/test/__init__.py +0 -0
  117. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/trial/test/test_deferred.py +0 -0
  118. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/unittest/appmap.yml +0 -0
  119. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/unittest/expected/pytest.appmap.json +0 -0
  120. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/unittest/expected/status_errored.metadata.json +0 -0
  121. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/unittest/expected/status_failed.metadata.json +0 -0
  122. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/unittest/expected/status_xfailed.metadata.json +0 -0
  123. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/unittest/expected/status_xsucceeded.metadata.json +0 -0
  124. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/unittest/expected/unittest.appmap.json +0 -0
  125. /appmap-2.1.0/appmap.pth → /appmap-2.1.1/_appmap/test/data/unittest/init/sitecustomize.py +0 -0
  126. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/unittest/simple/__init__.py +0 -0
  127. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/unittest/simple/test_noappmap.py +0 -0
  128. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/data/unittest/simple/test_simple.py +0 -0
  129. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/normalize.py +0 -0
  130. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/test_describe_value.py +0 -0
  131. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/test_env.py +0 -0
  132. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/test_generation.py +0 -0
  133. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/test_http.py +0 -0
  134. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/test_importer.py +0 -0
  135. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/test_labels.py +0 -0
  136. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/test_metadata.py +0 -0
  137. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/test_properties.py +0 -0
  138. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/test_runner.py +0 -0
  139. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/test_sqlalchemy.py +0 -0
  140. {appmap-2.1.0 → appmap-2.1.1}/_appmap/test/test_util.py +0 -0
  141. {appmap-2.1.0 → appmap-2.1.1}/_appmap/testing_framework.py +0 -0
  142. {appmap-2.1.0 → appmap-2.1.1}/_appmap/trace_logger.py +0 -0
  143. {appmap-2.1.0 → appmap-2.1.1}/_appmap/unittest.py +0 -0
  144. {appmap-2.1.0 → appmap-2.1.1}/_appmap/utils.py +0 -0
  145. {appmap-2.1.0 → appmap-2.1.1}/appmap/__init__.py +0 -0
  146. {appmap-2.1.0 → appmap-2.1.1}/appmap/command/__init__.py +0 -0
  147. {appmap-2.1.0 → appmap-2.1.1}/appmap/command/appmap_agent_init.py +0 -0
  148. {appmap-2.1.0 → appmap-2.1.1}/appmap/command/appmap_agent_status.py +0 -0
  149. {appmap-2.1.0 → appmap-2.1.1}/appmap/command/appmap_agent_validate.py +0 -0
  150. {appmap-2.1.0 → appmap-2.1.1}/appmap/command/runner.py +0 -0
  151. {appmap-2.1.0 → appmap-2.1.1}/appmap/http.py +0 -0
  152. {appmap-2.1.0 → appmap-2.1.1}/appmap/labeling/__init__.py +0 -0
  153. {appmap-2.1.0 → appmap-2.1.1}/appmap/labeling/crypto.yml +0 -0
  154. {appmap-2.1.0 → appmap-2.1.1}/appmap/labeling/django.yml +0 -0
  155. {appmap-2.1.0 → appmap-2.1.1}/appmap/labeling/flask.yml +0 -0
  156. {appmap-2.1.0 → appmap-2.1.1}/appmap/labeling/formats.yml +0 -0
  157. {appmap-2.1.0 → appmap-2.1.1}/appmap/labeling/http.yml +0 -0
  158. {appmap-2.1.0 → appmap-2.1.1}/appmap/labeling/jobs.yml +0 -0
  159. {appmap-2.1.0 → appmap-2.1.1}/appmap/labeling/jwt.yml +0 -0
  160. {appmap-2.1.0 → appmap-2.1.1}/appmap/labeling/logger.yml +0 -0
  161. {appmap-2.1.0 → appmap-2.1.1}/appmap/labeling/random.yml +0 -0
  162. {appmap-2.1.0 → appmap-2.1.1}/appmap/pytest.py +0 -0
  163. {appmap-2.1.0 → appmap-2.1.1}/appmap/sqlalchemy.py +0 -0
  164. {appmap-2.1.0 → appmap-2.1.1}/appmap/unittest.py +0 -0
  165. {appmap-2.1.0 → appmap-2.1.1}/appmap/uvicorn.py +0 -0
  166. {appmap-2.1.0 → appmap-2.1.1}/vendor/_appmap/wrapt/LICENSE +0 -0
  167. {appmap-2.1.0 → appmap-2.1.1}/vendor/_appmap/wrapt/__init__.py +0 -0
  168. {appmap-2.1.0 → appmap-2.1.1}/vendor/_appmap/wrapt/arguments.py +0 -0
  169. {appmap-2.1.0 → appmap-2.1.1}/vendor/_appmap/wrapt/decorators.py +0 -0
  170. {appmap-2.1.0 → appmap-2.1.1}/vendor/_appmap/wrapt/importer.py +0 -0
  171. {appmap-2.1.0 → appmap-2.1.1}/vendor/_appmap/wrapt/wrappers.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: appmap
3
- Version: 2.1.0
3
+ Version: 2.1.1
4
4
  Summary: Create AppMap files by recording a Python application.
5
5
  Home-page: https://github.com/applandinc/appmap-python
6
6
  License: MIT
@@ -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
- elif isdict:
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
- response = {"status_code": status_code}
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
- response.update(
487
- {
488
- "mime_type": headers.get("Content-Type"),
489
- "headers": none_if_empty(dict(headers)),
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["doc"] = prop.__doc__
216
- setattr(filterable.obj, prop_name, property(**instrumented_fns))
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
- if event_time - cls.get_current()._start_time > _MAX_TIME:
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 distutils.dir_util import copy_tree
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, TEST_PORT
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
- copy_tree(git_directory, str(tmp_path))
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=TEST_PORT, env=server_env)
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,4 @@
1
+ name: FlaskTest
2
+ packages:
3
+ - path: flaskapp
4
+ - path: flask
@@ -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 distutils.dir_util import copy_tree
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
- copy_tree(data_dir / request.param, str(repo_root))
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 distutils.dir_util import copy_tree
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
- copy_tree(data_dir / "config", str(repo_root))
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
- copy_tree(data_dir / "config", str(tmpdir))
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
- copy_tree(data_dir / "config-exclude", str(tmpdir))
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
- copy_tree(data_dir / "config", str(repo_root))
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
- copy_tree(data_dir / "config", str(repo_root))
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
- copy_tree(data_dir / "config", str(tmpdir))
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
- copy_tree(data_dir / "config-up", str(tmpdir))
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
- copy_tree(data_dir / "config-up", str(tmpdir))
283
- copy_tree(git_directory, str(repo_root))
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
- copy_tree(data_dir / "config-up", str(tmpdir))
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