1// Copyright 2023 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package main
6
7import (
8	"bytes"
9	"encoding/json"
10	"errors"
11	"fmt"
12	"io"
13	"sync"
14	"time"
15)
16
17// lockedWriter serializes Write calls to an underlying Writer.
18type lockedWriter struct {
19	lock sync.Mutex
20	w    io.Writer
21}
22
23func (w *lockedWriter) Write(b []byte) (int, error) {
24	w.lock.Lock()
25	defer w.lock.Unlock()
26	return w.w.Write(b)
27}
28
29// testJSONFilter is an io.Writer filter that replaces the Package field in
30// test2json output.
31type testJSONFilter struct {
32	w       io.Writer // Underlying writer
33	variant string    // Add ":variant" to Package field
34
35	lineBuf bytes.Buffer // Buffer for incomplete lines
36}
37
38func (f *testJSONFilter) Write(b []byte) (int, error) {
39	bn := len(b)
40
41	// Process complete lines, and buffer any incomplete lines.
42	for len(b) > 0 {
43		nl := bytes.IndexByte(b, '\n')
44		if nl < 0 {
45			f.lineBuf.Write(b)
46			break
47		}
48		var line []byte
49		if f.lineBuf.Len() > 0 {
50			// We have buffered data. Add the rest of the line from b and
51			// process the complete line.
52			f.lineBuf.Write(b[:nl+1])
53			line = f.lineBuf.Bytes()
54		} else {
55			// Process a complete line from b.
56			line = b[:nl+1]
57		}
58		b = b[nl+1:]
59		f.process(line)
60		f.lineBuf.Reset()
61	}
62
63	return bn, nil
64}
65
66func (f *testJSONFilter) Flush() {
67	// Write any remaining partial line to the underlying writer.
68	if f.lineBuf.Len() > 0 {
69		f.w.Write(f.lineBuf.Bytes())
70		f.lineBuf.Reset()
71	}
72}
73
74func (f *testJSONFilter) process(line []byte) {
75	if len(line) > 0 && line[0] == '{' {
76		// Plausible test2json output. Parse it generically.
77		//
78		// We go to some effort here to preserve key order while doing this
79		// generically. This will stay robust to changes in the test2json
80		// struct, or other additions outside of it. If humans are ever looking
81		// at the output, it's really nice to keep field order because it
82		// preserves a lot of regularity in the output.
83		dec := json.NewDecoder(bytes.NewBuffer(line))
84		dec.UseNumber()
85		val, err := decodeJSONValue(dec)
86		if err == nil && val.atom == json.Delim('{') {
87			// Rewrite the Package field.
88			found := false
89			for i := 0; i < len(val.seq); i += 2 {
90				if val.seq[i].atom == "Package" {
91					if pkg, ok := val.seq[i+1].atom.(string); ok {
92						val.seq[i+1].atom = pkg + ":" + f.variant
93						found = true
94						break
95					}
96				}
97			}
98			if found {
99				data, err := json.Marshal(val)
100				if err != nil {
101					// Should never happen.
102					panic(fmt.Sprintf("failed to round-trip JSON %q: %s", line, err))
103				}
104				f.w.Write(data)
105				// Copy any trailing text. We expect at most a "\n" here, but
106				// there could be other text and we want to feed that through.
107				io.Copy(f.w, dec.Buffered())
108				return
109			}
110		}
111	}
112
113	// Something went wrong. Just pass the line through.
114	f.w.Write(line)
115}
116
117type jsonValue struct {
118	atom json.Token  // If json.Delim, then seq will be filled
119	seq  []jsonValue // If atom == json.Delim('{'), alternating pairs
120}
121
122var jsonPop = errors.New("end of JSON sequence")
123
124func decodeJSONValue(dec *json.Decoder) (jsonValue, error) {
125	t, err := dec.Token()
126	if err != nil {
127		if err == io.EOF {
128			err = io.ErrUnexpectedEOF
129		}
130		return jsonValue{}, err
131	}
132
133	switch t := t.(type) {
134	case json.Delim:
135		if t == '}' || t == ']' {
136			return jsonValue{}, jsonPop
137		}
138
139		var seq []jsonValue
140		for {
141			val, err := decodeJSONValue(dec)
142			if err == jsonPop {
143				break
144			} else if err != nil {
145				return jsonValue{}, err
146			}
147			seq = append(seq, val)
148		}
149		return jsonValue{t, seq}, nil
150	default:
151		return jsonValue{t, nil}, nil
152	}
153}
154
155func (v jsonValue) MarshalJSON() ([]byte, error) {
156	var buf bytes.Buffer
157	var marshal1 func(v jsonValue) error
158	marshal1 = func(v jsonValue) error {
159		if t, ok := v.atom.(json.Delim); ok {
160			buf.WriteRune(rune(t))
161			for i, v2 := range v.seq {
162				if t == '{' && i%2 == 1 {
163					buf.WriteByte(':')
164				} else if i > 0 {
165					buf.WriteByte(',')
166				}
167				if err := marshal1(v2); err != nil {
168					return err
169				}
170			}
171			if t == '{' {
172				buf.WriteByte('}')
173			} else {
174				buf.WriteByte(']')
175			}
176			return nil
177		}
178		bytes, err := json.Marshal(v.atom)
179		if err != nil {
180			return err
181		}
182		buf.Write(bytes)
183		return nil
184	}
185	err := marshal1(v)
186	return buf.Bytes(), err
187}
188
189func synthesizeSkipEvent(enc *json.Encoder, pkg, msg string) {
190	type event struct {
191		Time    time.Time
192		Action  string
193		Package string
194		Output  string `json:",omitempty"`
195	}
196	ev := event{Time: time.Now(), Package: pkg, Action: "start"}
197	enc.Encode(ev)
198	ev.Action = "output"
199	ev.Output = msg
200	enc.Encode(ev)
201	ev.Action = "skip"
202	ev.Output = ""
203	enc.Encode(ev)
204}
205