Tests co-located with their subject: land #485, then finish the two deferred directories nf-wbpm ← Beads

closed priority 2 task unassigned

Twinned from https://github.com/cfsh/ninefold/issues/494 by tools/beads/import_github.py's reconcile pass.

Tests move next to the code they test, with no tests/ directory. PR [#485](https://github.com/cfsh/ninefold/pull/485) is open and implements the convention plus its gate; it covers most of the tree but deliberately not all of it. This issue is the parent: land #485, then finish the two directories it could not touch, then delete the transition scaffolding.

Aedan's ask, verbatim from chat:

> "i am not sure the style guide for where we're putting tests atm, e.g. tools/{nflib,tests}/{foo.py,foo_test.py}. I propose we always put tests next to the relevant file, no tests directory (test_data directories allowed). Do u concur? Also should we codify this in a ninefold/STYLE_GUIDE.md?"

and, on the mechanism:

> "global globs are risky for these reasons, suppose we end up with SitMathTest as a real game class."

The convention

Codified in STYLE_GUIDE.md §1 (added by #485). In short:

| language | marker | example | |---|---|---| | C# | .Tests.cs — a dot segment | PieceSet.csPieceSet.Tests.cs | | Python | _test.py — an underscore suffix | proc.pyproc_test.py |

The dot is load-bearing on the C# side and is the direct answer to the second quote: Tests is an ordinary way to end a real type name, so a **/*Tests.cs glob would silently drop a game class from the build. A dot is not legal in a C# type name. Python cannot use a dot (not a legal module name), so the asymmetry is forced by the languages rather than chosen.

Enforced by ./presubmit.py --suite layout, which asserts every tracked .cs reaches the right project. Not "exactly one project" — pure-logic sources are cross-linked into both on purpose.

Work remaining after #485 merges
1. games/explore/tests/ → co-located

Blocked only by #434, which is in flight against that directory. Mechanical once it lands: move the files, rename to *.Tests.cs, drop the namespace's .Tests segment, swap the csproj's ..\games\explore\tests\*.cs row for ..\games\explore\*.Tests.cs.

2. tools/tests/ → co-located — needs a guard built first

Blocked by #466, #470, #472, all writing into tools/tests/. Best done when the design-001 chunk drains.

⚠️ Do not start this move before adding the discovery guard below. unittest discover only recurses into directories that are packages, and a directory without __init__.py is skipped silently — measured on this box reporting OK / Ran 1 test while never loading a second test file that was sitting right there. That is silent test loss *during the migration that moves the tests*, which is the worst possible time for it.

Required before the move:

Renames, for whoever takes it:

| today | co-located | |---|---| | tools/tests/test_nflib_proc.py | tools/nflib/proc_test.py | | tools/tests/test_nflib_fs.py | tools/nflib/fs_test.py | | tools/tests/test_nflib_gh.py | tools/nflib/gh_test.py | | tools/tests/test_nflib_git.py | tools/nflib/git_test.py | | tools/tests/test_nflib_core.py | tools/nflib/core_test.py | | tools/tests/test_nflib_env.py | tools/nflib/env_test.py | | tools/tests/test_poll.py | tools/pr/poll_test.py | | tools/tests/test_presubmit.py | presubmit_test.py (repo root) |

Plus whatever #466/#470/#472 add (test_dispatch.py, test_merge_pr.py, test_probe_gate.py, test_nflib_verdict.py). tools/tests/fixtures/ becomes test_data/ per the convention.

⚠️ Two test files cover nflib/__init__.py (core_test.py, env_test.py). That is fine — the rule is a suffix, not a bijection — but do not "fix" it by merging them.

3. Delete the transition scaffolding

While both layouts are live, "is this a test file?" accepts *.Tests.cs or /tests/ in two places that must stay in sync:

Drop the /tests/ clause from both when the last tests/ directory goes. They are already the single spelling within each file (the pre-#485 code had the predicate inline at two call sites in opposite directions, which is how a guard silently inverts) — but they are two files, so they need doing together.

Acceptance
Notes for whoever picks this up

Refs #485, #434, #466, #470, #472

Dependencies

None.

Comments

No comments.

Add a comment