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  		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  
   117  type 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  
   122  var jsonPop = errors.New("end of JSON sequence")
   123  
   124  func 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  
   155  func (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  
   189  func 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  

View as plain text