mirror of
https://github.com/cheat/cheat.git
synced 2026-03-07 11:13:33 +01:00
Bug fixes: - Fix inverted pager detection logic (returned error instead of path) - Fix repo.Clone ignoring destination directory parameter - Fix sheet loading using append on pre-sized slices - Clean up partial files on copy failure - Trim whitespace from editor config Security: - Add path traversal protection for cheatsheet names Performance: - Move regex compilation outside search loop - Replace string concatenation with strings.Join in search Build: - Remove go:generate; embed config and usage as string literals - Parallelize release builds - Add fuzz testing infrastructure Testing: - Improve test coverage from 38.9% to 50.2% - Add fuzz tests for search, filter, tags, and validation Documentation: - Fix inaccurate code examples in HACKING.md - Add missing --conf and --all options to man page - Add ADRs for path traversal, env parsing, and search parallelization - Update CONTRIBUTING.md to reflect project policy Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
133 lines
3.7 KiB
Go
133 lines
3.7 KiB
Go
package sheet
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// FuzzParse tests the parse function with fuzzing to uncover edge cases
|
|
// and potential panics in YAML frontmatter parsing
|
|
func FuzzParse(f *testing.F) {
|
|
// Add seed corpus with various valid and edge case inputs
|
|
// Valid frontmatter
|
|
f.Add("---\nsyntax: go\n---\nContent")
|
|
f.Add("---\ntags: [a, b]\n---\n")
|
|
f.Add("---\nsyntax: bash\ntags: [linux, shell]\n---\n#!/bin/bash\necho hello")
|
|
|
|
// No frontmatter
|
|
f.Add("No frontmatter here")
|
|
f.Add("")
|
|
f.Add("Just plain text\nwith multiple lines")
|
|
|
|
// Edge cases with delimiters
|
|
f.Add("---")
|
|
f.Add("---\n")
|
|
f.Add("---\n---")
|
|
f.Add("---\n---\n")
|
|
f.Add("---\n---\n---")
|
|
f.Add("---\n---\n---\n---")
|
|
f.Add("------\n------")
|
|
|
|
// Invalid YAML
|
|
f.Add("---\n{invalid yaml\n---\n")
|
|
f.Add("---\nsyntax: \"unclosed quote\n---\n")
|
|
f.Add("---\ntags: [a, b,\n---\n")
|
|
|
|
// Windows line endings
|
|
f.Add("---\r\nsyntax: go\r\n---\r\nContent")
|
|
f.Add("---\r\n---\r\n")
|
|
|
|
// Mixed line endings
|
|
f.Add("---\nsyntax: go\r\n---\nContent")
|
|
f.Add("---\r\nsyntax: go\n---\r\nContent")
|
|
|
|
// Unicode and special characters
|
|
f.Add("---\ntags: [emoji, 🎉]\n---\n")
|
|
f.Add("---\nsyntax: 中文\n---\n")
|
|
f.Add("---\ntags: [\x00, \x01]\n---\n")
|
|
|
|
// Very long inputs
|
|
f.Add("---\ntags: [" + strings.Repeat("a,", 1000) + "a]\n---\n")
|
|
f.Add("---\n" + strings.Repeat("field: value\n", 1000) + "---\n")
|
|
|
|
// Nested structures
|
|
f.Add("---\ntags:\n - nested\n - list\n---\n")
|
|
f.Add("---\nmeta:\n author: test\n version: 1.0\n---\n")
|
|
|
|
f.Fuzz(func(t *testing.T, input string) {
|
|
// The parse function should never panic, regardless of input
|
|
func() {
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
t.Errorf("parse panicked with input %q: %v", input, r)
|
|
}
|
|
}()
|
|
|
|
fm, text, err := parse(input)
|
|
|
|
// Verify invariants
|
|
if err == nil {
|
|
// If parsing succeeded, validate the result
|
|
|
|
// The returned text should be a suffix of the input
|
|
// (either the whole input if no frontmatter, or the part after frontmatter)
|
|
if !strings.HasSuffix(input, text) && text != input {
|
|
t.Errorf("returned text %q is not a valid suffix of input %q", text, input)
|
|
}
|
|
|
|
// If input starts with delimiter and has valid frontmatter,
|
|
// text should be shorter than input
|
|
if strings.HasPrefix(input, "---\n") || strings.HasPrefix(input, "---\r\n") {
|
|
if len(fm.Tags) > 0 || fm.Syntax != "" {
|
|
// We successfully parsed frontmatter, so text should be shorter
|
|
if len(text) >= len(input) {
|
|
t.Errorf("text length %d should be less than input length %d when frontmatter is parsed",
|
|
len(text), len(input))
|
|
}
|
|
}
|
|
}
|
|
|
|
// Note: Tags can be nil when frontmatter is not present or empty
|
|
// This is expected behavior in Go for uninitialized slices
|
|
} else {
|
|
// If parsing failed, the original input should be returned as text
|
|
if text != input {
|
|
t.Errorf("on error, text should equal input: got %q, want %q", text, input)
|
|
}
|
|
}
|
|
}()
|
|
})
|
|
}
|
|
|
|
// FuzzParseDelimiterHandling specifically tests delimiter edge cases
|
|
func FuzzParseDelimiterHandling(f *testing.F) {
|
|
// Seed corpus focusing on delimiter variations
|
|
f.Add("---", "content")
|
|
f.Add("", "---")
|
|
f.Add("---", "---")
|
|
f.Add("", "")
|
|
|
|
f.Fuzz(func(t *testing.T, prefix string, suffix string) {
|
|
// Build input with controllable parts around delimiters
|
|
inputs := []string{
|
|
prefix + "---\n" + suffix,
|
|
prefix + "---\r\n" + suffix,
|
|
prefix + "---\n---\n" + suffix,
|
|
prefix + "---\r\n---\r\n" + suffix,
|
|
prefix + "---\n" + "yaml: data\n" + "---\n" + suffix,
|
|
}
|
|
|
|
for _, input := range inputs {
|
|
func() {
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
t.Errorf("parse panicked with constructed input: %v", r)
|
|
}
|
|
}()
|
|
|
|
_, _, _ = parse(input)
|
|
}()
|
|
}
|
|
})
|
|
}
|