package launch import ( "encoding/json" "errors" "fmt" "os" "path/filepath" "strings" "sync" "testing" "time" ) type refusingState struct{ subscribing } func (*refusingState) State(string, json.RawMessage) (json.RawMessage, error) { return nil, errors.New("the bus did not answer in 10s") } // A bundle that asks the runtime for a state once it is started, as a tool does. const askingBundle = `#!/bin/sh while IFS= read -r line; do id=$(printf '%s' "$line" | sed -n 's/.*"id":\([0-9]*\)[,}].*/\1/p') case "$line" in *'"method":"initialize"'*) printf '{"jsonrpc":"2.0","id":%s,"result":{}}\n' "$id" ;; *'"method":"tools/list"'*) printf '{"jsonrpc":"2.0","id":%s,"result":{"tools":[]}}\n' "$id" printf '{"jsonrpc":"2.0","id":"q1","method":"mesh/state.get","params":{"state":"copied","key":"k"}}\n' ;; esac done ` // A state operation the bus refuses or lets time out is said in the runtime's log with its method, its bundle and its // time (issue 1966: a hand-act seat whose calls hung said nothing). func TestAFailedStateOperationIsSaidInTheRuntimesLog(t *testing.T) { dir := t.TempDir() entry := filepath.Join(dir, "bundle") if err := os.WriteFile(entry, []byte(askingBundle), 0o755); err != nil { t.Fatal(err) } var mu sync.Mutex var lines []string logf := func(f string, a ...any) { mu.Lock() defer mu.Unlock() lines = append(lines, fmt.Sprintf(f, a...)) } l, err := Start("asker", entry, os.Environ(), &refusingState{}, logf) if err != nil { t.Fatal(err) } t.Cleanup(l.Stop) deadline := time.Now().Add(5 * time.Second) for time.Now().Before(deadline) { mu.Lock() all := strings.Join(lines, "\n") mu.Unlock() if strings.Contains(all, "asker's bundle asked mesh/state.get and it failed after") && strings.Contains(all, "did not answer in 10s") { return } time.Sleep(20 * time.Millisecond) } t.Fatalf("the failed state operation was not said; the log: %v", lines) }