release: prepare Sandwich Hime v1 beta
Publish the sanitized Beta 1 source candidate with version-stable generated provenance, classroom/evaluation support boundaries, provisional macOS support, signed-source release policy, and an exact candidate preflight. Material implementation, drafting, and review were assisted by OpenAI Codex. Cole Speelman reviewed the public snapshot and accepts human responsibility for the contribution. Signed-off-by: Cole Speelman <crspeelman@gmail.com>
This commit is contained in:
@@ -316,6 +316,28 @@ func auditTrustCalls(file *sourceFile) []Diagnostic {
|
||||
return diagnostics
|
||||
}
|
||||
|
||||
func bytesEqual(a, b []byte) bool {
|
||||
return bytes.Equal(a, b)
|
||||
// generatedCodeEqual compares the freshness-relevant portions of two
|
||||
// generated files. The compiler release is informational provenance: changing
|
||||
// only that line must not make otherwise identical output stale. Every other
|
||||
// byte remains part of the generated-code contract.
|
||||
func generatedCodeEqual(a, b []byte) bool {
|
||||
if bytes.Equal(a, b) {
|
||||
return true
|
||||
}
|
||||
aBody, aOK := generatedCodeWithoutCompilerVersion(a)
|
||||
bBody, bOK := generatedCodeWithoutCompilerVersion(b)
|
||||
return aOK && bOK && bytes.Equal(aBody, bBody)
|
||||
}
|
||||
|
||||
func generatedCodeWithoutCompilerVersion(code []byte) ([]byte, bool) {
|
||||
prefix := []byte(generatedPrefix + "\n// himesan:compiler ")
|
||||
if !bytes.HasPrefix(code, prefix) {
|
||||
return nil, false
|
||||
}
|
||||
remainder := code[len(prefix):]
|
||||
lineEnd := bytes.IndexByte(remainder, '\n')
|
||||
if lineEnd <= 0 {
|
||||
return nil, false
|
||||
}
|
||||
return remainder[lineEnd:], true
|
||||
}
|
||||
|
||||
@@ -67,11 +67,42 @@ func TestCommittedGoldenOutput(t *testing.T) {
|
||||
wantPath := sourcePath + ".go"
|
||||
compiled, diagnostics := compileWithMapping(sourcePath, mustRead(t, sourcePath), "internal/compiler/testdata/golden/basic.sando")
|
||||
assertNoErrorDiagnostics(t, diagnostics)
|
||||
if want := mustRead(t, wantPath); !bytes.Equal(compiled.Code, want) {
|
||||
if want := mustRead(t, wantPath); !generatedCodeEqual(compiled.Code, want) {
|
||||
t.Fatalf("committed golden output is stale; run himesan generate\n--- got ---\n%s\n--- want ---\n%s", compiled.Code, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGeneratedCodeEqualityIgnoresOnlyCompilerProvenance(t *testing.T) {
|
||||
t.Parallel()
|
||||
compiled, diagnostics := Compile("hello.sando", []byte(simpleSource("Hello", "hello")))
|
||||
assertNoErrorDiagnostics(t, diagnostics)
|
||||
|
||||
beta := replaceGeneratedCompilerVersion(t, compiled.Code, "v1.0.0-beta.1")
|
||||
if !generatedCodeEqual(compiled.Code, beta) {
|
||||
t.Fatal("compiler provenance alone made generated output unequal")
|
||||
}
|
||||
|
||||
changes := map[string][]byte{
|
||||
"runtime ABI": bytes.Replace(beta, []byte("himesan:runtime-abi sando.v1"), []byte("himesan:runtime-abi sando.v2"), 1),
|
||||
"source digest": bytes.Replace(beta, []byte("himesan:source-sha256"), []byte("himesan:source-sha257"), 1),
|
||||
"generated semantics": bytes.Replace(beta, []byte(".WriteString("), []byte(".WriteText("), 1),
|
||||
"missing provenance marker": bytes.Replace(beta, []byte("// himesan:compiler "), []byte("// compiler: "), 1),
|
||||
"empty provenance": replaceGeneratedCompilerVersion(t, beta, ""),
|
||||
}
|
||||
for name, changed := range changes {
|
||||
name, changed := name, changed
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
if bytes.Equal(beta, changed) {
|
||||
t.Fatalf("test mutation %q did not change generated output", name)
|
||||
}
|
||||
if generatedCodeEqual(compiled.Code, changed) {
|
||||
t.Fatalf("generatedCodeEqual ignored %s change", name)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHeaderAllowsBOMWhitespaceAndGoLexicalDelimiters(t *testing.T) {
|
||||
t.Parallel()
|
||||
source := "\xef\xbb\xbf \r\n\t<?sando go\npackage views\nfunc Lexical(v struct { Tag string `json:\"?>\"` })\n?>\n<p><?= \"?>\" ?></p>"
|
||||
@@ -580,6 +611,24 @@ func mustRead(t *testing.T, path string) []byte {
|
||||
return content
|
||||
}
|
||||
|
||||
func replaceGeneratedCompilerVersion(t *testing.T, code []byte, compilerVersion string) []byte {
|
||||
t.Helper()
|
||||
prefix := []byte(generatedPrefix + "\n// himesan:compiler ")
|
||||
if !bytes.HasPrefix(code, prefix) {
|
||||
t.Fatal("generated output has no compiler provenance line")
|
||||
}
|
||||
remainder := code[len(prefix):]
|
||||
lineEnd := bytes.IndexByte(remainder, '\n')
|
||||
if lineEnd < 0 {
|
||||
t.Fatal("generated compiler provenance line has no terminator")
|
||||
}
|
||||
replaced := make([]byte, 0, len(code)-lineEnd+len(compilerVersion))
|
||||
replaced = append(replaced, prefix...)
|
||||
replaced = append(replaced, compilerVersion...)
|
||||
replaced = append(replaced, remainder[lineEnd:]...)
|
||||
return replaced
|
||||
}
|
||||
|
||||
func assertNoErrorDiagnostics(t *testing.T, diagnostics []Diagnostic) {
|
||||
t.Helper()
|
||||
if hasErrors(diagnostics) {
|
||||
|
||||
@@ -3,7 +3,9 @@
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
@@ -12,6 +14,79 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDevelopmentAndBetaBinariesShareGeneratedOutput(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping compiler-binary integration in short mode")
|
||||
}
|
||||
t.Parallel()
|
||||
|
||||
_, thisFile, _, ok := runtime.Caller(0)
|
||||
if !ok {
|
||||
t.Fatal("runtime.Caller failed")
|
||||
}
|
||||
repositoryRoot := filepath.Clean(filepath.Join(filepath.Dir(thisFile), "..", ".."))
|
||||
developmentBinary := buildHimesanBinary(t, repositoryRoot, "himesan-development", "")
|
||||
betaVersion := "v1.0.0-beta.1"
|
||||
betaBinary := buildHimesanBinary(t, repositoryRoot, "himesan-beta", "-X gamertan.com/sandwich-hime/internal/version.Compiler="+betaVersion)
|
||||
|
||||
if got := compilerVersionFromBinary(t, developmentBinary, repositoryRoot); got != "0.1.0-dev" {
|
||||
t.Fatalf("development binary version = %q, want 0.1.0-dev", got)
|
||||
}
|
||||
if got := compilerVersionFromBinary(t, betaBinary, repositoryRoot); got != betaVersion {
|
||||
t.Fatalf("beta binary version = %q, want %q", got, betaVersion)
|
||||
}
|
||||
|
||||
directory := resolvedTempDir(t)
|
||||
mustWrite(t, filepath.Join(directory, "go.mod"), "module example.test/provenance\n\ngo 1.25\n")
|
||||
sourcePath := filepath.Join(directory, "hello.sando")
|
||||
mustWrite(t, sourcePath, `<?sando go
|
||||
package views
|
||||
func Hello(name string)
|
||||
?>
|
||||
<p>Hello <?= name ?></p>
|
||||
`)
|
||||
runHimesanBinary(t, developmentBinary, directory, "generate", "hello.sando")
|
||||
outputPath := sourcePath + ".go"
|
||||
developmentOutput := mustRead(t, outputPath)
|
||||
if !bytes.Contains(developmentOutput, []byte("// himesan:compiler 0.1.0-dev\n")) {
|
||||
t.Fatalf("development compiler did not record honest provenance:\n%s", developmentOutput)
|
||||
}
|
||||
|
||||
runHimesanBinary(t, betaBinary, directory, "check", "hello.sando")
|
||||
runHimesanBinary(t, betaBinary, directory, "generate", "hello.sando")
|
||||
if after := mustRead(t, outputPath); !bytes.Equal(after, developmentOutput) {
|
||||
t.Fatalf("beta compiler rewrote otherwise-current development provenance\n--- before ---\n%s\n--- after ---\n%s", developmentOutput, after)
|
||||
}
|
||||
|
||||
if err := os.Remove(outputPath); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
runHimesanBinary(t, betaBinary, directory, "generate", "hello.sando")
|
||||
betaOutput := mustRead(t, outputPath)
|
||||
if !bytes.Contains(betaOutput, []byte("// himesan:compiler "+betaVersion+"\n")) {
|
||||
t.Fatalf("beta compiler did not record honest provenance:\n%s", betaOutput)
|
||||
}
|
||||
runHimesanBinary(t, developmentBinary, directory, "check", "hello.sando")
|
||||
runHimesanBinary(t, developmentBinary, directory, "generate", "hello.sando")
|
||||
if after := mustRead(t, outputPath); !bytes.Equal(after, betaOutput) {
|
||||
t.Fatalf("development compiler rewrote otherwise-current beta provenance\n--- before ---\n%s\n--- after ---\n%s", betaOutput, after)
|
||||
}
|
||||
|
||||
tampered := bytes.Replace(betaOutput, []byte(".WriteText("), []byte(".WriteAttr("), 1)
|
||||
if bytes.Equal(tampered, betaOutput) {
|
||||
t.Fatal("semantic tamper did not find generated WriteText call")
|
||||
}
|
||||
if err := os.WriteFile(outputPath, tampered, 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
command := exec.Command(betaBinary, "check", "hello.sando")
|
||||
command.Dir = directory
|
||||
output, err := command.CombinedOutput()
|
||||
if err == nil || !strings.Contains(string(output), "HIM2204") {
|
||||
t.Fatalf("beta check accepted semantic generated-code tamper: err=%v\n%s", err, output)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGeneratedOutputCompilesInTemporaryModule(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping temporary-module compilation in short mode")
|
||||
@@ -81,3 +156,46 @@ func Page(view View)
|
||||
t.Fatal("generated output leaked the compiler checkout path")
|
||||
}
|
||||
}
|
||||
|
||||
func buildHimesanBinary(t *testing.T, repositoryRoot, name, linkerFlags string) string {
|
||||
t.Helper()
|
||||
if runtime.GOOS == "windows" {
|
||||
name += ".exe"
|
||||
}
|
||||
path := filepath.Join(t.TempDir(), name)
|
||||
arguments := []string{"build", "-trimpath"}
|
||||
if linkerFlags != "" {
|
||||
arguments = append(arguments, "-ldflags", linkerFlags)
|
||||
}
|
||||
arguments = append(arguments, "-o", path, "./cmd/himesan")
|
||||
command := exec.Command("go", arguments...)
|
||||
command.Dir = repositoryRoot
|
||||
command.Env = append(os.Environ(), "GOWORK=off")
|
||||
if output, err := command.CombinedOutput(); err != nil {
|
||||
t.Fatalf("build %s: %v\n%s", name, err, output)
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
func compilerVersionFromBinary(t *testing.T, binary, directory string) string {
|
||||
t.Helper()
|
||||
output := runHimesanBinary(t, binary, directory, "version", "--json")
|
||||
var information struct {
|
||||
Compiler string `json:"compiler"`
|
||||
}
|
||||
if err := json.Unmarshal(output, &information); err != nil {
|
||||
t.Fatalf("decode compiler version from %s: %v\n%s", binary, err, output)
|
||||
}
|
||||
return information.Compiler
|
||||
}
|
||||
|
||||
func runHimesanBinary(t *testing.T, binary, directory string, arguments ...string) []byte {
|
||||
t.Helper()
|
||||
command := exec.Command(binary, arguments...)
|
||||
command.Dir = directory
|
||||
output, err := command.CombinedOutput()
|
||||
if err != nil {
|
||||
t.Fatalf("%s %s: %v\n%s", binary, strings.Join(arguments, " "), err, output)
|
||||
}
|
||||
return output
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ func Generate(ctx context.Context, paths []string) (Result, error) {
|
||||
break
|
||||
}
|
||||
existing, readErr := os.ReadFile(file.OutputPath)
|
||||
if readErr == nil && bytesEqual(existing, file.Code) {
|
||||
if readErr == nil && generatedCodeEqual(existing, file.Code) {
|
||||
result.Files[index].Changed = false
|
||||
result.Unchanged++
|
||||
continue
|
||||
@@ -112,7 +112,7 @@ func Check(ctx context.Context, paths []string) (Result, error) {
|
||||
}
|
||||
continue
|
||||
}
|
||||
if bytesEqual(existing, file.Code) {
|
||||
if generatedCodeEqual(existing, file.Code) {
|
||||
result.Unchanged++
|
||||
continue
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user