Source file src/cmd/dist/testjson.go

     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  
     5  package main
     6  
     7  import (
     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.
    18  type lockedWriter struct {
    19  	lock sync.Mutex
    20  	w    io.Writer
    21  }
    22  
    23  func (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.
    31  type 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  
    38  func (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  
    66  func (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  
    74  func (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  		lineBuf := bytes.NewBuffer(line)
    84  		dec := json.NewDecoder(lineBuf)
    85  		dec.UseNumber()
    86  		val, err := decodeJSONValue(dec)
    87  		if err == nil && val.atom == json.Delim('{') {
    88  			// Rewrite the Package field.
    89  			found := false
    90  			for i := 0; i < len(val.seq); i += 2 {
    91  				if val.seq[i].atom == "Package" {
    92  					if pkg, ok := val.seq[i+1].atom.(string); ok {
    93  						val.seq[i+1].atom = pkg + ":" + f.variant
    94  						found = true
    95  						break
    96  					}
    97  				}
    98  			}
    99  			if found {
   100  				data, err := json.Marshal(val)
   101  				if err != nil {
   102  					// Should never happen.
   103  					panic(fmt.Sprintf("failed to round-trip JSON %q: %s", line, err))
   104  				}
   105  				f.w.Write(data)
   106  				// Copy any trailing text. We expect at most a "\n" here, but
   107  				// there could be other text and we want to feed that through.
   108  				io.Copy(f.w, dec.Buffered())
   109  				io.Copy(f.w, lineBuf)
   110  				return
   111  			}
   112  		}
   113  	}
   114  
   115  	// Something went wrong. Just pass the line through.
   116  	f.w.Write(line)
   117  }
   118  
   119  type jsonValue struct {
   120  	atom json.Token  // If json.Delim, then seq will be filled
   121  	seq  []jsonValue // If atom == json.Delim('{'), alternating pairs
   122  }
   123  
   124  var jsonPop = errors.New("end of JSON sequence")
   125  
   126  func decodeJSONValue(dec *json.Decoder) (jsonValue, error) {
   127  	t, err := dec.Token()
   128  	if err != nil {
   129  		if err == io.EOF {
   130  			err = io.ErrUnexpectedEOF
   131  		}
   132  		return jsonValue{}, err
   133  	}
   134  
   135  	switch t := t.(type) {
   136  	case json.Delim:
   137  		if t == '}' || t == ']' {
   138  			return jsonValue{}, jsonPop
   139  		}
   140  
   141  		var seq []jsonValue
   142  		for {
   143  			val, err := decodeJSONValue(dec)
   144  			if err == jsonPop {
   145  				break
   146  			} else if err != nil {
   147  				return jsonValue{}, err
   148  			}
   149  			seq = append(seq, val)
   150  		}
   151  		return jsonValue{t, seq}, nil
   152  	default:
   153  		return jsonValue{t, nil}, nil
   154  	}
   155  }
   156  
   157  func (v jsonValue) MarshalJSON() ([]byte, error) {
   158  	var buf bytes.Buffer
   159  	var marshal1 func(v jsonValue) error
   160  	marshal1 = func(v jsonValue) error {
   161  		if t, ok := v.atom.(json.Delim); ok {
   162  			buf.WriteRune(rune(t))
   163  			for i, v2 := range v.seq {
   164  				if t == '{' && i%2 == 1 {
   165  					buf.WriteByte(':')
   166  				} else if i > 0 {
   167  					buf.WriteByte(',')
   168  				}
   169  				if err := marshal1(v2); err != nil {
   170  					return err
   171  				}
   172  			}
   173  			if t == '{' {
   174  				buf.WriteByte('}')
   175  			} else {
   176  				buf.WriteByte(']')
   177  			}
   178  			return nil
   179  		}
   180  		bytes, err := json.Marshal(v.atom)
   181  		if err != nil {
   182  			return err
   183  		}
   184  		buf.Write(bytes)
   185  		return nil
   186  	}
   187  	err := marshal1(v)
   188  	return buf.Bytes(), err
   189  }
   190  
   191  func synthesizeSkipEvent(enc *json.Encoder, pkg, msg string) {
   192  	type event struct {
   193  		Time    time.Time
   194  		Action  string
   195  		Package string
   196  		Output  string `json:",omitempty"`
   197  	}
   198  	ev := event{Time: time.Now(), Package: pkg, Action: "start"}
   199  	enc.Encode(ev)
   200  	ev.Action = "output"
   201  	ev.Output = msg
   202  	enc.Encode(ev)
   203  	ev.Action = "skip"
   204  	ev.Output = ""
   205  	enc.Encode(ev)
   206  }
   207  

View as plain text