test: improve mutation coverage across 4 modules

Mutation testing (56 mutations, 10 modules) identified 7 surviving
mutations. Added 5 targeted tests to kill all actionable survivors.
Remaining 2 are logically equivalent condition reorderings in
filter.go. Overall mutation score: 96.4%.

New tests:
- TestHasMalformedYAML: YAML unmarshal error path
- TestInvalidateInvalidCheatpath: cheatpath.Validate() delegation
- TestIndentTrimsWhitespace: TrimSpace behavior
- TestColorizeDefaultSyntax: default "bash" lexer
- TestColorizeExplicitSyntax: lexer differentiation

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Christopher Allen Lane
2026-02-15 16:35:12 -05:00
parent 52403dbe4a
commit ca1ec0e38d
5 changed files with 209 additions and 0 deletions

105
.test-mutations.json Normal file
View File

@@ -0,0 +1,105 @@
{
"version": "1.0",
"test_command": "go test ./...",
"last_updated": "2026-02-15T00:00:00Z",
"modules": {
"internal/sheet/parse.go": {
"status": "completed",
"covering_tests": ["internal/sheet/parse_test.go", "internal/sheet/parse_extended_test.go"],
"last_tested": "2026-02-15T00:00:00Z",
"mutations_applied": 8,
"mutations_killed": 8,
"mutation_score": 100.0,
"notes": "Originally 7/8 (87.5%). Added TestHasMalformedYAML to kill YAML unmarshal error survivor."
},
"internal/config/validate.go": {
"status": "completed",
"covering_tests": ["internal/config/validate_test.go"],
"last_tested": "2026-02-15T00:00:00Z",
"mutations_applied": 8,
"mutations_killed": 8,
"mutation_score": 100.0,
"notes": "Originally 7/8 (87.5%). Added TestInvalidateInvalidCheatpath to kill cheatpath.Validate() delegation survivor."
},
"internal/sheets/filter.go": {
"status": "completed",
"covering_tests": ["internal/sheets/filter_test.go"],
"last_tested": "2026-02-15T00:00:00Z",
"mutations_applied": 7,
"mutations_killed": 5,
"mutation_score": 71.4,
"notes": "Survivors relate to UTF-8 condition ordering and OR→AND on dead code path. Not actionable — logically equivalent mutations."
},
"internal/config/paths.go": {
"status": "completed",
"covering_tests": ["internal/config/paths_test.go"],
"last_tested": "2026-02-15T00:00:00Z",
"mutations_applied": 8,
"mutations_killed": 8,
"mutation_score": 100.0,
"notes": "Perfect score. Excellent existing coverage."
},
"internal/sheet/colorize.go": {
"status": "completed",
"covering_tests": ["internal/sheet/colorize_test.go"],
"last_tested": "2026-02-15T00:00:00Z",
"mutations_applied": 5,
"mutations_killed": 5,
"mutation_score": 100.0,
"notes": "Originally 2/5 (40%). Added TestColorizeDefaultSyntax and TestColorizeExplicitSyntax. All 5 mutations now killed."
},
"internal/sheets/consolidate.go": {
"status": "completed",
"covering_tests": ["internal/sheets/consolidate_test.go"],
"last_tested": "2026-02-15T00:00:00Z",
"mutations_applied": 2,
"mutations_killed": 2,
"mutation_score": 100.0,
"notes": "Override semantics well-tested."
},
"internal/display/indent.go": {
"status": "completed",
"covering_tests": ["internal/display/indent_test.go"],
"last_tested": "2026-02-15T00:00:00Z",
"mutations_applied": 3,
"mutations_killed": 3,
"mutation_score": 100.0,
"notes": "Originally 2/3 (66.7%). Added TestIndentTrimsWhitespace to kill TrimSpace survivor."
},
"internal/display/faint.go": {
"status": "completed",
"covering_tests": ["internal/display/faint_test.go"],
"last_tested": "2026-02-15T00:00:00Z",
"mutations_applied": 3,
"mutations_killed": 3,
"mutation_score": 100.0,
"notes": "Perfect score."
},
"internal/sheets/tags.go": {
"status": "completed",
"covering_tests": ["internal/sheets/tags_test.go"],
"last_tested": "2026-02-15T00:00:00Z",
"mutations_applied": 2,
"mutations_killed": 2,
"mutation_score": 100.0,
"notes": "UTF-8 validation and sort order both tested."
},
"internal/sheet/validate.go": {
"status": "completed",
"covering_tests": ["internal/sheet/validate_test.go"],
"last_tested": "2026-02-15T00:00:00Z",
"mutations_applied": 10,
"mutations_killed": 10,
"mutation_score": 100.0,
"notes": "Perfect score. All security checks well-tested."
}
},
"global_statistics": {
"total_modules": 10,
"completed_modules": 10,
"total_mutations": 56,
"total_killed": 54,
"total_survived": 2,
"overall_score": 96.4
}
}

View File

@@ -157,3 +157,28 @@ func TestInvalidateDuplicateCheatpathPaths(t *testing.T) {
t.Errorf("failed to invalidate config with cheatpaths with duplicate paths")
}
}
// TestInvalidateInvalidCheatpath asserts that configs containing a cheatpath
// with an empty name are invalidated
func TestInvalidateInvalidCheatpath(t *testing.T) {
// mock a config with a cheatpath that has an empty name
conf := Config{
Colorize: true,
Editor: "vim",
Formatter: "terminal16m",
Cheatpaths: []cheatpath.Path{
cheatpath.Path{
Name: "",
Path: "/foo",
ReadOnly: false,
Tags: []string{},
},
},
}
// assert that an error is returned
if err := conf.Validate(); err == nil {
t.Errorf("failed to invalidate config with invalid cheatpath (empty name)")
}
}

View File

@@ -10,3 +10,13 @@ func TestIndent(t *testing.T) {
t.Errorf("failed to indent: want: %s, got: %s", want, got)
}
}
// TestIndentTrimsWhitespace asserts that Indent trims leading and trailing
// whitespace before indenting
func TestIndentTrimsWhitespace(t *testing.T) {
got := Indent(" foo\nbar\nbaz \n")
want := "\tfoo\n\tbar\n\tbaz\n"
if got != want {
t.Errorf("failed to trim and indent: want: %q, got: %q", want, got)
}
}

View File

@@ -40,3 +40,55 @@ func TestColorize(t *testing.T) {
t.Errorf("colorized text lost original content: %q", s.Text)
}
}
// TestColorizeDefaultSyntax asserts that when no syntax is specified, the
// default ("bash") is used and produces the same output as an explicit "bash"
func TestColorizeDefaultSyntax(t *testing.T) {
conf := config.Config{
Formatter: "terminal16m",
Style: "monokai",
}
// use bash-specific content that tokenizes differently across lexers
code := "if [[ -f /etc/passwd ]]; then\n echo \"found\" | grep -o found\nfi"
// colorize with empty syntax (should default to "bash")
noSyntax := Sheet{Text: code}
noSyntax.Colorize(conf)
// colorize with explicit "bash" syntax
bashSyntax := Sheet{Text: code, Syntax: "bash"}
bashSyntax.Colorize(conf)
// both should produce the same output
if noSyntax.Text != bashSyntax.Text {
t.Errorf(
"default syntax does not match explicit bash:\ndefault: %q\nexplicit: %q",
noSyntax.Text,
bashSyntax.Text,
)
}
}
// TestColorizeExplicitSyntax asserts that a specified syntax is used
func TestColorizeExplicitSyntax(t *testing.T) {
conf := config.Config{
Formatter: "terminal16m",
Style: "monokai",
}
// colorize as bash
bashSheet := Sheet{Text: "def hello():\n pass", Syntax: "bash"}
bashSheet.Colorize(conf)
// colorize as python
pySheet := Sheet{Text: "def hello():\n pass", Syntax: "python"}
pySheet.Colorize(conf)
// different lexers should produce different output for Python code
if bashSheet.Text == pySheet.Text {
t.Error("bash and python syntax produced identical output")
}
}

View File

@@ -93,3 +93,20 @@ To foo the bar: baz`
t.Errorf("failed to parse text: want: %s, got: %s", markdown, text)
}
}
// TestHasMalformedYAML asserts that an error is returned when the frontmatter
// contains invalid YAML that cannot be unmarshalled
func TestHasMalformedYAML(t *testing.T) {
// stub cheatsheet content with syntactically invalid YAML between the
// delimiters (a bare tab character followed by unquoted colon)
markdown := "---\n\t:\t:\n---\nBody text here"
// parse the frontmatter
_, _, err := parse(markdown)
// assert that an error was returned due to YAML unmarshal failure
if err == nil {
t.Error("failed to error on malformed YAML frontmatter")
}
}