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