Prepare Sandwich Hime v1 release candidate source
This commit is contained in:
@@ -0,0 +1,138 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"go/ast"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var diagnosticCodePattern = regexp.MustCompile(`^HIM[0-9]{4}$`)
|
||||
|
||||
func TestV1DiagnosticCodeContract(t *testing.T) {
|
||||
t.Parallel()
|
||||
directory := packageDirectory(t)
|
||||
entries, err := os.ReadDir(directory)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
codes := make(map[string]struct{})
|
||||
fileSet := token.NewFileSet()
|
||||
for _, entry := range entries {
|
||||
if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".go") || strings.HasSuffix(entry.Name(), "_test.go") {
|
||||
continue
|
||||
}
|
||||
parsed, err := parser.ParseFile(fileSet, filepath.Join(directory, entry.Name()), nil, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("parse %s: %v", entry.Name(), err)
|
||||
}
|
||||
ast.Inspect(parsed, func(node ast.Node) bool {
|
||||
literal, ok := node.(*ast.BasicLit)
|
||||
if !ok || literal.Kind != token.STRING {
|
||||
return true
|
||||
}
|
||||
value, err := strconv.Unquote(literal.Value)
|
||||
if err == nil && diagnosticCodePattern.MatchString(value) {
|
||||
codes[value] = struct{}{}
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
actual := make([]string, 0, len(codes))
|
||||
for code := range codes {
|
||||
actual = append(actual, code)
|
||||
}
|
||||
sort.Strings(actual)
|
||||
assertContractFile(t, filepath.Join(directory, "..", "..", "contracts", "diagnostic-codes-v1.txt"), strings.Join(actual, "\n")+"\n")
|
||||
}
|
||||
|
||||
func TestV1GeneratedProvenanceContract(t *testing.T) {
|
||||
t.Parallel()
|
||||
compiled, diagnostics := Compile("views/generic.sando", []byte(`<?sando go
|
||||
package views
|
||||
func List[T ~string](values []T)
|
||||
?>
|
||||
<ul><? for _, value := range values { ?><li><?= value ?></li><? } ?></ul>`))
|
||||
assertNoErrorDiagnostics(t, diagnostics)
|
||||
lines := strings.Split(string(compiled.Code), "\n")
|
||||
if len(lines) < 4 {
|
||||
t.Fatalf("generated header has %d lines", len(lines))
|
||||
}
|
||||
actual := []string{lines[0]}
|
||||
if !strings.HasPrefix(lines[1], "// himesan:compiler ") {
|
||||
t.Fatalf("compiler provenance line = %q", lines[1])
|
||||
}
|
||||
actual = append(actual, "// himesan:compiler <compiler-version>")
|
||||
if !strings.HasPrefix(lines[2], "// himesan:runtime-abi ") {
|
||||
t.Fatalf("runtime provenance line = %q", lines[2])
|
||||
}
|
||||
actual = append(actual, "// himesan:runtime-abi <runtime-abi>")
|
||||
if !regexp.MustCompile(`^// himesan:source-sha256 [0-9a-f]{64}$`).MatchString(lines[3]) {
|
||||
t.Fatalf("source provenance line = %q", lines[3])
|
||||
}
|
||||
actual = append(actual, "// himesan:source-sha256 <lowercase-sha256>")
|
||||
generated := string(compiled.Code)
|
||||
if !regexp.MustCompile(`(?m)^var _ = [A-Za-z_][A-Za-z0-9_]*\.ABISandoV1$`).MatchString(generated) {
|
||||
t.Fatal("generated output is missing the compile-time ABI marker")
|
||||
}
|
||||
actual = append(actual, "var _ = <sando-import>.ABISandoV1")
|
||||
if !regexp.MustCompile(`(?m)^//line [^\r\n]+:[1-9][0-9]*:[1-9][0-9]*$`).MatchString(generated) {
|
||||
t.Fatal("generated output is missing source mappings")
|
||||
}
|
||||
actual = append(actual, "//line <source-path>:<line>:<column>")
|
||||
assertContractFile(t, filepath.Join(packageDirectory(t), "..", "..", "contracts", "generated-provenance-v1.txt"), strings.Join(actual, "\n")+"\n")
|
||||
}
|
||||
|
||||
func TestV1GenericComponentSignature(t *testing.T) {
|
||||
t.Parallel()
|
||||
source := []byte(`<?sando go
|
||||
package views
|
||||
func List[T ~string](values []T)
|
||||
?>
|
||||
<ul><? for _, value := range values { ?><li><?= value ?></li><? } ?></ul>`)
|
||||
compiled, diagnostics := Compile("views/list.sando", source)
|
||||
assertNoErrorDiagnostics(t, diagnostics)
|
||||
if !bytes.Contains(compiled.Code, []byte("func List[T ~string](values []T)")) {
|
||||
t.Fatalf("generic signature was not preserved:\n%s", compiled.Code)
|
||||
}
|
||||
analyses := AnalyzeSources(context.Background(), []SourceInput{{Path: "views/list.sando", Source: source}})
|
||||
if len(analyses) != 1 {
|
||||
t.Fatalf("analysis count = %d, want 1", len(analyses))
|
||||
}
|
||||
analysis := analyses[0]
|
||||
if analysis.TypeParams != "[T ~string]" || analysis.Params != "(values []T)" || analysis.Signature != "func List[T ~string](values []T)" {
|
||||
t.Fatalf("generic analysis contract = %#v", analysis)
|
||||
}
|
||||
}
|
||||
|
||||
func packageDirectory(t *testing.T) string {
|
||||
t.Helper()
|
||||
_, file, _, ok := runtime.Caller(0)
|
||||
if !ok {
|
||||
t.Fatal("runtime.Caller failed")
|
||||
}
|
||||
return filepath.Dir(file)
|
||||
}
|
||||
|
||||
func assertContractFile(t *testing.T, path, actual string) {
|
||||
t.Helper()
|
||||
expected, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
expected = bytes.TrimPrefix(expected, []byte("# SPDX-License-Identifier: AGPL-3.0-only\n\n"))
|
||||
if string(expected) != actual {
|
||||
t.Fatalf("contract drift in %s\n--- expected ---\n%s--- actual ---\n%s", path, expected, actual)
|
||||
}
|
||||
}
|
||||
@@ -128,6 +128,9 @@ func TestRCDATACannotBeBypassedByTrustedHTML(t *testing.T) {
|
||||
view := View{Name: "title", URL: "/", JS: sando.TrustJS(""), HTML: sando.TrustHTML("</textarea><script>bad()</script>")}
|
||||
if err := sando.Render(context.Background(), &output, Page(view)); err != nil { t.Fatal(err) }
|
||||
if strings.Contains(output.String(), "</textarea><script>") { t.Fatalf("RCDATA boundary escaped: %s", output.String()) }
|
||||
output.Reset()
|
||||
if err := sando.Render(context.Background(), &output, List([]string{"one", "two"})); err != nil { t.Fatal(err) }
|
||||
if output.String() != "\n<ul><li>one</li><li>two</li></ul>" { t.Fatalf("generic component output: %q", output.String()) }
|
||||
}
|
||||
`)
|
||||
templatePath := filepath.Join(directory, "page.sando")
|
||||
@@ -141,7 +144,13 @@ func Page(view View)
|
||||
<script><?= view.JS ?></script>
|
||||
<textarea><?= view.HTML ?></textarea>
|
||||
</body></html>`)
|
||||
result, err := Generate(context.Background(), []string{templatePath})
|
||||
genericPath := filepath.Join(directory, "list.sando")
|
||||
mustWrite(t, genericPath, `<?sando go
|
||||
package generated
|
||||
func List[T ~string](values []T)
|
||||
?>
|
||||
<ul><? for _, value := range values { ?><li><?= value ?></li><? } ?></ul>`)
|
||||
result, err := Generate(context.Background(), []string{templatePath, genericPath})
|
||||
if err != nil {
|
||||
t.Fatalf("Generate failed: %v (%v)", err, result.Diagnostics)
|
||||
}
|
||||
|
||||
@@ -2,7 +2,16 @@
|
||||
|
||||
package compiler
|
||||
|
||||
import "testing"
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func FuzzCompileNeverPanics(f *testing.F) {
|
||||
for _, seed := range []string{
|
||||
@@ -14,13 +23,69 @@ func FuzzCompileNeverPanics(f *testing.F) {
|
||||
"<?sando go\npackage p\nfunc F(v string)\n?>\n<script><!--<script></script>\n<?= v ?>\n<!--\n</script>\n-->",
|
||||
"\xef\xbb\xbf\r\n<?sando go\r\npackage p\r\nfunc F()\r\n?>\r\n<p>x</p>",
|
||||
} {
|
||||
f.Add(seed)
|
||||
f.Add(seed, "fuzz.sando")
|
||||
}
|
||||
f.Fuzz(func(t *testing.T, source string) {
|
||||
_, _ = Compile("fuzz.sando", []byte(source))
|
||||
f.Add("<?sando go\npackage p\nfunc F()\n?>\n<p>x</p>", "path%with\ncontrols\x00.sando")
|
||||
f.Fuzz(func(t *testing.T, source, mapping string) {
|
||||
if len(source) > 64<<10 || len(mapping) > 4<<10 {
|
||||
t.Skip()
|
||||
}
|
||||
input := []byte(source)
|
||||
before := append([]byte(nil), input...)
|
||||
first, firstDiagnostics := compileWithMapping("fuzz.sando", input, mapping)
|
||||
second, secondDiagnostics := compileWithMapping("fuzz.sando", input, mapping)
|
||||
if !bytes.Equal(input, before) {
|
||||
t.Fatal("compiler modified its source input")
|
||||
}
|
||||
if !reflect.DeepEqual(firstDiagnostics, secondDiagnostics) ||
|
||||
first.SourcePath != second.SourcePath || first.OutputPath != second.OutputPath ||
|
||||
first.Package != second.Package || first.Component != second.Component ||
|
||||
first.Digest != second.Digest || !bytes.Equal(first.Code, second.Code) {
|
||||
t.Fatal("repeated in-memory compilation was not deterministic")
|
||||
}
|
||||
for _, diagnostic := range firstDiagnostics {
|
||||
if diagnostic.Path != "fuzz.sando" || diagnostic.Line < 1 || diagnostic.Column < 1 {
|
||||
t.Fatalf("diagnostic has an invalid location: %#v", diagnostic)
|
||||
}
|
||||
if !validDiagnosticCode(diagnostic.Code) || strings.TrimSpace(diagnostic.Message) != diagnostic.Message || diagnostic.Message == "" {
|
||||
t.Fatalf("diagnostic violates the public shape: %#v", diagnostic)
|
||||
}
|
||||
if diagnostic.Severity != SeverityError && diagnostic.Severity != SeverityWarning {
|
||||
t.Fatalf("diagnostic has an invalid severity: %#v", diagnostic)
|
||||
}
|
||||
}
|
||||
if len(first.Code) == 0 {
|
||||
return
|
||||
}
|
||||
if first.SourcePath != "fuzz.sando" || first.OutputPath != "fuzz.sando.go" {
|
||||
t.Fatalf("compiled paths are invalid: %#v", first)
|
||||
}
|
||||
if first.Digest != fmt.Sprintf("%x", sha256.Sum256(input)) {
|
||||
t.Fatalf("source digest is not bound to the exact input: %s", first.Digest)
|
||||
}
|
||||
if _, err := parser.ParseFile(token.NewFileSet(), "fuzz.sando.go", first.Code, parser.AllErrors); err != nil {
|
||||
t.Fatalf("successful compilation produced invalid Go: %v\n%s", err, first.Code)
|
||||
}
|
||||
for _, line := range bytes.Split(first.Code, []byte{'\n'}) {
|
||||
if bytes.HasPrefix(line, []byte("//line ")) && (bytes.ContainsAny(line, "\r\x00") || bytes.Count(line, []byte(":")) < 2) {
|
||||
t.Fatalf("source-map directive was not safely encoded: %q", line)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func validDiagnosticCode(code string) bool {
|
||||
if len(code) != 7 || !strings.HasPrefix(code, "HIM") {
|
||||
return false
|
||||
}
|
||||
for _, digit := range code[3:] {
|
||||
if digit < '0' || digit > '9' {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func FuzzGoDelimiterNeverPanics(f *testing.F) {
|
||||
for _, seed := range []string{`?>`, `"?>" ?>`, "`?>` ?>", `/* ?> */ ?>`, "// ?>\n?>", `'?' ?>`} {
|
||||
f.Add(seed, uint8(0))
|
||||
|
||||
Reference in New Issue
Block a user