1// Copyright 2020 The Bazel Authors. All rights reserved. 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package bzltestutil 16 17import ( 18 "bufio" 19 "bytes" 20 "fmt" 21 "io" 22 "io/ioutil" 23 "log" 24 "os" 25 "os/exec" 26 "path/filepath" 27 "strconv" 28 "strings" 29 "sync" 30) 31 32// TestWrapperAbnormalExit is used by Wrap to indicate the child 33// process exitted without an exit code (for example being killed by a signal). 34// We use 6, in line with Bazel's RUN_FAILURE. 35const TestWrapperAbnormalExit = 6 36 37func ShouldWrap() bool { 38 if wrapEnv, ok := os.LookupEnv("GO_TEST_WRAP"); ok { 39 wrap, err := strconv.ParseBool(wrapEnv) 40 if err != nil { 41 log.Fatalf("invalid value for GO_TEST_WRAP: %q", wrapEnv) 42 } 43 return wrap 44 } 45 _, ok := os.LookupEnv("XML_OUTPUT_FILE") 46 return ok 47} 48 49// shouldAddTestV indicates if the test wrapper should prepend a -test.v flag to 50// the test args. This is required to get information about passing tests from 51// test2json for complete XML reports. 52func shouldAddTestV() bool { 53 if wrapEnv, ok := os.LookupEnv("GO_TEST_WRAP_TESTV"); ok { 54 wrap, err := strconv.ParseBool(wrapEnv) 55 if err != nil { 56 log.Fatalf("invalid value for GO_TEST_WRAP_TESTV: %q", wrapEnv) 57 } 58 return wrap 59 } 60 return false 61} 62 63// streamMerger intelligently merges an input stdout and stderr stream and dumps 64// the output to the writer `inner`. Additional synchronization is applied to 65// ensure that one line at a time is written to the inner writer. 66type streamMerger struct { 67 OutW, ErrW *io.PipeWriter 68 mutex sync.Mutex 69 inner io.Writer 70 wg sync.WaitGroup 71 outR, errR *bufio.Reader 72} 73 74func NewStreamMerger(w io.Writer) *streamMerger { 75 outR, outW := io.Pipe() 76 errR, errW := io.Pipe() 77 return &streamMerger{ 78 inner: w, 79 OutW: outW, 80 ErrW: errW, 81 outR: bufio.NewReader(outR), 82 errR: bufio.NewReader(errR), 83 } 84} 85 86func (m *streamMerger) Start() { 87 m.wg.Add(2) 88 process := func(r *bufio.Reader) { 89 for { 90 s, err := r.ReadString('\n') 91 if len(s) > 0 { 92 m.mutex.Lock() 93 io.WriteString(m.inner, s) 94 m.mutex.Unlock() 95 } 96 if err == io.EOF { 97 break 98 } 99 } 100 m.wg.Done() 101 } 102 go process(m.outR) 103 go process(m.errR) 104} 105 106func (m *streamMerger) Wait() { 107 m.wg.Wait() 108} 109 110func Wrap(pkg string) error { 111 var jsonBuffer bytes.Buffer 112 jsonConverter := NewConverter(&jsonBuffer, pkg, Timestamp) 113 streamMerger := NewStreamMerger(jsonConverter) 114 115 args := os.Args[1:] 116 if shouldAddTestV() { 117 args = append([]string{"-test.v"}, args...) 118 } 119 exePath := os.Args[0] 120 if !filepath.IsAbs(exePath) && strings.ContainsRune(exePath, filepath.Separator) && testExecDir != "" { 121 exePath = filepath.Join(testExecDir, exePath) 122 } 123 cmd := exec.Command(exePath, args...) 124 cmd.Env = append(os.Environ(), "GO_TEST_WRAP=0") 125 cmd.Stderr = io.MultiWriter(os.Stderr, streamMerger.ErrW) 126 cmd.Stdout = io.MultiWriter(os.Stdout, streamMerger.OutW) 127 streamMerger.Start() 128 err := cmd.Run() 129 streamMerger.ErrW.Close() 130 streamMerger.OutW.Close() 131 streamMerger.Wait() 132 jsonConverter.Close() 133 if out, ok := os.LookupEnv("XML_OUTPUT_FILE"); ok { 134 werr := writeReport(jsonBuffer, pkg, out) 135 if werr != nil { 136 if err != nil { 137 return fmt.Errorf("error while generating testreport: %s, (error wrapping test execution: %s)", werr, err) 138 } 139 return fmt.Errorf("error while generating testreport: %s", werr) 140 } 141 } 142 return err 143} 144 145func writeReport(jsonBuffer bytes.Buffer, pkg string, path string) error { 146 xml, cerr := json2xml(&jsonBuffer, pkg) 147 if cerr != nil { 148 return fmt.Errorf("error converting test output to xml: %s", cerr) 149 } 150 if err := ioutil.WriteFile(path, xml, 0664); err != nil { 151 return fmt.Errorf("error writing test xml: %s", err) 152 } 153 return nil 154} 155