Files
aego/app/playground/playground_test.go
sanspie 41d949717d deploy: push-to-deploy docs site and playground
make site-headless builds the CLI without cgo (aego_nonative) and the
static site with the wasm playground. deploy/ holds the image, the
container nginx config (gzip_static with Vary), compose and deploy.sh
run by the post-receive hook on mainserv.

.gitignore ignores only the root site/ output; tools/gendocs/site was
never committed because of it.

Docs site: index and section summaries render code, bold and italic
instead of raw markdown; search.json gets the plain text. Page summaries
drop a leading list or quote marker. The nav toggle is three CSS bars
centred in a 32px button.

Playground: a loading overlay in the editor pane shows a spinner, a
progress bar and the downloaded size while aego.wasm streams in, then
'Starting engine' until the runtime is ready; a failed start keeps the
error on the overlay. docstool writes the wasm size and a version hash
into index.html; play.js, play.css, wasm_exec.js and aego.wasm are
referenced with ?v=<hash> so a deploy never mixes cached old files with
new ones.

Instruction budget: default 1M per callback (was 100k), playground 2M;
the donut needs ~120k per frame. Playground: software 3D donut example,
multi-line log entries render as monospace blocks, a fault is reported
once instead of also as a "script stopped" log line.

The compose project is named aego explicitly; the default name came from
the deploy/ directory and collided with other stacks on the host.
2026-10-14 22:40:20 +03:00

131 lines
3.3 KiB
Go

package playground
import (
"strings"
"testing"
)
func open(t *testing.T) *Playground {
t.Helper()
p, err := New()
if err != nil {
t.Fatal(err)
}
return p
}
func TestExamplesRun(t *testing.T) {
p := open(t)
ex := p.Examples()
if len(ex) == 0 {
t.Fatal("the documentation has no run blocks")
}
for _, e := range ex {
t.Run(e.ID, func(t *testing.T) {
res := p.Run(map[string]string{"main.aes": e.Code}, DefaultFrames)
for _, d := range res.Diagnostics {
if d.Severity == "error" {
t.Errorf("%s:%d:%d %s %s", d.File, d.Line, d.Col, d.Code, d.Message)
}
}
for _, f := range res.Faults {
t.Errorf("%s faulted with %s at %s:%d: %s", f.Script, f.Code, f.File, f.Line, f.Message)
}
if res.Frames != DefaultFrames {
t.Errorf("ran %d frames, want %d", res.Frames, DefaultFrames)
}
})
}
}
func TestBlinkLogs(t *testing.T) {
p := open(t)
code := ""
for _, e := range p.Examples() {
if strings.HasPrefix(e.ID, "language/coroutines/") {
code = e.Code
break
}
}
if code == "" {
t.Fatal("language/coroutines has no run block")
}
res := p.Run(map[string]string{"main.aes": code}, 60)
var got []string
for _, l := range res.Log {
if l.Channel == "script" {
got = append(got, l.Text)
}
}
want := []string{"blink 0", "blink 1", "blink 2", "done"}
if strings.Join(got, "|") != strings.Join(want, "|") {
t.Fatalf("log = %q, want %q", got, want)
}
}
func TestCompileReportsPosition(t *testing.T) {
p := open(t)
src := "package demo\n\nscript S {\n\tfunc Update(dt float) {\n\t\tx := missing\n\t}\n}\n"
ds := p.Compile(map[string]string{"main.aes": src})
for _, d := range ds {
if d.Severity == "error" && d.File == "demo/main.aes" && d.Line == 5 {
return
}
}
t.Fatalf("no error on demo/main.aes:5, got %+v", ds)
}
func TestTokensUseUTF16Offsets(t *testing.T) {
toks := Tokens("// привет\nvar x int")
if len(toks) == 0 || toks[0].Kind != "comment" || toks[0].End != 9 {
t.Fatalf("comment token = %+v", toks)
}
for _, tk := range toks {
if tk.Kind == "keyword" {
if tk.Start != 10 || tk.End != 13 {
t.Fatalf("var token = %+v", tk)
}
return
}
}
t.Fatal("no keyword token")
}
func TestReply(t *testing.T) {
p := open(t)
if out := p.Reply("tokens", `{"source":"var"}`); !strings.Contains(out, `"keyword"`) {
t.Fatalf("tokens reply = %s", out)
}
if out := p.Reply("nope", ""); !strings.Contains(out, `"error"`) {
t.Fatalf("unknown method reply = %s", out)
}
if out := p.Reply("stubs", ""); !strings.Contains(out, "stub:aego/math.aes") {
t.Fatalf("stubs reply has no aego/math")
}
}
func TestDonutRenders(t *testing.T) {
p := open(t)
res := p.Run(map[string]string{"main.aes": donutSource}, DefaultFrames)
if len(res.Faults) > 0 {
t.Fatalf("donut faulted: %+v", res.Faults)
}
frames := 0
for _, l := range res.Log {
if strings.HasPrefix(l.Text, "script stopped") {
t.Fatalf("fault is logged twice: %s", l.Text)
}
if !strings.HasPrefix(l.Text, "\n") {
continue
}
frames++
rows := strings.Split(strings.Trim(l.Text, "\n"), "\n")
if len(rows) != 22 || len(rows[0]) != 52 || !strings.ContainsAny(l.Text, "@#*") {
t.Fatalf("frame %d is %d rows, first %q", frames, len(rows), rows[0])
}
}
if frames != 1+DefaultFrames/12 {
t.Fatalf("rendered %d frames, want %d", frames, 1+DefaultFrames/12)
}
}