2023-05-11 14:20:36 -06:00
|
|
|
// Copyright 2023 The Go Authors. All rights reserved.
|
|
|
|
// Use of this source code is governed by a BSD-style
|
|
|
|
// license that can be found in the LICENSE file.
|
|
|
|
|
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"encoding/json"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"sync"
|
2023-07-21 11:49:06 -06:00
|
|
|
"time"
|
2023-05-11 14:20:36 -06:00
|
|
|
)
|
|
|
|
|
|
|
|
// lockedWriter serializes Write calls to an underlying Writer.
|
|
|
|
type lockedWriter struct {
|
|
|
|
lock sync.Mutex
|
|
|
|
w io.Writer
|
|
|
|
}
|
|
|
|
|
|
|
|
func (w *lockedWriter) Write(b []byte) (int, error) {
|
|
|
|
w.lock.Lock()
|
|
|
|
defer w.lock.Unlock()
|
|
|
|
return w.w.Write(b)
|
|
|
|
}
|
|
|
|
|
|
|
|
// testJSONFilter is an io.Writer filter that replaces the Package field in
|
|
|
|
// test2json output.
|
|
|
|
type testJSONFilter struct {
|
|
|
|
w io.Writer // Underlying writer
|
|
|
|
variant string // Add ":variant" to Package field
|
|
|
|
|
|
|
|
lineBuf bytes.Buffer // Buffer for incomplete lines
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *testJSONFilter) Write(b []byte) (int, error) {
|
|
|
|
bn := len(b)
|
|
|
|
|
|
|
|
// Process complete lines, and buffer any incomplete lines.
|
|
|
|
for len(b) > 0 {
|
|
|
|
nl := bytes.IndexByte(b, '\n')
|
|
|
|
if nl < 0 {
|
|
|
|
f.lineBuf.Write(b)
|
|
|
|
break
|
|
|
|
}
|
|
|
|
var line []byte
|
|
|
|
if f.lineBuf.Len() > 0 {
|
|
|
|
// We have buffered data. Add the rest of the line from b and
|
|
|
|
// process the complete line.
|
|
|
|
f.lineBuf.Write(b[:nl+1])
|
|
|
|
line = f.lineBuf.Bytes()
|
|
|
|
} else {
|
|
|
|
// Process a complete line from b.
|
|
|
|
line = b[:nl+1]
|
|
|
|
}
|
|
|
|
b = b[nl+1:]
|
|
|
|
f.process(line)
|
|
|
|
f.lineBuf.Reset()
|
|
|
|
}
|
|
|
|
|
|
|
|
return bn, nil
|
|
|
|
}
|
|
|
|
|
2023-05-19 07:32:22 -06:00
|
|
|
func (f *testJSONFilter) Flush() {
|
|
|
|
// Write any remaining partial line to the underlying writer.
|
|
|
|
if f.lineBuf.Len() > 0 {
|
|
|
|
f.w.Write(f.lineBuf.Bytes())
|
|
|
|
f.lineBuf.Reset()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-11 14:20:36 -06:00
|
|
|
func (f *testJSONFilter) process(line []byte) {
|
|
|
|
if len(line) > 0 && line[0] == '{' {
|
|
|
|
// Plausible test2json output. Parse it generically.
|
|
|
|
//
|
|
|
|
// We go to some effort here to preserve key order while doing this
|
|
|
|
// generically. This will stay robust to changes in the test2json
|
|
|
|
// struct, or other additions outside of it. If humans are ever looking
|
|
|
|
// at the output, it's really nice to keep field order because it
|
|
|
|
// preserves a lot of regularity in the output.
|
|
|
|
dec := json.NewDecoder(bytes.NewBuffer(line))
|
|
|
|
dec.UseNumber()
|
|
|
|
val, err := decodeJSONValue(dec)
|
|
|
|
if err == nil && val.atom == json.Delim('{') {
|
|
|
|
// Rewrite the Package field.
|
|
|
|
found := false
|
|
|
|
for i := 0; i < len(val.seq); i += 2 {
|
|
|
|
if val.seq[i].atom == "Package" {
|
|
|
|
if pkg, ok := val.seq[i+1].atom.(string); ok {
|
|
|
|
val.seq[i+1].atom = pkg + ":" + f.variant
|
|
|
|
found = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if found {
|
|
|
|
data, err := json.Marshal(val)
|
|
|
|
if err != nil {
|
|
|
|
// Should never happen.
|
2024-03-06 20:16:17 -07:00
|
|
|
panic(fmt.Sprintf("failed to round-trip JSON %q: %s", line, err))
|
2023-05-11 14:20:36 -06:00
|
|
|
}
|
2023-05-22 10:56:28 -06:00
|
|
|
f.w.Write(data)
|
2023-05-17 15:59:49 -06:00
|
|
|
// Copy any trailing text. We expect at most a "\n" here, but
|
|
|
|
// there could be other text and we want to feed that through.
|
2023-05-22 10:56:28 -06:00
|
|
|
io.Copy(f.w, dec.Buffered())
|
2023-05-11 14:20:36 -06:00
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Something went wrong. Just pass the line through.
|
|
|
|
f.w.Write(line)
|
|
|
|
}
|
|
|
|
|
|
|
|
type jsonValue struct {
|
|
|
|
atom json.Token // If json.Delim, then seq will be filled
|
|
|
|
seq []jsonValue // If atom == json.Delim('{'), alternating pairs
|
|
|
|
}
|
|
|
|
|
|
|
|
var jsonPop = errors.New("end of JSON sequence")
|
|
|
|
|
|
|
|
func decodeJSONValue(dec *json.Decoder) (jsonValue, error) {
|
|
|
|
t, err := dec.Token()
|
|
|
|
if err != nil {
|
|
|
|
if err == io.EOF {
|
|
|
|
err = io.ErrUnexpectedEOF
|
|
|
|
}
|
|
|
|
return jsonValue{}, err
|
|
|
|
}
|
|
|
|
|
|
|
|
switch t := t.(type) {
|
|
|
|
case json.Delim:
|
|
|
|
if t == '}' || t == ']' {
|
|
|
|
return jsonValue{}, jsonPop
|
|
|
|
}
|
|
|
|
|
|
|
|
var seq []jsonValue
|
|
|
|
for {
|
|
|
|
val, err := decodeJSONValue(dec)
|
|
|
|
if err == jsonPop {
|
|
|
|
break
|
|
|
|
} else if err != nil {
|
|
|
|
return jsonValue{}, err
|
|
|
|
}
|
|
|
|
seq = append(seq, val)
|
|
|
|
}
|
|
|
|
return jsonValue{t, seq}, nil
|
|
|
|
default:
|
|
|
|
return jsonValue{t, nil}, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (v jsonValue) MarshalJSON() ([]byte, error) {
|
|
|
|
var buf bytes.Buffer
|
|
|
|
var marshal1 func(v jsonValue) error
|
|
|
|
marshal1 = func(v jsonValue) error {
|
|
|
|
if t, ok := v.atom.(json.Delim); ok {
|
|
|
|
buf.WriteRune(rune(t))
|
|
|
|
for i, v2 := range v.seq {
|
|
|
|
if t == '{' && i%2 == 1 {
|
|
|
|
buf.WriteByte(':')
|
|
|
|
} else if i > 0 {
|
|
|
|
buf.WriteByte(',')
|
|
|
|
}
|
|
|
|
if err := marshal1(v2); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if t == '{' {
|
|
|
|
buf.WriteByte('}')
|
|
|
|
} else {
|
|
|
|
buf.WriteByte(']')
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
bytes, err := json.Marshal(v.atom)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
buf.Write(bytes)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
err := marshal1(v)
|
|
|
|
return buf.Bytes(), err
|
|
|
|
}
|
2023-07-21 11:49:06 -06:00
|
|
|
|
|
|
|
func synthesizeSkipEvent(enc *json.Encoder, pkg, msg string) {
|
|
|
|
type event struct {
|
|
|
|
Time time.Time
|
|
|
|
Action string
|
|
|
|
Package string
|
|
|
|
Output string `json:",omitempty"`
|
|
|
|
}
|
|
|
|
ev := event{Time: time.Now(), Package: pkg, Action: "start"}
|
|
|
|
enc.Encode(ev)
|
|
|
|
ev.Action = "output"
|
|
|
|
ev.Output = msg
|
|
|
|
enc.Encode(ev)
|
|
|
|
ev.Action = "skip"
|
|
|
|
ev.Output = ""
|
|
|
|
enc.Encode(ev)
|
|
|
|
}
|