Skip to content

[minor] Add a test project - #98

Merged
matt-edmondson merged 1 commit into
mainfrom
claude/record-issues-defects-n6p37v
Aug 27, 2026
Merged

[minor] Add a test project#98
matt-edmondson merged 1 commit into
mainfrom
claude/record-issues-defects-n6p37v

Conversation

@matt-edmondson

Copy link
Copy Markdown
Contributor

Fixes #96. Surfaced #97 along the way.

Why

The solution had one project and no tests. This tool deletes files — an off-by-one in grouping, or a mistake in the keep/delete decision, destroys user data, and nothing stood between a refactor and that outcome.

21 tests over Deduplicator, FileHasher and FileScanner.

The test that matters most

DryRunEquivalenceTests asserts that the set of files DryRun says it would delete is exactly the set Deduplicate does delete.

DryRun is the safety net users rely on before letting this tool remove anything, so a divergence between the two would be the worst defect this codebase could have: the preview would be a lie, and the thing it lied about is irreversible.

The two verbs share GroupByHash, FindDuplicates and SelectFileToKeep, so today they agree by construction. That is worth pinning rather than assuming — it holds only while both keep calling the same three methods and while SelectFileToKeep stays deterministic, and neither was enforced by anything.

It also asserts every group retains exactly one survivor, since deleting a whole group would lose the content entirely.

Real filesystem, not a fake

The tests drive throwaway directories under the temp path rather than an abstraction, because that is what the production code uses: FileScanner calls Directory.EnumerateFiles and Deduplicator calls File.Delete directly. Faking those would exercise a seam that does not exist and would not catch a mistake in the delete path — the part that destroys data. TempTree names each directory after the calling test, so a leaked directory names the test that leaked it.

Also covered

Grouping across nested directories; unique files never offered for deletion; empty files as duplicates of each other; the shortest-filename keep rule; that only the file name and not the path length decides it; deterministic tie-breaking regardless of input order; reclaimed byte counting; a second pass over a deduplicated tree being a no-op; scanning recursion plus the missing- and empty-directory cases; hash agreement across names; the empty-file SHA-256; and that parallel hashing matches single-file hashing.

Verification

21/21 pass. Mutation-checked, by substitution rather than deletion:

Mutation Tests failed
SelectFileToKeep returns .Last() instead of .First() 3
DeleteDuplicates stops sparing the keeper (deletes every copy) 5

The tree was restored from a backup and re-verified after each run — git diff clean, no Last(), no && false residue.

One thing to know before merging

InternalsVisibleTo names the test assembly FileDeduplicator.Test, without the ktsu. prefix every other repository uses. That is not a typo: this repository has no AUTHORS.md, so ktsu.Sdk resolves an empty AuthorsNamespace and names the assembly FileDeduplicator rather than ktsu.FileDeduplicator. It is the only one of six repositories I checked that is missing the file.

I left it alone here — correcting it renames the published artifact, which does not belong in a test-only change. #97 has the detail, including the warning that fixing it must update this literal in the same commit or the test project silently loses access to internals. There is a comment at the attribute saying so.

Caveat

I could not run the repo's own analyzers locally: this sandbox's SDK is 10.0.111 (Roslyn 5.0.0.0) and ktsu.Sdk.Analyzers 2.28.0 requires 5.9.0.0, so CSC refuses it with CS9057. The .NET analyzers and EnforceCodeStyleInBuild did run and are clean. CI is the first place KTSU0001–0007 will actually see this — the new test project is the likeliest place for a KTSU0001 standard-package complaint.


Generated by Claude Code

The solution had one project and no tests. This is a tool that deletes files,
so an off-by-one in grouping or a mistake in the keep/delete decision destroys
user data, and nothing stood between a refactor and that outcome.

Adds FileDeduplicator.Test with 21 tests over the three core types.

The tests drive the real filesystem through throwaway directories under the
temp path rather than an abstraction, because that is what the production code
uses: FileScanner calls Directory.EnumerateFiles and Deduplicator calls
File.Delete directly. Faking those would exercise a seam that does not exist
and would not catch a mistake in the delete path, which is the part that
destroys data. TempTree names each directory after the calling test, so a
leaked directory names the test that leaked it.

The most important test is DryRunEquivalenceTests, which asserts that the set
of files DryRun reports it would delete is exactly the set Deduplicate does
delete. DryRun is the safety net users rely on before letting this tool remove
anything, so a divergence would be a lie about something irreversible. The two
verbs share GroupByHash, FindDuplicates and SelectFileToKeep, so today they
agree by construction -- that holds only while both keep calling the same three
methods and while SelectFileToKeep stays deterministic, neither of which was
enforced by anything.

Also covered: grouping across nested directories, unique files never being
offered for deletion, empty files as duplicates of each other, the
shortest-filename keep rule, that only the file name and not the path length
decides it, deterministic tie-breaking regardless of input order, that every
group retains exactly one survivor, reclaimed byte counting, that a second pass
over a deduplicated tree is a no-op, scanning recursion and the missing- and
empty-directory cases, hash agreement across names, and that parallel hashing
matches single-file hashing.

Verified by mutation. Making SelectFileToKeep pick the last candidate instead
of the first fails 3 tests; making DeleteDuplicates stop sparing the keeper --
which would delete every copy and lose the content entirely -- fails 5.

One thing surfaced while wiring this up: InternalsVisibleTo names the test
assembly FileDeduplicator.Test, without the ktsu. prefix every other repository
in the organization uses. This repository has no AUTHORS.md, so ktsu.Sdk
resolves an empty AuthorsNamespace and names the assembly FileDeduplicator
rather than ktsu.FileDeduplicator. That is left alone here because correcting
it renames the published artifact, which does not belong in a test-only change;
it is raised separately and noted at the attribute so the two stay in step.

Fixes #96

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DTHNXgSNEHUSQ5KMLgivno
@sonarqubecloud

Copy link
Copy Markdown

@matt-edmondson
matt-edmondson merged commit 126ad70 into main Aug 27, 2026
12 checks passed
@matt-edmondson
matt-edmondson deleted the claude/record-issues-defects-n6p37v branch August 27, 2026 05:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

No test project

2 participants