narrativetrace-pytest 0.2.0__tar.gz → 0.4.0__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.
- {narrativetrace_pytest-0.2.0 → narrativetrace_pytest-0.4.0}/.gitignore +5 -0
- {narrativetrace_pytest-0.2.0 → narrativetrace_pytest-0.4.0}/LICENSE +2 -2
- {narrativetrace_pytest-0.2.0 → narrativetrace_pytest-0.4.0}/PKG-INFO +1 -1
- {narrativetrace_pytest-0.2.0 → narrativetrace_pytest-0.4.0}/pyproject.toml +1 -1
- {narrativetrace_pytest-0.2.0 → narrativetrace_pytest-0.4.0}/src/narrativetrace_pytest/__init__.py +1 -1
- narrativetrace_pytest-0.4.0/tests/fixture_wiring.py +26 -0
- narrativetrace_pytest-0.4.0/tests/test_fixture_wiring.py +28 -0
- {narrativetrace_pytest-0.2.0 → narrativetrace_pytest-0.4.0}/tests/test_plugin.py +61 -0
- {narrativetrace_pytest-0.2.0 → narrativetrace_pytest-0.4.0}/tests/test_structural_plugin.py +65 -0
- {narrativetrace_pytest-0.2.0 → narrativetrace_pytest-0.4.0}/README.md +0 -0
- {narrativetrace_pytest-0.2.0 → narrativetrace_pytest-0.4.0}/src/narrativetrace_pytest/plugin.py +0 -0
- {narrativetrace_pytest-0.2.0 → narrativetrace_pytest-0.4.0}/src/narrativetrace_pytest/py.typed +0 -0
- {narrativetrace_pytest-0.2.0 → narrativetrace_pytest-0.4.0}/tests/_subprocess_coverage/sitecustomize.py +0 -0
- {narrativetrace_pytest-0.2.0 → narrativetrace_pytest-0.4.0}/tests/conftest.py +0 -0
- {narrativetrace_pytest-0.2.0 → narrativetrace_pytest-0.4.0}/tests/test_conformance.py +0 -0
- {narrativetrace_pytest-0.2.0 → narrativetrace_pytest-0.4.0}/tests/test_doctor_truthy_parity.py +0 -0
- {narrativetrace_pytest-0.2.0 → narrativetrace_pytest-0.4.0}/tests/test_run_name_byte_identity.py +0 -0
|
@@ -28,3 +28,8 @@ build/
|
|
|
28
28
|
# narrativetrace-pytest's default per-test artifact directory -- on by default, opt out with
|
|
29
29
|
# NARRATIVETRACE_OUTPUT=false (see documentation/what-to-commit.md)
|
|
30
30
|
narrative-traces/
|
|
31
|
+
|
|
32
|
+
# The recipe scenario's approved baselines are source; the files an approval run writes beside them
|
|
33
|
+
# are not (documentation/what-to-commit.md)
|
|
34
|
+
examples/recipes/checkout/test-narratives/**/*.received.nt
|
|
35
|
+
examples/recipes/checkout/test-narratives/**/*.incomplete.nt
|
|
@@ -7,7 +7,7 @@ Parameters
|
|
|
7
7
|
|
|
8
8
|
Licensor: Empower Agile
|
|
9
9
|
|
|
10
|
-
Licensed Work: NarrativeTrace for Python (the narrativetrace PyPI distributions) version 0.
|
|
10
|
+
Licensed Work: NarrativeTrace for Python (the narrativetrace PyPI distributions) version 0.4.0. The Licensed Work is (c) 2026 Empower Agile.
|
|
11
11
|
|
|
12
12
|
Additional Use Grant: You may make production use of the Licensed Work for any
|
|
13
13
|
purpose, including internal use and use in products and
|
|
@@ -18,7 +18,7 @@ Additional Use Grant: You may make production use of the Licensed Work for any
|
|
|
18
18
|
parties as a logging, tracing, or code-narrative product
|
|
19
19
|
or service.
|
|
20
20
|
|
|
21
|
-
Change Date: Four years from the date the Licensed Work is published. (2030-10-
|
|
21
|
+
Change Date: Four years from the date the Licensed Work is published. (2030-10-16 for this version)
|
|
22
22
|
|
|
23
23
|
Change License: Apache License, Version 2.0
|
|
24
24
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: narrativetrace-pytest
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: pytest plugin for narrativetrace: per-test trace fixtures, artifacts, and reporting
|
|
5
5
|
Project-URL: Homepage, https://github.com/narrativetrace/narrativetrace-python
|
|
6
6
|
Project-URL: Source, https://github.com/narrativetrace/narrativetrace-python
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# SPDX-License-Identifier: BUSL-1.1
|
|
2
|
+
# Licensed under the Business Source License 1.1 (see LICENSE); Change Date: four
|
|
3
|
+
# years from publication; Change License: Apache-2.0
|
|
4
|
+
# Copyright (c) 2026 Empower Agile
|
|
5
|
+
"""The pytest row's wiring, as the framework table shows it: a test that requests the
|
|
6
|
+
``narrative_trace`` fixture and traces through it. Not collected here (the outer session disables
|
|
7
|
+
the plugin); ``test_fixture_wiring.py`` runs it under the real plugin. The doctor's
|
|
8
|
+
``config.pytest-fixture`` fix and ``llms-full.md`` carry the ``wiring`` region verbatim
|
|
9
|
+
(``narrativetrace_tooling/frameworks/wiring-snippets.md``)."""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
# snippet:begin wiring
|
|
14
|
+
from narrativetrace import ContextVarNarrativeContext, trace_object
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def test_place_order(narrative_trace: ContextVarNarrativeContext) -> None:
|
|
18
|
+
service = trace_object(OrderService(), narrative_trace)
|
|
19
|
+
|
|
20
|
+
assert service.place_order("cust-1", "prod-42") == "ORD-cust-1-prod-42"
|
|
21
|
+
# snippet:end wiring
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class OrderService:
|
|
25
|
+
def place_order(self, customer_id: str, product_id: str) -> str:
|
|
26
|
+
return f"ORD-{customer_id}-{product_id}"
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# SPDX-License-Identifier: BUSL-1.1
|
|
2
|
+
# Licensed under the Business Source License 1.1 (see LICENSE); Change Date: four
|
|
3
|
+
# years from publication; Change License: Apache-2.0
|
|
4
|
+
# Copyright (c) 2026 Empower Agile
|
|
5
|
+
"""The pytest row's wiring fixture works as printed: run under the real plugin, its test passes and
|
|
6
|
+
the plugin writes that test's trace."""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
import pytest
|
|
13
|
+
|
|
14
|
+
_FIXTURE = Path(__file__).with_name("fixture_wiring.py")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def test_the_wired_test_passes_and_writes_its_trace(
|
|
18
|
+
pytester: pytest.Pytester, monkeypatch: pytest.MonkeyPatch
|
|
19
|
+
) -> None:
|
|
20
|
+
out_dir = pytester.path / "nt-out"
|
|
21
|
+
monkeypatch.setenv("NARRATIVETRACE_OUTPUT_DIR", str(out_dir))
|
|
22
|
+
pytester.makepyfile(test_wired=_FIXTURE.read_text(encoding="utf-8"))
|
|
23
|
+
|
|
24
|
+
result = pytester.runpytest_subprocess()
|
|
25
|
+
|
|
26
|
+
result.assert_outcomes(passed=1)
|
|
27
|
+
traces = [path.read_text(encoding="utf-8") for path in out_dir.rglob("*.md")]
|
|
28
|
+
assert any("OrderService.place_order" in text for text in traces)
|
|
@@ -914,3 +914,64 @@ def test_a_malformed_committed_glossary_skips_harvesting_without_failing_the_sui
|
|
|
914
914
|
|
|
915
915
|
result.assert_outcomes(passed=1)
|
|
916
916
|
result.stdout.fnmatch_lines(["*glossary harvest failed, skipped*"])
|
|
917
|
+
|
|
918
|
+
|
|
919
|
+
def test_a_failing_checkout_test_gets_its_own_narrative_showing_the_failure(
|
|
920
|
+
pytester: pytest.Pytester, monkeypatch: pytest.MonkeyPatch
|
|
921
|
+
) -> None:
|
|
922
|
+
"""The recipe's "why did this test fail" shape: the payment charge throws, so the failure
|
|
923
|
+
narrative (terminal block and the written trace) shows reserve, then the failing charge, and
|
|
924
|
+
never the order save that was not reached."""
|
|
925
|
+
out_dir = pytester.path / "nt-out"
|
|
926
|
+
monkeypatch.setenv("NARRATIVETRACE_OUTPUT", "1")
|
|
927
|
+
monkeypatch.setenv("NARRATIVETRACE_OUTPUT_DIR", str(out_dir))
|
|
928
|
+
pytester.makepyfile(
|
|
929
|
+
"""
|
|
930
|
+
import pytest
|
|
931
|
+
from narrativetrace.trace_object import trace_object
|
|
932
|
+
|
|
933
|
+
class Inventory:
|
|
934
|
+
def reserve(self, sku): return True
|
|
935
|
+
|
|
936
|
+
class Payment:
|
|
937
|
+
def charge(self, amount): raise ValueError("card declined")
|
|
938
|
+
|
|
939
|
+
class Orders:
|
|
940
|
+
def save(self, order_id): return order_id
|
|
941
|
+
|
|
942
|
+
class Checkout:
|
|
943
|
+
def __init__(self, inventory, payment, orders):
|
|
944
|
+
self.inventory, self.payment, self.orders = inventory, payment, orders
|
|
945
|
+
|
|
946
|
+
def place_order(self, sku):
|
|
947
|
+
self.inventory.reserve(sku)
|
|
948
|
+
self.payment.charge(10)
|
|
949
|
+
return self.orders.save("ORD-1")
|
|
950
|
+
|
|
951
|
+
def test_checkout_charges_the_card(narrative_trace):
|
|
952
|
+
checkout = Checkout(
|
|
953
|
+
trace_object(Inventory(), narrative_trace),
|
|
954
|
+
trace_object(Payment(), narrative_trace),
|
|
955
|
+
trace_object(Orders(), narrative_trace),
|
|
956
|
+
)
|
|
957
|
+
trace_object(checkout, narrative_trace).place_order("sku-1")
|
|
958
|
+
"""
|
|
959
|
+
)
|
|
960
|
+
|
|
961
|
+
result = pytester.runpytest_subprocess()
|
|
962
|
+
|
|
963
|
+
result.assert_outcomes(failed=1)
|
|
964
|
+
result.stdout.fnmatch_lines(
|
|
965
|
+
["*Inventory.reserve*", "*Payment.charge*ValueError*card declined*"]
|
|
966
|
+
)
|
|
967
|
+
traces = (
|
|
968
|
+
out_dir
|
|
969
|
+
/ "traces"
|
|
970
|
+
/ "test_a_failing_checkout_test_gets_its_own_narrative_showing_the_failure"
|
|
971
|
+
)
|
|
972
|
+
narrative = traces / "test_checkout_charges_the_card.md"
|
|
973
|
+
text = narrative.read_text(encoding="utf-8")
|
|
974
|
+
assert "**Result:** FAILED" in text
|
|
975
|
+
assert "Payment.charge" in text
|
|
976
|
+
assert "ValueError" in text
|
|
977
|
+
assert "Orders.save" not in text
|
|
@@ -181,3 +181,68 @@ def test_approval_mode_rejects_a_changed_structure_with_a_readable_diff(
|
|
|
181
181
|
changed.assert_outcomes(passed=1, errors=1)
|
|
182
182
|
changed.stdout.fnmatch_lines(["*Structure changed against the approved trace*"])
|
|
183
183
|
assert next(approved_dir.rglob("*.received.nt")).is_file()
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
_CHECKOUT_COMMON = """
|
|
187
|
+
from narrativetrace.trace_object import trace_object
|
|
188
|
+
|
|
189
|
+
class Inventory:
|
|
190
|
+
def reserve(self, sku):
|
|
191
|
+
return True
|
|
192
|
+
|
|
193
|
+
class Payment:
|
|
194
|
+
def charge(self, amount):
|
|
195
|
+
return "paid"
|
|
196
|
+
|
|
197
|
+
class Orders:
|
|
198
|
+
def save(self, order_id):
|
|
199
|
+
return order_id
|
|
200
|
+
|
|
201
|
+
class Checkout:
|
|
202
|
+
def __init__(self, inventory, payment, orders):
|
|
203
|
+
self.inventory, self.payment, self.orders = inventory, payment, orders
|
|
204
|
+
|
|
205
|
+
def place_order(self, sku):
|
|
206
|
+
self.inventory.reserve(sku)
|
|
207
|
+
%s
|
|
208
|
+
return self.orders.save("ORD-1")
|
|
209
|
+
|
|
210
|
+
def test_arithmetic_is_unaffected():
|
|
211
|
+
assert 1 + 1 == 2
|
|
212
|
+
|
|
213
|
+
def test_place_order(narrative_trace):
|
|
214
|
+
checkout = Checkout(
|
|
215
|
+
trace_object(Inventory(), narrative_trace),
|
|
216
|
+
trace_object(Payment(), narrative_trace),
|
|
217
|
+
trace_object(Orders(), narrative_trace),
|
|
218
|
+
)
|
|
219
|
+
trace_object(checkout, narrative_trace).place_order("sku-1")
|
|
220
|
+
"""
|
|
221
|
+
_CHECKOUT_BASELINE_SOURCE = _CHECKOUT_COMMON % " self.payment.charge(10)"
|
|
222
|
+
_CHECKOUT_DROPPED_CHARGE_SOURCE = _CHECKOUT_COMMON % " pass"
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def test_approval_mode_names_a_dropped_call_while_the_ordinary_tests_stay_green(
|
|
226
|
+
pytester: pytest.Pytester, monkeypatch: pytest.MonkeyPatch
|
|
227
|
+
) -> None:
|
|
228
|
+
"""The recipe's refactoring-review shape: checkout reserves inventory, charges payment and
|
|
229
|
+
saves the order; a change drops the payment charge and the run must fail naming it."""
|
|
230
|
+
approved_dir = pytester.path / "approved"
|
|
231
|
+
monkeypatch.setenv("NARRATIVETRACE_APPROVAL", "true")
|
|
232
|
+
monkeypatch.setenv("NARRATIVETRACE_APPROVED_DIR", str(approved_dir))
|
|
233
|
+
pytester.makepyfile(_CHECKOUT_BASELINE_SOURCE)
|
|
234
|
+
pytester.runpytest_subprocess("-s").assert_outcomes(passed=2, errors=1)
|
|
235
|
+
received = next(approved_dir.rglob("*.received.nt"))
|
|
236
|
+
received.replace(received.with_name(received.name.replace(".received.nt", ".approved.nt")))
|
|
237
|
+
pytester.runpytest_subprocess("-s").assert_outcomes(passed=2)
|
|
238
|
+
|
|
239
|
+
pytester.makepyfile(_CHECKOUT_DROPPED_CHARGE_SOURCE)
|
|
240
|
+
dropped = pytester.runpytest_subprocess("-s")
|
|
241
|
+
|
|
242
|
+
dropped.assert_outcomes(passed=2, errors=1)
|
|
243
|
+
dropped.stdout.fnmatch_lines(
|
|
244
|
+
[
|
|
245
|
+
"*Structure changed against the approved trace (-1 call Payment.charge):*",
|
|
246
|
+
"*- #1.2 - Payment.charge(amount) *",
|
|
247
|
+
]
|
|
248
|
+
)
|
|
File without changes
|
{narrativetrace_pytest-0.2.0 → narrativetrace_pytest-0.4.0}/src/narrativetrace_pytest/plugin.py
RENAMED
|
File without changes
|
{narrativetrace_pytest-0.2.0 → narrativetrace_pytest-0.4.0}/src/narrativetrace_pytest/py.typed
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{narrativetrace_pytest-0.2.0 → narrativetrace_pytest-0.4.0}/tests/test_doctor_truthy_parity.py
RENAMED
|
File without changes
|
{narrativetrace_pytest-0.2.0 → narrativetrace_pytest-0.4.0}/tests/test_run_name_byte_identity.py
RENAMED
|
File without changes
|