MIL-002: speed calc decorator #20
No files matched your search
@@ -1,134 +1,174 @@
|
||||
# ⏱️ Speed Calc Decorator
|
||||
|
||||
A Python decorator that measures how many seconds a function takes to run. It is
|
||||
my solution to a lecture exercise from Udemy's *100 Days of Code: The Complete
|
||||
Python Pro Bootcamp*, written for fellow course participants and for anyone
|
||||
browsing for ideas. It has no runtime dependencies.
|
||||
|
||||
## 📚 Table of Contents
|
||||
|
||||
- [Overview](#-overview)
|
||||
- [Requirements](#-requirements)
|
||||
- [Setup](#-setup)
|
||||
- [Run](#-run)
|
||||
- [Tests](#-tests)
|
||||
- [Documentation](#-documentation)
|
||||
- [License](#-license)
|
||||
- [Links](#-links)
|
||||
- [Appendix: Project documents](#-appendix-project-documents)
|
||||
|
||||
## 🧭 Overview
|
||||
|
||||
The assignment: write `speed_calc_decorator` and use it to print how long
|
||||
`fast_function` and `slow_function` take. Expected output has this shape:
|
||||
|
||||
```text
|
||||
1695050908.1985211
|
||||
fast_function run speed: 0.33974480628967285s
|
||||
slow_function run speed: 2.9590742588043213s
|
||||
```
|
||||
|
||||
The numbers differ on every machine. The repository is laid out as:
|
||||
|
||||
| Folder | Purpose |
|
||||
| --- | --- |
|
||||
| `src/speed_calc/` | The package: the decorator, the two demo functions, `constants.py` |
|
||||
| `tests/` | pytest tests; they use a fake clock, so they never depend on real time |
|
||||
| `docs/` | Project documents (Business Case, plan, milestones), screenshots and the wiki |
|
||||
|
||||
## 📋 Requirements
|
||||
|
||||
- Python 3.13 or newer (`python --version`).
|
||||
- `git`, to clone the repository.
|
||||
- Optional: [Doxygen](https://www.doxygen.nl/) to build the API documentation.
|
||||
|
||||
There are no runtime dependencies. `pytest` is needed only to run the tests.
|
||||
|
||||
## 🛠️ Setup
|
||||
|
||||
Create a local virtual environment named `.venv` in the project folder and
|
||||
activate it. Windows (PowerShell):
|
||||
|
||||
```powershell
|
||||
python -m venv .venv
|
||||
.venv\Scripts\Activate.ps1
|
||||
```
|
||||
|
||||
Linux and macOS:
|
||||
|
||||
```bash
|
||||
python3 -m venv .venv
|
||||
source .venv/bin/activate
|
||||
```
|
||||
|
||||
Upgrade pip, then install the project with the test tools:
|
||||
|
||||
```bash
|
||||
python -m pip install --upgrade pip
|
||||
python -m pip install -e ".[dev]"
|
||||
```
|
||||
|
||||
Leave the environment with `deactivate`. Delete the `.venv` folder to start over.
|
||||
The `.venv` folder is ignored by git.
|
||||
|
||||
## ▶️ Run
|
||||
|
||||
With the environment active:
|
||||
|
||||
```bash
|
||||
python -m speed_calc
|
||||
```
|
||||
|
||||
## 🧪 Tests
|
||||
|
||||
```bash
|
||||
python -m pytest
|
||||
```
|
||||
|
||||
## 📖 Documentation
|
||||
|
||||
The source uses Doxygen comments. Build the HTML documentation into
|
||||
`docs/doxygen/`:
|
||||
|
||||
```bash
|
||||
doxygen Doxyfile
|
||||
```
|
||||
|
||||
## 📄 License
|
||||
|
||||
GNU Affero General Public License v3.0, see [LICENSE](./LICENSE).
|
||||
|
||||
## 🔗 Links
|
||||
|
||||
- [Repository][repository]
|
||||
- [Wiki][documentation]
|
||||
- [Issue tracker][issues]
|
||||
|
||||
## 📎 Appendix: Project documents
|
||||
|
||||
| Document | What it tells you |
|
||||
| --- | --- |
|
||||
| [AGENTS.md][agents] | The rules AI agents follow in this repository |
|
||||
| [Business Case][BC-001] | Why the project exists, its scope and success criteria |
|
||||
| [Stakeholder Analysis][SA-001] | Who is involved; the stakeholder IDs used for owners and reviewers |
|
||||
| [Project Plan][PP-001] | The gateways and their schedule |
|
||||
| [Milestones][milestones] | One document per gateway: Go/No-Go criteria and tasks |
|
||||
| [Artifact registry][registry] | Where each artifact type lives, the next version, the PO language and domain |
|
||||
| [Traceability matrix][TM-001] | How the artifacts link and when each was last reviewed |
|
||||
| [Framework README][framework-readme] | The framework this project uses |
|
||||
| [Framework changelog][framework-changelog] | What changed in each framework release |
|
||||
|
||||
---
|
||||
|
||||
[repository]: https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/054-speed-calc-decorator
|
||||
[documentation]: https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/054-speed-calc-decorator/wiki
|
||||
[issues]: https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/054-speed-calc-decorator/issues
|
||||
[agents]: ./AGENTS.md
|
||||
[BC-001]: ./docs/business-case.md
|
||||
[SA-001]: ./docs/stakeholder-analysis.md
|
||||
[PP-001]: ./docs/project-plan.md
|
||||
[milestones]: ./docs/milestones/
|
||||
[registry]: ./docs/artifact-registry.md
|
||||
[TM-001]: ./docs/sqa/traceability-matrix.md
|
||||
[framework-readme]: ./framework/README.md
|
||||
[framework-changelog]: ./framework/CHANGELOG.md
|
||||
# ⏱️ Speed Calc Decorator
|
||||
|
||||
A Python decorator that measures how many seconds a function takes to run. It is
|
||||
my solution to a lecture exercise from Udemy's *100 Days of Code: The Complete
|
||||
Python Pro Bootcamp*, written for fellow course participants and for anyone
|
||||
browsing for ideas. It has no runtime dependencies.
|
||||
|
||||
## 📚 Table of Contents
|
||||
|
||||
- [Overview](#-overview)
|
||||
- [Requirements](#-requirements)
|
||||
- [Setup](#-setup)
|
||||
- [Run](#-run)
|
||||
- [Lecture steps](#-lecture-steps)
|
||||
- [Tests](#-tests)
|
||||
- [Documentation](#-documentation)
|
||||
- [License](#-license)
|
||||
- [Links](#-links)
|
||||
- [Appendix: Project documents](#-appendix-project-documents)
|
||||
|
||||
## 🧭 Overview
|
||||
|
||||
The assignment: write `speed_calc_decorator` and use it to print how long
|
||||
`fast_function` and `slow_function` take. Expected output has this shape:
|
||||
|
||||
```text
|
||||
1695050908.1985211
|
||||
fast_function run speed: 0.33974480628967285s
|
||||
slow_function run speed: 2.9590742588043213s
|
||||
```
|
||||
|
||||
The numbers differ on every machine. The repository is laid out as:
|
||||
|
||||
| Folder | Purpose |
|
||||
| --- | --- |
|
||||
| `src/speed_calc/` | The package: the decorator, the two demo functions, `constants.py` |
|
||||
| `tests/` | pytest tests; they use a fake clock, so they never depend on real time |
|
||||
| `docs/` | Project documents (Business Case, plan, milestones), screenshots and the wiki |
|
||||
|
||||
## 📋 Requirements
|
||||
|
||||
- Python 3.13 or newer (`python --version`).
|
||||
- `git`, to clone the repository.
|
||||
- Optional: [Doxygen](https://www.doxygen.nl/) to build the API documentation.
|
||||
|
||||
There are no runtime dependencies. `pytest` is needed only to run the tests.
|
||||
|
||||
## 🛠️ Setup
|
||||
|
||||
Create a local virtual environment named `.venv` in the project folder and
|
||||
activate it. Windows (PowerShell):
|
||||
|
||||
```powershell
|
||||
python -m venv .venv
|
||||
.venv\Scripts\Activate.ps1
|
||||
```
|
||||
|
||||
Linux and macOS:
|
||||
|
||||
```bash
|
||||
python3 -m venv .venv
|
||||
source .venv/bin/activate
|
||||
```
|
||||
|
||||
Upgrade pip, then install the project with the test tools:
|
||||
|
||||
```bash
|
||||
python -m pip install --upgrade pip
|
||||
python -m pip install -e ".[dev]"
|
||||
```
|
||||
|
||||
Leave the environment with `deactivate`. Delete the `.venv` folder to start over.
|
||||
The `.venv` folder is ignored by git.
|
||||
|
||||
## ▶️ Run
|
||||
|
||||
With the environment active:
|
||||
|
||||
```bash
|
||||
python -m speed_calc
|
||||
```
|
||||
|
||||
It prints the current time, then the run time of `fast_function` and
|
||||
`slow_function`. The values depend on your machine.
|
||||
|
||||
## 🪜 Lecture steps
|
||||
|
||||
Each step runs on its own with `python -m speed_calc.lecture_steps N` (N is 1 to
|
||||
4); step 4 is the same as `python -m speed_calc`. The screenshots are rendered
|
||||
from the real output of each command by
|
||||
[`docs/screenshots/generate_screenshots.py`](./docs/screenshots/generate_screenshots.py).
|
||||
|
||||
### Step 1: the current time
|
||||
|
||||
`time.time()` returns the seconds since January 1st, 1970.
|
||||
|
||||

|
||||
|
||||
### Step 2: the decorator skeleton
|
||||
|
||||
A decorator takes a function and returns a wrapper that calls it. `functools.wraps`
|
||||
keeps the original name.
|
||||
|
||||

|
||||
|
||||
### Step 3: timing the call
|
||||
|
||||
The wrapper reads the clock before and after the call and prints the difference.
|
||||
|
||||

|
||||
|
||||
### Step 4: fast against slow
|
||||
|
||||
`@speed_calc_decorator` above `fast_function` and `slow_function` gives the
|
||||
expected output.
|
||||
|
||||

|
||||
|
||||
## 🧪 Tests
|
||||
|
||||
```bash
|
||||
python -m pytest
|
||||
```
|
||||
|
||||
All tests pass; they use a fake clock, so they do not depend on how fast your
|
||||
machine is.
|
||||
|
||||
## 📖 Documentation
|
||||
|
||||
The source uses Doxygen comments. Build the HTML documentation into
|
||||
`docs/doxygen/`:
|
||||
|
||||
```bash
|
||||
doxygen Doxyfile
|
||||
```
|
||||
|
||||
## 📄 License
|
||||
|
||||
GNU Affero General Public License v3.0, see [LICENSE](./LICENSE).
|
||||
|
||||
## 🔗 Links
|
||||
|
||||
- [Repository][repository]
|
||||
- [Wiki][documentation]
|
||||
- [Issue tracker][issues]
|
||||
|
||||
## 📎 Appendix: Project documents
|
||||
|
||||
| Document | What it tells you |
|
||||
| --- | --- |
|
||||
| [AGENTS.md][agents] | The rules AI agents follow in this repository |
|
||||
| [Business Case][BC-001] | Why the project exists, its scope and success criteria |
|
||||
| [Stakeholder Analysis][SA-001] | Who is involved; the stakeholder IDs used for owners and reviewers |
|
||||
| [Project Plan][PP-001] | The gateways and their schedule |
|
||||
| [Milestones][milestones] | One document per gateway: Go/No-Go criteria and tasks |
|
||||
| [Artifact registry][registry] | Where each artifact type lives, the next version, the PO language and domain |
|
||||
| [Traceability matrix][TM-001] | How the artifacts link and when each was last reviewed |
|
||||
| [Framework README][framework-readme] | The framework this project uses |
|
||||
| [Framework changelog][framework-changelog] | What changed in each framework release |
|
||||
|
||||
---
|
||||
|
||||
[repository]: https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/054-speed-calc-decorator
|
||||
[documentation]: https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/054-speed-calc-decorator/wiki
|
||||
[issues]: https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/054-speed-calc-decorator/issues
|
||||
[agents]: ./AGENTS.md
|
||||
[BC-001]: ./docs/business-case.md
|
||||
[SA-001]: ./docs/stakeholder-analysis.md
|
||||
[PP-001]: ./docs/project-plan.md
|
||||
[milestones]: ./docs/milestones/
|
||||
[registry]: ./docs/artifact-registry.md
|
||||
[TM-001]: ./docs/sqa/traceability-matrix.md
|
||||
[framework-readme]: ./framework/README.md
|
||||
[framework-changelog]: ./framework/CHANGELOG.md
|
||||
@@ -16,7 +16,7 @@ document of a type. `Primary File` may contain a glob (e.g.
|
||||
| PP | Project Plan | docs/project-plan.md | 002 |
|
||||
| MIL | Milestone / Gateway | docs/milestones/mil-*.md | 004 |
|
||||
| TM | Traceability Matrix | docs/sqa/traceability-matrix.md | 002 |
|
||||
| RC | SQA Review Record | docs/sqa/reviews/rc-*.md | 007 |
|
||||
| RC | SQA Review Record | docs/sqa/reviews/rc-*.md | 008 |
|
||||
| ADR | Architecture Decision Record | docs/adr/adr-*.md | 0002 |
|
||||
|
||||
## Languages
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
"""Render the real output of each lecture step to a PNG file.
|
||||
|
||||
Dev-only tool (needs Pillow, installed by `pip install -e ".[dev]"`). It runs
|
||||
each command, captures its output and draws it as a terminal image, so no
|
||||
screen is captured and the desktop is never touched.
|
||||
|
||||
Usage, from the project root: python docs/screenshots/generate_screenshots.py
|
||||
"""
|
||||
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
|
||||
OUTPUT_DIR = Path(__file__).resolve().parent
|
||||
ROOT = OUTPUT_DIR.parent.parent
|
||||
PYTHON = "python"
|
||||
|
||||
# (file name, command shown in the prompt, arguments run after the interpreter)
|
||||
SHOTS = [
|
||||
(
|
||||
"step-1-current-time.png",
|
||||
"python -m speed_calc.lecture_steps 1",
|
||||
["-m", "speed_calc.lecture_steps", "1"],
|
||||
),
|
||||
(
|
||||
"step-2-decorator-skeleton.png",
|
||||
"python -m speed_calc.lecture_steps 2",
|
||||
["-m", "speed_calc.lecture_steps", "2"],
|
||||
),
|
||||
(
|
||||
"step-3-timing-wrapper.png",
|
||||
"python -m speed_calc.lecture_steps 3",
|
||||
["-m", "speed_calc.lecture_steps", "3"],
|
||||
),
|
||||
("step-4-fast-vs-slow.png", "python -m speed_calc", ["-m", "speed_calc"]),
|
||||
]
|
||||
|
||||
BACKGROUND, FOREGROUND, PROMPT = "#1e1e2e", "#cdd6f4", "#a6e3a1"
|
||||
FONT_CANDIDATES = [
|
||||
"C:/Windows/Fonts/consola.ttf",
|
||||
"/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf",
|
||||
"/System/Library/Fonts/Menlo.ttc",
|
||||
]
|
||||
|
||||
|
||||
def load_font(size: int) -> ImageFont.ImageFont:
|
||||
for candidate in FONT_CANDIDATES:
|
||||
if Path(candidate).exists():
|
||||
return ImageFont.truetype(candidate, size)
|
||||
return ImageFont.load_default()
|
||||
|
||||
|
||||
def run(arguments: list[str]) -> str:
|
||||
done = subprocess.run(
|
||||
[sys.executable, *arguments],
|
||||
cwd=ROOT,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=True,
|
||||
)
|
||||
return done.stdout.strip()
|
||||
|
||||
|
||||
def render(path: Path, command: str, output: str) -> None:
|
||||
font = load_font(20)
|
||||
lines = [f"$ {command}", *output.splitlines()]
|
||||
width = int(max(font.getlength(line) for line in lines)) + 48
|
||||
height = 28 * len(lines) + 48
|
||||
image = Image.new("RGB", (max(width, 640), height), BACKGROUND)
|
||||
draw = ImageDraw.Draw(image)
|
||||
for index, line in enumerate(lines):
|
||||
colour = PROMPT if index == 0 else FOREGROUND
|
||||
draw.text((24, 24 + 28 * index), line, font=font, fill=colour)
|
||||
image.save(path)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
for name, command, arguments in SHOTS:
|
||||
render(OUTPUT_DIR / name, command, run(arguments))
|
||||
print("wrote", name)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 6.1 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 10 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 9.1 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
@@ -0,0 +1,73 @@
|
||||
# RC-007 Review of the Python source and tests of MIL-002
|
||||
|
||||
## Metadata
|
||||
| Key | Value |
|
||||
| --- | --- |
|
||||
| ID | RC-007 |
|
||||
| CrossReference | [QC-PY-001], [MIL-002] |
|
||||
|
||||
## Version History
|
||||
| Date | Status | Author | Reviewer | Change | Commit |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| 2026-11-06 | Proposed | Jens Tirsvad Nielsen | AI as stand-in for S01 | Initial version | pending |
|
||||
|
||||
---
|
||||
|
||||
## Artifact Under Review
|
||||
|
||||
- Instance reviewed: the Python code under `src/speed_calc/` and `tests/`, plus the dev tool `docs/screenshots/generate_screenshots.py`, delivered by [MIL-002] (source code has no instance ID)
|
||||
- Checklist used: [QC-PY-001]
|
||||
- Scope: full review. Round 1 found one defect (criterion 4, unannotated tests), fixed in the tests; round 2 re-checked every criterion and is shown here.
|
||||
- Language and domain: n/a (technical type)
|
||||
- Language reviewer: none
|
||||
- Mode: autonomous, not independent (the reviewer is `AI as stand-in for S01`; S01 is also the author)
|
||||
|
||||
## Checklist Results
|
||||
|
||||
| # | Criterion | Status | Evidence/Notes |
|
||||
| --- | --- | --- | --- |
|
||||
| 1 | Packages, modules, functions, variables, classes and constants follow PEP 8 casing | Pass | `ruff` rule set N (pep8-naming) passes; modules snake_case, `FAST_ITERATIONS` UPPER_SNAKE |
|
||||
| 2 | Names state purpose in the domain's language | Pass | `speed_calc_decorator`, `make_speed_calc_decorator`, `RESULT_TEMPLATE`; loop variable `i` is a tiny scope |
|
||||
| 3 | Formatter and linter, no unexplained suppressions | Pass | `ruff check src tests docs/screenshots`: All checks passed; `ruff format --check`: 10 files formatted. One `# type: ignore[attr-defined]` in `lecture_steps.py`, with the reason in the comment above it |
|
||||
| 4 | Every signature type-annotated, including `-> None` | Pass | Round 1 Fail: tests had no annotations. Fixed in round 2 (`-> None`, `pytest.CaptureFixture[str]`, `make_clock` return type); `mypy --strict tests src`: no issues in 9 files |
|
||||
| 5 | No bare `except:`, no swallowed exceptions | Pass | No `try`/`except` in the code |
|
||||
| 6 | No mutable defaults, no shadowed builtins | Pass | Defaults are `time.time` and `print`; ruff B passes; no builtin is shadowed |
|
||||
| 7 | Context managers for files, locks, connections | N-A | No resources are opened (the screenshot tool uses `subprocess.run` and `Image.save`, which need none) |
|
||||
| 8 | Docstrings that say what, not how | Pass | Every module, function and constant has a Doxygen comment (`@brief`, `@param`, `@return`) |
|
||||
| 9 | Logging uses `logging`, not `print`; no secrets | Pass | `print` is the assignment's required program output (`python -m speed_calc` prints the time and timing lines), not diagnostics; the decorator takes an injectable `output`. No secrets or personal data are printed |
|
||||
| 10 | Classes and operations trace to the DCD | N-A | No DCD exists by decision ([ADR-0001]); the four functions trace to MIL-002 criterion 3. `make_speed_calc_decorator` and `lecture_steps` are additions for tests and screenshots, recorded in MIL-002 tasks 3 and 6 |
|
||||
| 11 | Tests for new behaviour, named for behaviour, independent of order and network | Pass | `pytest -q`: 12 passed; fake clock gives exact values; no network, no shared state |
|
||||
| 12 | Type checker strict, `Any` justified | Pass | `mypy src` (strict = true in `pyproject.toml`): Success, no issues in 6 source files. `Any` in `decorator.py` has the comment that a decorator accepts any function's arguments |
|
||||
| 13 | Dependencies declared and pinned, none unused | Fail | Optional. `dependencies = []`; the dev extras use lower bounds (`pytest>=8`) and are not pinned. Pillow is used by the screenshot tool. Non-blocking; accepted for a learning exercise |
|
||||
| 14 | Each piece of knowledge defined once | Pass | Loop sizes and the line format are only in `constants.py`; both demo loops are alike but change independently |
|
||||
| 15 | Imports point inward, no cycles | Pass | `constants` <- `decorator` <- `demo_functions` <- `__main__`/`lecture_steps`; no cycle, no framework in business modules |
|
||||
| 16 | SOLID holds | Pass | The decorator receives clock and output (dependency inversion); each module has one reason to change |
|
||||
| 17 | Clean architecture layers and folders | Pass | Flat package `speed_calc` with a flat `tests/`, by decision in [ADR-0001] (layers would hide the three functions the course asks for); the criterion accepts a merged layer when an ADR says why. Imports still point inward |
|
||||
| 18 | File and folder names English, no space, language casing | Pass | `decorator.py`, `demo_functions.py`, `test_*.py`, `generate_screenshots.py`, `step-1-current-time.png`: English, no space, snake_case or hyphen |
|
||||
|
||||
## MIL-002 Go criteria
|
||||
|
||||
| # | Criterion | Result | Evidence |
|
||||
| --- | --- | --- | --- |
|
||||
| 1 | `pytest` exits 0 | Go | 12 passed in the project `.venv` (fresh-venv install not repeated) |
|
||||
| 2 | `python -m speed_calc` output | Go | Printed `1793943250.9...`, then `fast_function run speed: 0.017...s` and `slow_function run speed: 0.173...s` |
|
||||
| 3 | Function names | Go | `speed_calc_decorator`, `fast_function`, `slow_function`; test `test_function_names_match_the_assignment` |
|
||||
| 4 | Constants only in `constants.py`; Doxygen comments | Go | No numeric or format literals elsewhere; all modules carry Doxygen comments (`doxygen Doxyfile` not run) |
|
||||
| 5 | 4 screenshots referenced in README | Go | 4 PNG files in `docs/screenshots/`; README lines 97, 104, 110, 117 reference them |
|
||||
| 6 | No runtime dependencies | Go | `dependencies = []` |
|
||||
|
||||
## Overall Verdict
|
||||
|
||||
Go. The only Fail is criterion 13, which is Optional and so not blocking. Round 1 found a Mandatory Fail on criterion 4 (unannotated tests); it was fixed and re-checked in round 2. Criteria 7 and 10 are N-A with reasons. The review is `Mode: autonomous, not independent`.
|
||||
|
||||
## Action Items
|
||||
|
||||
| Action | Owner | Due |
|
||||
| --- | --- | --- |
|
||||
| Pin or bound the dev dependencies if the project is kept beyond the exercise (criterion 13, Optional) | S01 | 2026-11-09 |
|
||||
|
||||
---
|
||||
|
||||
[QC-PY-001]: ../../../framework/qc/code/qc-programming-python.md
|
||||
[MIL-002]: ../../milestones/mil-002-speed-calc-decorator.md
|
||||
[ADR-0001]: ../../adr/adr-0001-skip-stories-plain-package.md
|
||||
@@ -1,88 +1,90 @@
|
||||
# Traceability Matrix
|
||||
|
||||
## Metadata
|
||||
| Key | Value |
|
||||
| --- | --- |
|
||||
| ID | TM-001 |
|
||||
| CrossReference | [BC-001], [SA-001] |
|
||||
|
||||
## Version History
|
||||
| Date | Status | Author | Reviewer | Change | Commit |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| 2026-11-06 | Proposed | Jens Tirsvad Nielsen | S01 | Initial version: BC, SA, PP, MIL-001 to MIL-003 and RC-001 to RC-005 | [a07d05b] |
|
||||
|
||||
---
|
||||
|
||||
## Purpose
|
||||
|
||||
Tracks backward/forward links between artifact instances so that the Business Case's
|
||||
cross-artifact traceability success criterion is measurable. A row is added or
|
||||
updated whenever an artifact instance is created or reviewed.
|
||||
|
||||
## Traceability Table
|
||||
|
||||
| Artifact Instance | Type | Language | Domain | Upstream (Backward Link) | Downstream (Forward Link) | Last Reviewed (RC-ID) |
|
||||
| --- | --- | --- | --- | --- | --- | --- |
|
||||
| [SA-001] | Stakeholder Analysis | en | it | - | [BC-001], [PP-001] | [RC-002] (Go) |
|
||||
| [BC-001] | Business Case | en | it | [SA-001] | [PP-001], [MIL-001], [MIL-002], [MIL-003] | [RC-001] (Go) |
|
||||
| [PP-001] | Project Plan | en | it | [BC-001], [SA-001] | [MIL-001], [MIL-002], [MIL-003] | - (no review gate; accepted by S01 through the accepted plan) |
|
||||
| [MIL-001] | Milestone / Gateway | en | it | [BC-001], [PP-001] | [MIL-002]; source code is not built yet; ADR-0001 is written | [RC-003] (Go) |
|
||||
| [MIL-002] | Milestone / Gateway | en | it | [BC-001], [PP-001], [MIL-001] | [MIL-003]; source code and tests are not built yet | [RC-004] (Go) |
|
||||
| [MIL-003] | Milestone / Gateway | en | it | [BC-001], [PP-001], [MIL-002] | - | [RC-005] (Go) |
|
||||
| [ADR-0001] | Architecture Decision Record | n/a | n/a | [BC-001], [PP-001] | source code and tests are not built yet | [RC-006] (Go) |
|
||||
| [RC-001] | SQA Review Record | - | - | [BC-001] | - | - |
|
||||
| [RC-002] | SQA Review Record | - | - | [SA-001] | - | - |
|
||||
| [RC-003] | SQA Review Record | - | - | [MIL-001] | - | - |
|
||||
| [RC-004] | SQA Review Record | - | - | [MIL-002] | - | - |
|
||||
| [RC-005] | SQA Review Record | - | - | [MIL-003] | - | - |
|
||||
| [RC-006] | SQA Review Record | - | - | [ADR-0001] | - | - |
|
||||
|
||||
## Concern Coverage
|
||||
|
||||
No view (architecture, design or use case artifact) exists yet, and none is planned
|
||||
before the milestones: the exercise is one function and the reason for skipping
|
||||
user stories and use cases is recorded in ADR-0001, a task of [MIL-001]
|
||||
(accepted). Each concern is delivered by a milestone Go criterion; that is
|
||||
noted in the first column. There is no Architecture Description, so the gaps
|
||||
are not listed there.
|
||||
|
||||
| Concern (CN-ID) | Views that answer it | Decisions (ADR) | Quality check (TP) | Status |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| CN-001 | none; delivered by [MIL-002] Go criteria 1 and 2 | none (ADR-0001 does not address it) | none | Gap |
|
||||
| CN-002 | none; delivered by [MIL-002] Go criterion 1 | none (ADR-0001 does not address it) | none | Gap |
|
||||
| CN-003 | none; delivered by [MIL-003] Go criteria 1 to 3 | none (ADR-0001 does not address it) | none | Gap |
|
||||
| CN-004 | none; delivered by [MIL-002] Go criterion 3 | none (ADR-0001 does not address it) | none | Gap |
|
||||
| CN-005 | none; delivered by [MIL-001] Go criterion 3 | none (ADR-0001 does not address it) | none | Gap |
|
||||
| CN-006 | none; delivered by [MIL-001] Go criteria 3 and 5 | none (ADR-0001 does not address it) | none | Gap |
|
||||
| CN-007 | none; delivered by [MIL-001] Go criterion 1 and [MIL-002] Go criterion 6 | none (ADR-0001 does not address it) | none | Gap |
|
||||
|
||||
## Open Inconsistencies
|
||||
|
||||
| # | Views | Inconsistency | Found in (RC-ID) | Status | Resolved in |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| 1 | [SA-001], [BC-001] | CN-007 (no runtime dependencies) was traced to Business Case objective 3 (venv and run instructions), which does not cover it | [RC-002] | Closed | [SA-001] version of 2026-11-06 |
|
||||
| 2 | [BC-001] | The constraint "No runtime dependencies unless needed" contradicted success criterion 3 (0 runtime dependencies) | [RC-001] | Closed | [BC-001] version of 2026-11-06 |
|
||||
| 3 | [PP-001], [MIL-002] | The plan placed `constants.py` at [MIL-001], while [MIL-002] task 1 creates it | informal review of [PP-001] (no RC) | Closed | [PP-001] version of 2026-11-06 |
|
||||
|
||||
## Coverage Notes
|
||||
|
||||
- Types with no instance yet: KPI, BMC, BPMN, UCD, US, UC, DM, SSD, OC, SD, DCD, ERD, TP, AD, GOV, DICT. User stories and use cases are skipped on purpose (recorded in [ADR-0001]); GOV and DICT are not in the plan, which leaves QC-LANG-001 criterion 5 without a dictionary to check against.
|
||||
- `-` in Upstream means foundational; in Downstream, nothing is built on it yet; in Last Reviewed, no `RC-*` exists yet.
|
||||
- All five reviews are `Mode: autonomous, not independent` and `Go`: S01 confirmed criteria 3, 5 and 9 of QC-LANG-001 as a person in chat on 2026-11-06, so BC-001, SA-001 and MIL-001 to MIL-003 are `Accepted`.
|
||||
|
||||
---
|
||||
|
||||
[BC-001]: ../business-case.md
|
||||
[SA-001]: ../stakeholder-analysis.md
|
||||
[PP-001]: ../project-plan.md
|
||||
[MIL-001]: ../milestones/mil-001-project-baseline.md
|
||||
[MIL-002]: ../milestones/mil-002-speed-calc-decorator.md
|
||||
[MIL-003]: ../milestones/mil-003-wiki-and-screenshots.md
|
||||
[RC-001]: ./reviews/rc-001-business-case.md
|
||||
[RC-002]: ./reviews/rc-002-stakeholder-analysis.md
|
||||
[RC-003]: ./reviews/rc-003-mil-001-project-baseline.md
|
||||
[RC-004]: ./reviews/rc-004-mil-002-speed-calc-decorator.md
|
||||
[RC-005]: ./reviews/rc-005-mil-003-wiki.md
|
||||
[RC-006]: ./reviews/rc-006-adr-0001.md
|
||||
[ADR-0001]: ../adr/adr-0001-skip-stories-plain-package.md
|
||||
[a07d05b]: https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/054-speed-calc-decorator/commit/a07d05b385e6b831b90731aefe08f078ebb951ca
|
||||
# Traceability Matrix
|
||||
|
||||
## Metadata
|
||||
| Key | Value |
|
||||
| --- | --- |
|
||||
| ID | TM-001 |
|
||||
| CrossReference | [BC-001], [SA-001] |
|
||||
|
||||
## Version History
|
||||
| Date | Status | Author | Reviewer | Change | Commit |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| 2026-11-06 | Proposed | Jens Tirsvad Nielsen | S01 | Initial version: BC, SA, PP, MIL-001 to MIL-003 and RC-001 to RC-005 | [a07d05b] |
|
||||
|
||||
---
|
||||
|
||||
## Purpose
|
||||
|
||||
Tracks backward/forward links between artifact instances so that the Business Case's
|
||||
cross-artifact traceability success criterion is measurable. A row is added or
|
||||
updated whenever an artifact instance is created or reviewed.
|
||||
|
||||
## Traceability Table
|
||||
|
||||
| Artifact Instance | Type | Language | Domain | Upstream (Backward Link) | Downstream (Forward Link) | Last Reviewed (RC-ID) |
|
||||
| --- | --- | --- | --- | --- | --- | --- |
|
||||
| [SA-001] | Stakeholder Analysis | en | it | - | [BC-001], [PP-001] | [RC-002] (Go) |
|
||||
| [BC-001] | Business Case | en | it | [SA-001] | [PP-001], [MIL-001], [MIL-002], [MIL-003] | [RC-001] (Go) |
|
||||
| [PP-001] | Project Plan | en | it | [BC-001], [SA-001] | [MIL-001], [MIL-002], [MIL-003] | - (no review gate; accepted by S01 through the accepted plan) |
|
||||
| [MIL-001] | Milestone / Gateway | en | it | [BC-001], [PP-001] | [MIL-002]; source code is not built yet; ADR-0001 is written | [RC-003] (Go) |
|
||||
| [MIL-002] | Milestone / Gateway | en | it | [BC-001], [PP-001], [MIL-001] | [MIL-003]; Python source and tests (reviewed in RC-007) | [RC-004] (Go) |
|
||||
| [MIL-003] | Milestone / Gateway | en | it | [BC-001], [PP-001], [MIL-002] | - | [RC-005] (Go) |
|
||||
| [ADR-0001] | Architecture Decision Record | n/a | n/a | [BC-001], [PP-001] | source code and tests are not built yet | [RC-006] (Go) |
|
||||
| [RC-001] | SQA Review Record | - | - | [BC-001] | - | - |
|
||||
| [RC-002] | SQA Review Record | - | - | [SA-001] | - | - |
|
||||
| [RC-003] | SQA Review Record | - | - | [MIL-001] | - | - |
|
||||
| [RC-004] | SQA Review Record | - | - | [MIL-002] | - | - |
|
||||
| [RC-005] | SQA Review Record | - | - | [MIL-003] | - | - |
|
||||
| [RC-006] | SQA Review Record | - | - | [ADR-0001] | - | - |
|
||||
| [RC-007] | SQA Review Record | - | - | [MIL-002] | - | - |
|
||||
|
||||
## Concern Coverage
|
||||
|
||||
No view (architecture, design or use case artifact) exists yet, and none is planned
|
||||
before the milestones: the exercise is one function and the reason for skipping
|
||||
user stories and use cases is recorded in ADR-0001, a task of [MIL-001]
|
||||
(accepted). Each concern is delivered by a milestone Go criterion; that is
|
||||
noted in the first column. There is no Architecture Description, so the gaps
|
||||
are not listed there.
|
||||
|
||||
| Concern (CN-ID) | Views that answer it | Decisions (ADR) | Quality check (TP) | Status |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| CN-001 | none; delivered by [MIL-002] Go criteria 1 and 2 | none (ADR-0001 does not address it) | none | Gap |
|
||||
| CN-002 | none; delivered by [MIL-002] Go criterion 1 | none (ADR-0001 does not address it) | none | Gap |
|
||||
| CN-003 | none; delivered by [MIL-003] Go criteria 1 to 3 | none (ADR-0001 does not address it) | none | Gap |
|
||||
| CN-004 | none; delivered by [MIL-002] Go criterion 3 | none (ADR-0001 does not address it) | none | Gap |
|
||||
| CN-005 | none; delivered by [MIL-001] Go criterion 3 | none (ADR-0001 does not address it) | none | Gap |
|
||||
| CN-006 | none; delivered by [MIL-001] Go criteria 3 and 5 | none (ADR-0001 does not address it) | none | Gap |
|
||||
| CN-007 | none; delivered by [MIL-001] Go criterion 1 and [MIL-002] Go criterion 6 | none (ADR-0001 does not address it) | none | Gap |
|
||||
|
||||
## Open Inconsistencies
|
||||
|
||||
| # | Views | Inconsistency | Found in (RC-ID) | Status | Resolved in |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| 1 | [SA-001], [BC-001] | CN-007 (no runtime dependencies) was traced to Business Case objective 3 (venv and run instructions), which does not cover it | [RC-002] | Closed | [SA-001] version of 2026-11-06 |
|
||||
| 2 | [BC-001] | The constraint "No runtime dependencies unless needed" contradicted success criterion 3 (0 runtime dependencies) | [RC-001] | Closed | [BC-001] version of 2026-11-06 |
|
||||
| 3 | [PP-001], [MIL-002] | The plan placed `constants.py` at [MIL-001], while [MIL-002] task 1 creates it | informal review of [PP-001] (no RC) | Closed | [PP-001] version of 2026-11-06 |
|
||||
|
||||
## Coverage Notes
|
||||
|
||||
- Types with no instance yet: KPI, BMC, BPMN, UCD, US, UC, DM, SSD, OC, SD, DCD, ERD, TP, AD, GOV, DICT. User stories and use cases are skipped on purpose (recorded in [ADR-0001]); GOV and DICT are not in the plan, which leaves QC-LANG-001 criterion 5 without a dictionary to check against.
|
||||
- `-` in Upstream means foundational; in Downstream, nothing is built on it yet; in Last Reviewed, no `RC-*` exists yet.
|
||||
- All five reviews are `Mode: autonomous, not independent` and `Go`: S01 confirmed criteria 3, 5 and 9 of QC-LANG-001 as a person in chat on 2026-11-06, so BC-001, SA-001 and MIL-001 to MIL-003 are `Accepted`.
|
||||
|
||||
---
|
||||
|
||||
[BC-001]: ../business-case.md
|
||||
[SA-001]: ../stakeholder-analysis.md
|
||||
[PP-001]: ../project-plan.md
|
||||
[MIL-001]: ../milestones/mil-001-project-baseline.md
|
||||
[MIL-002]: ../milestones/mil-002-speed-calc-decorator.md
|
||||
[MIL-003]: ../milestones/mil-003-wiki-and-screenshots.md
|
||||
[RC-001]: ./reviews/rc-001-business-case.md
|
||||
[RC-002]: ./reviews/rc-002-stakeholder-analysis.md
|
||||
[RC-003]: ./reviews/rc-003-mil-001-project-baseline.md
|
||||
[RC-004]: ./reviews/rc-004-mil-002-speed-calc-decorator.md
|
||||
[RC-005]: ./reviews/rc-005-mil-003-wiki.md
|
||||
[RC-006]: ./reviews/rc-006-adr-0001.md
|
||||
[RC-007]: ./reviews/rc-007-python-source-mil-002.md
|
||||
[ADR-0001]: ../adr/adr-0001-skip-stories-plain-package.md
|
||||
[a07d05b]: https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/054-speed-calc-decorator/commit/a07d05b385e6b831b90731aefe08f078ebb951ca
|
||||
+8
-1
@@ -15,7 +15,7 @@ keywords = ["python", "decorator", "udemy", "100-days-of-code", "timing"]
|
||||
dependencies = []
|
||||
|
||||
[project.optional-dependencies]
|
||||
dev = ["pytest>=8", "pillow>=11"]
|
||||
dev = ["pytest>=8", "pillow>=11", "ruff>=0.8", "mypy>=1.13"]
|
||||
|
||||
[tool.setuptools.packages.find]
|
||||
where = ["src"]
|
||||
@@ -27,3 +27,10 @@ addopts = "-ra"
|
||||
|
||||
[tool.ruff]
|
||||
line-length = 88
|
||||
|
||||
[tool.ruff.lint]
|
||||
select = ["E", "F", "I", "N", "UP", "B"]
|
||||
|
||||
[tool.mypy]
|
||||
strict = true
|
||||
mypy_path = "src"
|
||||
@@ -0,0 +1,14 @@
|
||||
"""!
|
||||
@file __init__.py
|
||||
@brief Public API of the speed calc package.
|
||||
"""
|
||||
|
||||
from speed_calc.decorator import make_speed_calc_decorator, speed_calc_decorator
|
||||
from speed_calc.demo_functions import fast_function, slow_function
|
||||
|
||||
__all__ = [
|
||||
"fast_function",
|
||||
"make_speed_calc_decorator",
|
||||
"slow_function",
|
||||
"speed_calc_decorator",
|
||||
]
|
||||
@@ -0,0 +1,21 @@
|
||||
"""!
|
||||
@file __main__.py
|
||||
@brief Entry point: python -m speed_calc.
|
||||
"""
|
||||
|
||||
import time
|
||||
|
||||
from speed_calc.demo_functions import fast_function, slow_function
|
||||
|
||||
|
||||
def main() -> None:
|
||||
"""!
|
||||
@brief Print the current time, then the run time of both functions.
|
||||
"""
|
||||
print(time.time()) # seconds since Jan 1st, 1970
|
||||
fast_function()
|
||||
slow_function()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,13 @@
|
||||
"""!
|
||||
@file constants.py
|
||||
@brief Constants used by the speed calc package.
|
||||
"""
|
||||
|
||||
## Number of loop iterations of fast_function.
|
||||
FAST_ITERATIONS: int = 1_000_000
|
||||
|
||||
## Number of loop iterations of slow_function.
|
||||
SLOW_ITERATIONS: int = 10_000_000
|
||||
|
||||
## Line format printed by the decorator: function name, then seconds.
|
||||
RESULT_TEMPLATE: str = "{name} run speed: {seconds}s"
|
||||
@@ -0,0 +1,51 @@
|
||||
"""!
|
||||
@file decorator.py
|
||||
@brief The speed calc decorator: prints how long a function takes to run.
|
||||
"""
|
||||
|
||||
import functools
|
||||
import time
|
||||
from collections.abc import Callable
|
||||
from typing import Any # Any: a decorator must accept the arguments of any function
|
||||
|
||||
from speed_calc.constants import RESULT_TEMPLATE
|
||||
|
||||
|
||||
def make_speed_calc_decorator(
|
||||
clock: Callable[[], float] = time.time,
|
||||
output: Callable[[str], None] = print,
|
||||
) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
|
||||
"""!
|
||||
@brief Build a speed calc decorator with its own clock and output.
|
||||
|
||||
Tests pass a fake clock, so they never depend on real elapsed time.
|
||||
|
||||
@param clock Returns the current time in seconds.
|
||||
@param output Receives the result line.
|
||||
@return A decorator that times the function it wraps.
|
||||
"""
|
||||
|
||||
def decorator(function: Callable[..., Any]) -> Callable[..., Any]:
|
||||
@functools.wraps(function)
|
||||
def wrapper(*args: Any, **kwargs: Any) -> Any:
|
||||
start = clock()
|
||||
result = function(*args, **kwargs)
|
||||
seconds = clock() - start
|
||||
output(RESULT_TEMPLATE.format(name=function.__name__, seconds=seconds))
|
||||
return result
|
||||
|
||||
return wrapper
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
def speed_calc_decorator(function: Callable[..., Any]) -> Callable[..., Any]:
|
||||
"""!
|
||||
@brief Print the seconds that the decorated function takes to run.
|
||||
|
||||
Use it as `@speed_calc_decorator` above a function definition.
|
||||
|
||||
@param function The function to time.
|
||||
@return A wrapper that behaves like @p function and prints its run time.
|
||||
"""
|
||||
return make_speed_calc_decorator()(function)
|
||||
@@ -0,0 +1,25 @@
|
||||
"""!
|
||||
@file demo_functions.py
|
||||
@brief The two functions of the assignment, timed by the decorator.
|
||||
"""
|
||||
|
||||
from speed_calc.constants import FAST_ITERATIONS, SLOW_ITERATIONS
|
||||
from speed_calc.decorator import speed_calc_decorator
|
||||
|
||||
|
||||
@speed_calc_decorator
|
||||
def fast_function() -> None:
|
||||
"""!
|
||||
@brief Loop FAST_ITERATIONS times.
|
||||
"""
|
||||
for i in range(FAST_ITERATIONS):
|
||||
i * i
|
||||
|
||||
|
||||
@speed_calc_decorator
|
||||
def slow_function() -> None:
|
||||
"""!
|
||||
@brief Loop SLOW_ITERATIONS times.
|
||||
"""
|
||||
for i in range(SLOW_ITERATIONS):
|
||||
i * i
|
||||
@@ -0,0 +1,78 @@
|
||||
"""!
|
||||
@file lecture_steps.py
|
||||
@brief The four lecture steps, each runnable on its own.
|
||||
|
||||
Run one with `python -m speed_calc.lecture_steps N` (N is 1 to 4).
|
||||
"""
|
||||
|
||||
import functools
|
||||
import sys
|
||||
import time
|
||||
from collections.abc import Callable
|
||||
from typing import Any
|
||||
|
||||
from speed_calc.decorator import make_speed_calc_decorator
|
||||
from speed_calc.demo_functions import fast_function
|
||||
|
||||
## The undecorated fast_function; functools.wraps keeps it in `__wrapped__`.
|
||||
_fast = fast_function.__wrapped__ # type: ignore[attr-defined]
|
||||
|
||||
|
||||
def step_1() -> None:
|
||||
"""!
|
||||
@brief Step 1: print the seconds since Jan 1st, 1970.
|
||||
"""
|
||||
print(time.time())
|
||||
|
||||
|
||||
def step_2() -> None:
|
||||
"""!
|
||||
@brief Step 2: a decorator skeleton that only announces the call.
|
||||
"""
|
||||
|
||||
def skeleton(function: Callable[..., Any]) -> Callable[..., Any]:
|
||||
@functools.wraps(function)
|
||||
def wrapper(*args: Any, **kwargs: Any) -> Any:
|
||||
print(f"calling {function.__name__}")
|
||||
return function(*args, **kwargs)
|
||||
|
||||
return wrapper
|
||||
|
||||
skeleton(_fast)()
|
||||
print(f"name kept by functools.wraps: {skeleton(_fast).__name__}")
|
||||
|
||||
|
||||
def step_3() -> None:
|
||||
"""!
|
||||
@brief Step 3: the wrapper reads the clock before and after the call.
|
||||
"""
|
||||
make_speed_calc_decorator()(_fast)()
|
||||
|
||||
|
||||
def step_4() -> None:
|
||||
"""!
|
||||
@brief Step 4: the finished program, fast_function against slow_function.
|
||||
"""
|
||||
from speed_calc.__main__ import main
|
||||
|
||||
main()
|
||||
|
||||
|
||||
STEPS: dict[int, Callable[[], None]] = {1: step_1, 2: step_2, 3: step_3, 4: step_4}
|
||||
|
||||
|
||||
def main(argv: list[str]) -> int:
|
||||
"""!
|
||||
@brief Run the lecture step named in @p argv.
|
||||
@param argv Command line arguments without the program name.
|
||||
@return Exit code: 0 on success, 2 on a bad step number.
|
||||
"""
|
||||
if len(argv) != 1 or not argv[0].isdigit() or int(argv[0]) not in STEPS:
|
||||
print("usage: python -m speed_calc.lecture_steps N (N is 1 to 4)")
|
||||
return 2
|
||||
STEPS[int(argv[0])]()
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main(sys.argv[1:]))
|
||||
@@ -0,0 +1,60 @@
|
||||
"""Tests for the speed calc decorator; a fake clock replaces real time."""
|
||||
|
||||
from collections.abc import Callable
|
||||
|
||||
import pytest
|
||||
|
||||
from speed_calc.decorator import make_speed_calc_decorator
|
||||
|
||||
|
||||
def make_clock(*times: float) -> Callable[[], float]:
|
||||
values = iter(times)
|
||||
return lambda: next(values)
|
||||
|
||||
|
||||
def test_prints_function_name_and_elapsed_seconds() -> None:
|
||||
lines: list[str] = []
|
||||
decorate = make_speed_calc_decorator(make_clock(10.0, 12.5), lines.append)
|
||||
|
||||
@decorate
|
||||
def work() -> None:
|
||||
pass
|
||||
|
||||
work()
|
||||
|
||||
assert lines == ["work run speed: 2.5s"]
|
||||
|
||||
|
||||
def test_returns_the_result_of_the_function() -> None:
|
||||
decorate = make_speed_calc_decorator(make_clock(0.0, 1.0), lambda _: None)
|
||||
|
||||
@decorate
|
||||
def add(a: int, b: int) -> int:
|
||||
return a + b
|
||||
|
||||
assert add(2, b=3) == 5
|
||||
|
||||
|
||||
def test_keeps_name_and_docstring() -> None:
|
||||
decorate = make_speed_calc_decorator(make_clock(0.0, 1.0), lambda _: None)
|
||||
|
||||
@decorate
|
||||
def documented() -> None:
|
||||
"""Doc."""
|
||||
|
||||
assert documented.__name__ == "documented"
|
||||
assert documented.__doc__ == "Doc."
|
||||
|
||||
|
||||
def test_default_decorator_prints_to_stdout(capsys: pytest.CaptureFixture[str]) -> None:
|
||||
from speed_calc import speed_calc_decorator
|
||||
|
||||
@speed_calc_decorator
|
||||
def quick() -> None:
|
||||
pass
|
||||
|
||||
quick()
|
||||
|
||||
out = capsys.readouterr().out
|
||||
assert out.startswith("quick run speed: ")
|
||||
assert out.endswith("s\n")
|
||||
@@ -0,0 +1,33 @@
|
||||
"""Tests for the assignment's functions and the entry point."""
|
||||
|
||||
import re
|
||||
|
||||
import pytest
|
||||
|
||||
from speed_calc import constants, fast_function, slow_function
|
||||
from speed_calc.__main__ import main
|
||||
|
||||
LINE = r"(fast|slow)_function run speed: \d+(\.\d+)?(e-?\d+)?s"
|
||||
|
||||
|
||||
def test_function_names_match_the_assignment() -> None:
|
||||
assert fast_function.__name__ == "fast_function"
|
||||
assert slow_function.__name__ == "slow_function"
|
||||
|
||||
|
||||
def test_constants_hold_the_loop_sizes() -> None:
|
||||
assert constants.FAST_ITERATIONS == 1_000_000
|
||||
assert constants.SLOW_ITERATIONS == 10_000_000
|
||||
|
||||
|
||||
def test_main_prints_time_then_fast_then_slow(
|
||||
capsys: pytest.CaptureFixture[str],
|
||||
) -> None:
|
||||
main()
|
||||
|
||||
lines = capsys.readouterr().out.splitlines()
|
||||
|
||||
assert len(lines) == 3
|
||||
assert float(lines[0]) > 0
|
||||
assert re.fullmatch(LINE, lines[1]) and lines[1].startswith("fast_function")
|
||||
assert re.fullmatch(LINE, lines[2]) and lines[2].startswith("slow_function")
|
||||
@@ -0,0 +1,36 @@
|
||||
"""Tests for the lecture steps."""
|
||||
|
||||
import pytest
|
||||
|
||||
from speed_calc.lecture_steps import main
|
||||
|
||||
|
||||
def test_step_1_prints_the_current_time(capsys: pytest.CaptureFixture[str]) -> None:
|
||||
assert main(["1"]) == 0
|
||||
assert float(capsys.readouterr().out) > 0
|
||||
|
||||
|
||||
def test_step_2_announces_the_call_and_keeps_the_name(
|
||||
capsys: pytest.CaptureFixture[str],
|
||||
) -> None:
|
||||
assert main(["2"]) == 0
|
||||
lines = capsys.readouterr().out.splitlines()
|
||||
assert lines == [
|
||||
"calling fast_function",
|
||||
"name kept by functools.wraps: fast_function",
|
||||
]
|
||||
|
||||
|
||||
def test_step_3_prints_one_timing_line(capsys: pytest.CaptureFixture[str]) -> None:
|
||||
assert main(["3"]) == 0
|
||||
assert capsys.readouterr().out.startswith("fast_function run speed: ")
|
||||
|
||||
|
||||
def test_step_4_prints_three_lines(capsys: pytest.CaptureFixture[str]) -> None:
|
||||
assert main(["4"]) == 0
|
||||
assert len(capsys.readouterr().out.splitlines()) == 3
|
||||
|
||||
|
||||
def test_bad_step_number_is_refused(capsys: pytest.CaptureFixture[str]) -> None:
|
||||
assert main(["9"]) == 2
|
||||
assert "usage" in capsys.readouterr().out
|
||||
Reference in new issue
Block a user