xref: /aosp_15_r20/external/bazelbuild-rules_go/go/tools/bzltestutil/wrap.go (revision 9bb1b549b6a84214c53be0924760be030e66b93a)
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