Do not extract a pure helper until three reads are pinned. Those reads are random, time, and the current working directory. A clean split still lies if those reads stay inside.
Characterization tests should lock them before any move. The smallest safe change is one injected clock seam. This note uses a toy module, not a live production trace.
Counts below come from a manual scan of that fixture. They are not field benchmarks and not product scores.
Why this pin comes first
Messy report code mixes chance, time, and path lookup. A coding model often sees length and offers a split. That split looks smaller than the behavior risk.
Hidden reads move into the new helper unchanged. Narrow happy-path string checks can still stay green. Next week the clock or the workdir drifts.
The extract then fails without a clear cause. Three reads dominate this whole class of failure. Random state changes generated ids between two runs.
Time still changes stamps even when inputs match. The current directory changes the relative path text. Pin all three before you move a single line.
Leave format tweaks for a separate later commit. A format edit in the same diff hides the real break. Keep that edit behind its own pin.
Toy module and counted reads
The sample below is unexecuted example code only. It is not a log pulled from a live service. A manual scan of the sample produced the table.
Re-scan your own file before you trust a count. A copied table from this note is not your evidence. Your line numbers are the only count that matters.
| Read | Calls in build_report | Pin |
|---|---|---|
| random.random | 1 | replace or seed, then lock output |
| time.time | 2 | one frozen clock callable |
| os.getcwd | 1 | temp directory via chdir |
| format sites | 4 | full string, including newline |
Four format sites form the output surface here. Three reads form the input surface you must pin. Do not clean formats until those inputs are fixed.
# unexecuted example: src/report.py
import os
import random
import time
def build_report(name):
ticket = f"{random.random():.4f}"
started = int(time.time())
root = os.getcwd()
finished = int(time.time())
return f"{name}|{ticket}|{started}|{root}|{finished}\n"
Scan before you trust a model
Run a narrow search from the repository root first. The pattern is a starting net, not complete proof. Dynamic calls can still hide a read from search.
If the search is empty, open the file anyway. Record each hit in a three-field note. Write the file, the line, and the symbol.
Do not paste secrets from nearby environment reads. If the search hits a key load, stop the flow. That function is outside this pin's safe scope.
rg -n "random\.|time\.time|os\.getcwd|Path\.cwd" src/report.py
Expected hits on the toy file are four lines. You should see one random call and two time calls. You should also see one getcwd call in the file.
A fifth hit means you update the table first. Do not start an extract with a stale count. A stale count lets a hidden read ride along.
What a skipped pin hides
Do not move ticket generation without a random pin. The golden line then changes on every run. You cannot separate a bad extract from noise.
Freeze random only, and leave both time calls live. Both stamps still drift between two local runs. A green local job can be timing luck.
Freeze the workdir on the laptop tree only. A remote runner often uses another working directory. The same patch can fail in that second place.
Path text may also differ by operating system. Record that path difference in the test note. Do not trim the assert just to force a pass.
The decision rule stays small and fully mechanical. If a read can change the golden line, pin it. If you cannot pin that read, do not extract.
1. Add the characterization test first
Do not edit the production file in this step. Add one test that forces the three inputs. Compare the full string, including the trailing newline.
Partial asserts can hide a field that moved. Compare bytes of the returned line, not a prefix. A shorter assert will bless a swapped field.
# unexecuted example: tests/test_build_report_pin.py
import os
def test_build_report_pins_three_reads(monkeypatch, tmp_path):
import report
monkeypatch.setattr(report.random, "random", lambda: 0.25)
ticks = iter([1700000000, 1700000005])
monkeypatch.setattr(report.time, "time", lambda: next(ticks))
monkeypatch.chdir(tmp_path)
root = os.getcwd()
got = report.build_report("ada")
want = f"ada|0.2500|1700000000|{root}|1700000005\n"
assert got == want
The expected ticket is defined by the stub. It is not a sample from CPython's generator. The value 0.25 with four decimals is 0.2500.
Both stamps come from the iterator, in order. The root is os.getcwd after chdir, not a handwritten path. That check avoids a common symlink string mismatch.
Use the synthetic name ada in the golden line. Do not put customer text in that expected string. If a second clock call appears, the iterator raises.
That failure means the clock call count changed. Treat the raise as a signal, not as noise. Update the pin before you touch product code.
If your runner lacks monkeypatch.chdir, patch os.getcwd instead. Keep the same full-string assert either way. Do not drop the path field to dodge the gap.
2. Run the pin before any product edit
Run one test file, not the whole suite, first. You want a fast signal about this contract. A full suite pass can hide a weak pin.
python -m pytest tests/test_build_report_pin.py -q
Expect one passed test on the untouched toy module. If the pin fails, fix the test fixture. Do not edit src/report.py to satisfy that test.
A red pin means the fixture is wrong. Do not refresh a golden file for a rewrite. You have not made that production rewrite yet.
Add a second test for the default clock path. Patch time.time in that test as well. Call build_report with ada and no new argument.
That test guards callers that omit the seam. Write it before the seam exists in product code. It should pass on today's function before any edit.
3. Let a free model draft tests only
MonkeyCode is an open-source coding assistant for this workflow. Free model access can draft the characterization file. It should not land the production rewrite itself.
Disclosure: This article was prepared as part of MonkeyCode's product outreach. Use a narrow prompt with an explicit allowed path. Reject any extra file the draft tries to add.
Do not edit src/report.py.
Add tests/test_build_report_pin.py only.
Pin random.random, time.time, and cwd.
Assert the full returned string, including the newline.
Do not add features or rename fields.
Read the full diff before you run the draft. Any hunk outside the test file is a reject. A new helper under src is also a reject.
A format change inside that helper is a reject. The model may still miss a call-order assert. Your scan table is the checklist, not the draft.
Free model access here is only a drafting seat. This note states no model name, quota, or score. Those details were not verified for this draft.
4. Rerun the same pin on a free server
A free server option can rerun pytest off your laptop. Local cwd and locale are not neutral inputs. The remote run is a second observation, not a trophy.
Keep secrets out of the prompt and the repo. This toy module contains no keys or tokens. A real repository often contains both of those.
Strip env files before any remote upload step. If a test needs a live token, stop this flow. Do not assume the server matches your operating system.
Do not assume it matches your Python patch level. This note states no hardware size and no duration. If the remote pin fails on path text, record it.
Do not weaken the assert down to a substring. Fix the pin, or split platforms in a later test. A softer assert hides the path bug you meant to catch.
python -m pytest tests/test_build_report_pin.py -q --tb=short
Run that same command in both test environments. Save both short outputs beside the commit note. You need a pass or a fail, not a screenshot.
Two passing pins are the gate for the seam. One local pass is not that gate. Wait for the second run before you edit production code.
5. Land one seam, then stop
After both runs pass, change exactly one thing. Inject a clock argument, then stop the commit. Do not also rename fields in that same diff.
Do not also change the ticket width there. Keep one behavior risk inside each commit. A second risk makes the next red test ambiguous.
# unexecuted example: second commit only
def build_report(name, now=None):
clock = time.time if now is None else now
ticket = f"{random.random():.4f}"
started = int(clock())
root = os.getcwd()
finished = int(clock())
return f"{name}|{ticket}|{started}|{root}|{finished}\n"
A default of now=None keeps old callers on time.time. The new test can pass a fake clock in. The old test still patches time.time directly.
It still calls build_report with the name ada. Together the two tests lock both call paths. Random and cwd stay patched in both tests.
You do not pretend those reads already vanished. Stop the branch after this single seam commit. A later commit may seed random at the edge.
A later commit may pass root in as an argument. Those moves are separate pins, not this seam. Each one needs the same two-run gate.
Decision table
| Signal | Action |
|---|---|
| Pin fails locally | Fix the test. Do not edit product code. |
| Search count changed | Update the table before any extract. |
| Model edits src/ | Reject the diff. |
| Local pass, server fail on path text | Record the path form. Do not substring the assert. |
| Both pins pass | Land one clock seam only. |
| Ticket format must change | New commit after the seam is green. |
| Env or key reads appear | Stop. This flow is for the toy class of reads. |
Use the table as a gate, not as advice flavor. One row should match the diff you are holding. If two rows match, take the stricter stop.
Limits of the pin
This pin does not prove thread safety at all. Two calls can still interleave on shared random state. It does not cover network calls or databases.
It does not cover file reads beyond cwd. It does not make a bad golden value correct. If today's output is a bug, write a failing test.
Do not freeze that bug and call it a pin. Stubbed random.random does not test the real generator. If you need a true seed pin, add it later.
Then assert a value you computed on your machine. Do not invent a generator digit in the test. Free model drafts can omit an assert you listed.
Free server runs can differ by locale and build. Availability of either option can change later. Treat both as current access, not a permanent contract.
No speed claim belongs in the merge note. A green pin only says these three reads held still. It does not say the helper is now pure.
Who should skip this flow
Skip this flow when the contract is security sensitive. A random ticket is not a secret by itself. This extract will not turn that ticket into one.
Do not ask a model to harden ids here. Skip the flow when you cannot run tests. An unexecuted pin is only a stale comment.
Skip it when the golden line needs customer data. Use a synthetic name, as this toy module does. Skip it when the goal is a wide cleanup.
Wide cleanups hide which seam broke the line. Split that work into a pin, then one seam. Start the next pin only after that seam is green.
What to do next
Pin the random stand-in, the clock, and cwd. Run that pin on the laptop and the free server. Then land one clock seam and stop.
Leave format edits for a later, separate commit. If that free model access is already on your account, use it narrowly. Point that draft at the test file only.
If the free server option is available too, rerun the same pin there. Keep the merge decision on your side of the diff.
Top comments (0)