667f20a7b3
P0b — Canonical Markdown+frontmatter jobspec parser (R-013/R-014).
Parser (internal/jobspec/markdown.go, REQ-064):
- WorkloadSpec/RuntimeBlock/PortSpec/VolumeSpec types. ParseMarkdown
hand-rolled YAML frontmatter (no yaml.v3 dep). Kind validation (Job/
Service/DaemonSet per R-012). BOM-stripped frontmatter, byte-exact body
preservation (R-015) via the fuzz harness.
Dispatcher (internal/jobspec/dispatch.go, REQ-064):
- ParseFile/Dispatch routes on extension: .md->Markdown, .yaml/.yml->
Markdown-with-empty-body, .hcl->ParseHCL adapter. HCL adapter converts
Spec{Job,Tasks} to *WorkloadSpec (Kind=Job, Runtime.one_of=process).
Backward compat preserved (REQ-090) — orca job run old-spec.hcl works.
- Legacy Parse renamed ParseHCLLegacy, marked // Deprecated per R-013.
Fuzz harness (internal/jobspec/markdown_fuzz_test.go, REQ-067, R-015):
- FuzzParseMarkdownRoundTrip with 10 seed corpus entries (CRLF, BOM,
no-frontmatter, only-closing-separator, code-fence ---, trailing
whitespace, empty body, etc). Asserts byte-exact body round-trip.
Tests: markdown_test.go (19 tests), dispatch_test.go (17 tests), fuzz
(10 seeds). jobspec package 89.2% coverage. cli 81.8% (no regression).
18 packages pass, 20 bats pass, gofmt clean, verify-reqs 90 consistent.
---ci---
project: orca
phase: P0b
milestone: v0.9
status: execute
---/ci---
110 lines
3.7 KiB
Go
110 lines
3.7 KiB
Go
package jobspec
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// FuzzParseMarkdownRoundTrip is the REQ-067 fuzz harness for R-015
|
|
// byte-exact body preservation. It generates random frontmatter + body
|
|
// combinations, runs ParseMarkdown, and asserts that the parsed Body
|
|
// equals the original body byte-for-byte whenever parsing succeeds.
|
|
// When parsing fails (bad frontmatter), the iteration passes — the
|
|
// parser is allowed to reject malformed input.
|
|
//
|
|
// The seed corpus (added via f.Add) covers adversarial fixtures: CRLF
|
|
// body, BOM prefix, no frontmatter, only-closing-separator, body with
|
|
// `---` inside a code fence, trailing whitespace, empty body. The seed
|
|
// corpus runs as regular tests under `go test` (CI); random input runs
|
|
// only under `go test -fuzz=FuzzParseMarkdownRoundTrip` in a dedicated
|
|
// process.
|
|
func FuzzParseMarkdownRoundTrip(f *testing.F) {
|
|
// Seed 1: valid frontmatter + simple body.
|
|
f.Add([]byte("---\nkind: Job\nname: seed1\n---\n# body\n"))
|
|
|
|
// Seed 2: CRLF body.
|
|
f.Add([]byte("---\r\nkind: Job\r\nname: seed2\r\n---\r\n# body\r\nCRLF\r\n"))
|
|
|
|
// Seed 3: BOM prefix.
|
|
f.Add([]byte("\uFEFF---\nkind: Job\nname: seed3\n---\nbody\n"))
|
|
|
|
// Seed 4: no frontmatter (just body) — should fail to parse.
|
|
f.Add([]byte("# just a body\nno frontmatter\n"))
|
|
|
|
// Seed 5: frontmatter with only the closing `---` (no opening).
|
|
f.Add([]byte("body\n---\nmore body\n"))
|
|
|
|
// Seed 6: body containing `---` in a code fence.
|
|
f.Add([]byte("---\nkind: Job\nname: seed6\n---\n```bash\necho '---'\n```\n"))
|
|
|
|
// Seed 7: body with trailing whitespace.
|
|
f.Add([]byte("---\nkind: Job\nname: seed7\n---\nbody with trailing spaces \n"))
|
|
|
|
// Seed 8: empty body.
|
|
f.Add([]byte("---\nkind: Job\nname: seed8\n---\n"))
|
|
|
|
// Seed 9: empty frontmatter (should fail).
|
|
f.Add([]byte("---\n---\nbody\n"))
|
|
|
|
// Seed 10: body with no trailing newline.
|
|
f.Add([]byte("---\nkind: Job\nname: seed10\n---\nno trailing newline"))
|
|
|
|
f.Fuzz(func(t *testing.T, data []byte) {
|
|
// Reconstruct the body from the input so we can assert
|
|
// byte-exact round-trip. We do this by re-splitting the
|
|
// frontmatter using the same logic the parser uses, but only
|
|
// to extract the expected body. If the input has no valid
|
|
// frontmatter delimiter pair, ParseMarkdown will return an
|
|
// error and we pass the iteration.
|
|
expectedBody := extractExpectedBody(string(data))
|
|
|
|
spec, err := ParseMarkdown(data)
|
|
if err != nil {
|
|
// Parser rejected the input — acceptable for a fuzz
|
|
// iteration (the input may be malformed). Pass.
|
|
return
|
|
}
|
|
// R-015: body must be byte-exact.
|
|
if spec.Body != expectedBody {
|
|
t.Errorf("R-015 body round-trip mismatch:\n got = %q\nwant = %q", spec.Body, expectedBody)
|
|
}
|
|
})
|
|
}
|
|
|
|
// extractExpectedBody returns the body portion of a Markdown jobspec
|
|
// input using the same delimiter-splitting logic as splitFrontmatter,
|
|
// so the fuzz harness can assert byte-exact preservation independently
|
|
// of the parser's internal extraction. If the input has no valid
|
|
// frontmatter, the result is "" (and ParseMarkdown will error).
|
|
func extractExpectedBody(content string) string {
|
|
stripped := content
|
|
if strings.HasPrefix(stripped, "\uFEFF") {
|
|
stripped = stripped[len("\uFEFF"):]
|
|
}
|
|
trimmed := strings.TrimLeft(stripped, "\r\n\t ")
|
|
if !strings.HasPrefix(trimmed, "---") {
|
|
return ""
|
|
}
|
|
rest := trimmed[3:]
|
|
if len(rest) > 0 && rest[0] != '\n' && rest[0] != '\r' {
|
|
return ""
|
|
}
|
|
rest = strings.TrimLeft(rest, "\r\n")
|
|
idx := findClosingDelimiter(rest)
|
|
if idx < 0 {
|
|
return ""
|
|
}
|
|
afterClose := rest[idx:]
|
|
newlineIdx := strings.IndexAny(afterClose, "\r\n")
|
|
if newlineIdx < 0 {
|
|
return ""
|
|
}
|
|
bodyStart := newlineIdx
|
|
if strings.HasPrefix(afterClose[bodyStart:], "\r\n") {
|
|
bodyStart += 2
|
|
} else {
|
|
bodyStart += 1
|
|
}
|
|
return afterClose[bodyStart:]
|
|
}
|