PPysolate SpineREAD THE CODE · UNDERSTAND THE SYSTEM
章节目录
源码索引 / CURRENT IMPLEMENTATION

prefix_test.go

181 行 · 构建时读取的实际文件,不是讲解用伪代码。

package spine

import (
	"context"
	"encoding/json"
	"errors"
	"os"
	"strings"
	"sync"
	"testing"
	"time"
)

func TestPrefixGuest(t *testing.T) {
	wasm, err := os.ReadFile("dist/spine.wasm")
	if err != nil {
		t.Fatal(err)
	}
	var mu sync.Mutex
	calls := map[string]int{}
	var started, stopped chan struct{}
	read := func(ctx context.Context, raw json.RawMessage) (any, error) {
		var a struct {
			Key string `json:"key"`
		}
		if err := json.Unmarshal(raw, &a); err != nil {
			return nil, err
		}
		mu.Lock()
		calls[a.Key]++
		mu.Unlock()
		if started != nil {
			select {
			case started <- struct{}{}:
			default:
			}
		}
		switch a.Key {
		case "book":
			return 21, nil
		case "shipping":
			return 5, nil
		case "choose":
			return "book", nil
		case "wait":
			<-ctx.Done()
			close(stopped)
			return nil, ctx.Err()
		default:
			return nil, errors.New("missing key")
		}
	}
	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
	defer cancel()
	r, err := New(ctx, wasm, map[string]Tool{"lookup": read}, "lookup")
	if err != nil {
		t.Fatal(err)
	}
	defer r.Close(context.Background())
	count := func(key string) int { mu.Lock(); defer mu.Unlock(); return calls[key] }
	stream := func(parts ...string) <-chan string {
		ch := make(chan string, len(parts))
		for _, p := range parts {
			ch <- p
		}
		close(ch)
		return ch
	}
	t.Run("starts before source closes and is claimed once", func(t *testing.T) {
		runCtx, stop := context.WithCancel(ctx)
		defer stop()
		started = make(chan struct{}, 2)
		chunks := make(chan string)
		// Producer refuses to send the rest until the real Host tool has started.
		go func() {
			defer close(chunks)
			select {
			case chunks <- "price=tool('lookup',key=inputs['item'])\n":
			case <-runCtx.Done():
				return
			}
			select {
			case <-started:
			case <-runCtx.Done():
				return
			}
			select {
			case chunks <- "shipping=tool('lookup',key='shipping')\nresult=price+shipping":
			case <-runCtx.Done():
			}
		}()
		before := count("book")
		out, err := r.RunPrefix(runCtx, chunks, map[string]any{"item": "book"})
		if err != nil || string(out.Value) != "26" || count("book") != before+1 {
			t.Fatalf("%+v %v", out, err)
		}
		started = nil
	})
	t.Run("unsupported final syntax reuses the leading read", func(t *testing.T) {
		before := count("book")
		out, err := r.RunPrefix(ctx, stream("a=tool('lookup',key='book')\n", "import math\nresult=a"), nil)
		if err != nil || string(out.Value) != "21" || out.Transformed != "" || count("book") != before+1 {
			t.Fatalf("%+v %v", out, err)
		}
	})
	t.Run("partial statement is not executed", func(t *testing.T) {
		before := count("book")
		_, err := r.RunPrefix(ctx, stream("a=tool('lookup',key='book'"), nil)
		if err == nil || !strings.Contains(err.Error(), "SyntaxError") || count("book") != before {
			t.Fatalf("%v", err)
		}
	})
	t.Run("duplicate requests have distinct handles", func(t *testing.T) {
		before := count("book")
		out, err := r.RunPrefix(ctx, stream("a=tool('lookup',key='book')\n", "b=tool('lookup',key='book')\nresult=a+b"), nil)
		if err != nil || string(out.Value) != "42" || count("book") != before+2 {
			t.Fatalf("%+v %v", out, err)
		}
	})
	t.Run("dependency waits for final execution", func(t *testing.T) {
		out, err := r.RunPrefix(ctx, stream("key=tool('lookup',key='choose')\n", "a=tool('lookup',key=key)\nresult=a"), nil)
		if err != nil || string(out.Value) != "21" {
			t.Fatalf("%+v %v", out, err)
		}
	})
	t.Run("unselected branch is not prepared", func(t *testing.T) {
		before := count("book")
		out, err := r.RunPrefix(ctx, stream("if False:\n a=tool('lookup',key='book')\n", "result=7"), nil)
		if err != nil || string(out.Value) != "7" || count("book") != before {
			t.Fatalf("%+v %v", out, err)
		}
	})
	t.Run("cancellation during source intake joins workers", func(t *testing.T) {
		runCtx, stop := context.WithCancel(ctx)
		defer stop()
		started, stopped = make(chan struct{}, 1), make(chan struct{})
		chunks := make(chan string, 1)
		chunks <- "a=tool('lookup',key='wait')\n"
		go func() {
			select {
			case <-started:
				stop()
			case <-runCtx.Done():
			}
		}()
		_, err := r.RunPrefix(runCtx, chunks, nil)
		if err == nil {
			t.Fatal("expected cancellation")
		}
		select {
		case <-stopped:
		default:
			t.Fatal("worker still alive")
		}
		started = nil
	})
	t.Run("final syntax error cancels prepared reads", func(t *testing.T) {
		stopped = make(chan struct{})
		_, err := r.RunPrefix(ctx, stream("a=tool('lookup',key='wait')\n", "result = ("), nil)
		if err == nil || !strings.Contains(err.Error(), "SyntaxError") {
			t.Fatalf("%v", err)
		}
		select {
		case <-stopped:
		default:
			t.Fatal("worker still alive")
		}
	})
	t.Run("source bound", func(t *testing.T) {
		_, err := r.RunPrefix(ctx, stream(strings.Repeat("#", maxMessage+1)), nil)
		if err == nil || !strings.Contains(err.Error(), "1 MiB") {
			t.Fatalf("%v", err)
		}
	})
	t.Run("empty stream executes empty program", func(t *testing.T) {
		out, err := r.RunPrefix(ctx, stream(), nil)
		if err != nil || string(out.Value) != "null" {
			t.Fatalf("%+v %v", out, err)
		}
	})
}