mirror of
https://github.com/priyanshujain/sanderling.git
synced 2026-10-02 19:17:10 +00:00
feat(verifier): encode state.logs for the web host
Claude-Session: https://claude.ai/code/session_01ShuAy8q8ZfPi8KHxwc8JpQ
This commit is contained in:
1 parent
226f5805aa
commit
e18dafeba9
2 files changed
+78
-6
No files matched your search
@@ -468,19 +468,44 @@ func runtimeMillis(stepTime, runStart time.Time) int64 {
|
|||||||
return stepTime.Sub(runStart).Milliseconds()
|
return stepTime.Sub(runStart).Milliseconds()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// logFields is the ONE description of a state.logs entry, for the same reason
|
||||||
|
// lastActionFields is: the goja host turns it into a JS object (logsArray) and
|
||||||
|
// the web host receives the same fields as JSON (EncodeLogs), so a property
|
||||||
|
// counting error-level lines cannot read one shape on native and another on web.
|
||||||
|
func logFields(entry LogEntry) []actionField {
|
||||||
|
return []actionField{
|
||||||
|
{key: "unixMillis", value: entry.UnixMillis},
|
||||||
|
{key: "level", value: entry.Level},
|
||||||
|
{key: "tag", value: entry.Tag},
|
||||||
|
{key: "message", value: entry.Message},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func logsArray(runtime *goja.Runtime, logs []LogEntry) *goja.Object {
|
func logsArray(runtime *goja.Runtime, logs []LogEntry) *goja.Object {
|
||||||
array := runtime.NewArray()
|
array := runtime.NewArray()
|
||||||
for index, entry := range logs {
|
for index, entry := range logs {
|
||||||
item := runtime.NewObject()
|
_ = array.Set(fmt.Sprintf("%d", index), objectFromFields(runtime, logFields(entry)))
|
||||||
_ = item.Set("unixMillis", entry.UnixMillis)
|
|
||||||
_ = item.Set("level", entry.Level)
|
|
||||||
_ = item.Set("tag", entry.Tag)
|
|
||||||
_ = item.Set("message", entry.Message)
|
|
||||||
_ = array.Set(fmt.Sprintf("%d", index), item)
|
|
||||||
}
|
}
|
||||||
return array
|
return array
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// EncodeLogs renders this step's log entries for the web host, which has no
|
||||||
|
// Go-side state object to read: the runner pushes this JSON into the page
|
||||||
|
// before each extractor evaluation. No entries encodes as an empty array, the
|
||||||
|
// same value the goja host reports for a step whose log fetch found nothing.
|
||||||
|
func EncodeLogs(logs []LogEntry) json.RawMessage {
|
||||||
|
var buffer bytes.Buffer
|
||||||
|
buffer.WriteByte('[')
|
||||||
|
for index, entry := range logs {
|
||||||
|
if index > 0 {
|
||||||
|
buffer.WriteByte(',')
|
||||||
|
}
|
||||||
|
buffer.Write(encodeFields(logFields(entry)))
|
||||||
|
}
|
||||||
|
buffer.WriteByte(']')
|
||||||
|
return buffer.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
func exceptionsArray(runtime *goja.Runtime, exceptions []Exception) *goja.Object {
|
func exceptionsArray(runtime *goja.Runtime, exceptions []Exception) *goja.Object {
|
||||||
array := runtime.NewArray()
|
array := runtime.NewArray()
|
||||||
for index, exception := range exceptions {
|
for index, exception := range exceptions {
|
||||||
|
|||||||
@@ -282,3 +282,50 @@ func TestLastAction_ReportsARelaunchSeparatelyFromTheDispatch(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestLogs_WebJSONMatchesTheGojaObject pins state.logs to ONE shape across the
|
||||||
|
// two hosts, for the same reason lastAction is pinned. On web the page's
|
||||||
|
// reading of every extractor replaces the host's, so state.logs is whatever
|
||||||
|
// EncodeLogs put in the page: a field this side renames or cases differently
|
||||||
|
// leaves the default noLogcatErrors counting nothing on web while it counts on
|
||||||
|
// native, with nothing reporting that it never saw an entry.
|
||||||
|
func TestLogs_WebJSONMatchesTheGojaObject(t *testing.T) {
|
||||||
|
verifier := newVerifier(t)
|
||||||
|
mustLoad(t, verifier, `
|
||||||
|
globalThis.lines = __sanderling__.extract(state => JSON.stringify(state.logs));
|
||||||
|
`)
|
||||||
|
|
||||||
|
for _, testCase := range []struct {
|
||||||
|
name string
|
||||||
|
logs []LogEntry
|
||||||
|
}{
|
||||||
|
{"none", nil},
|
||||||
|
{"empty", []LogEntry{}},
|
||||||
|
{
|
||||||
|
"one error",
|
||||||
|
[]LogEntry{{UnixMillis: 1700000000123, Level: "E", Tag: "console", Message: "boom from the page"}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mixed levels",
|
||||||
|
[]LogEntry{
|
||||||
|
{UnixMillis: 1, Level: "E", Tag: "console", Message: `say "hi" <b> & co`},
|
||||||
|
{UnixMillis: 2, Level: "W", Tag: "AndroidRuntime", Message: "a warning"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(testCase.name, func(t *testing.T) {
|
||||||
|
if err := verifier.PushSnapshot(SnapshotInput{
|
||||||
|
Snapshots: Snapshots{},
|
||||||
|
Logs: testCase.logs,
|
||||||
|
}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
handle := verifier.runtime.GlobalObject().Get("lines").ToObject(verifier.runtime)
|
||||||
|
goja := handle.Get("current").String()
|
||||||
|
web := string(EncodeLogs(testCase.logs))
|
||||||
|
if goja != web {
|
||||||
|
t.Errorf("the two hosts disagree on state.logs\n goja: %s\n web: %s", goja, web)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user