...

Source file src/github.com/bazelbuild/rules_go/go/tools/bzltestutil/wrap.go

Documentation: github.com/bazelbuild/rules_go/go/tools/bzltestutil

     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  
    15  package bzltestutil
    16  
    17  import (
    18  	"bufio"
    19  	"bytes"
    20  	"fmt"
    21  	"io"
    22  	"io/ioutil"
    23  	"log"
    24  	"os"
    25  	"os/exec"
    26  	"os/signal"
    27  	"path/filepath"
    28  	"strconv"
    29  	"strings"
    30  	"sync"
    31  	"syscall"
    32  
    33  	"github.com/bazelbuild/rules_go/go/tools/bzltestutil/chdir"
    34  )
    35  
    36  // TestWrapperAbnormalExit is used by Wrap to indicate the child
    37  // process exitted without an exit code (for example being killed by a signal).
    38  // We use 6, in line with Bazel's RUN_FAILURE.
    39  const TestWrapperAbnormalExit = 6
    40  
    41  func ShouldWrap() bool {
    42  	if wrapEnv, ok := os.LookupEnv("GO_TEST_WRAP"); ok {
    43  		wrap, err := strconv.ParseBool(wrapEnv)
    44  		if err != nil {
    45  			log.Fatalf("invalid value for GO_TEST_WRAP: %q", wrapEnv)
    46  		}
    47  		return wrap
    48  	}
    49  	_, ok := os.LookupEnv("XML_OUTPUT_FILE")
    50  	return ok
    51  }
    52  
    53  // shouldAddTestV indicates if the test wrapper should prepend a -test.v flag to
    54  // the test args. This is required to get information about passing tests from
    55  // test2json for complete XML reports.
    56  func shouldAddTestV() bool {
    57  	if wrapEnv, ok := os.LookupEnv("GO_TEST_WRAP_TESTV"); ok {
    58  		wrap, err := strconv.ParseBool(wrapEnv)
    59  		if err != nil {
    60  			log.Fatalf("invalid value for GO_TEST_WRAP_TESTV: %q", wrapEnv)
    61  		}
    62  		return wrap
    63  	}
    64  	return false
    65  }
    66  
    67  // streamMerger intelligently merges an input stdout and stderr stream and dumps
    68  // the output to the writer `inner`. Additional synchronization is applied to
    69  // ensure that one line at a time is written to the inner writer.
    70  type streamMerger struct {
    71  	OutW, ErrW *io.PipeWriter
    72  	mutex      sync.Mutex
    73  	inner      io.Writer
    74  	wg         sync.WaitGroup
    75  	outR, errR *bufio.Reader
    76  }
    77  
    78  func NewStreamMerger(w io.Writer) *streamMerger {
    79  	outR, outW := io.Pipe()
    80  	errR, errW := io.Pipe()
    81  	return &streamMerger{
    82  		inner: w,
    83  		OutW:  outW,
    84  		ErrW:  errW,
    85  		outR:  bufio.NewReader(outR),
    86  		errR:  bufio.NewReader(errR),
    87  	}
    88  }
    89  
    90  func (m *streamMerger) Start() {
    91  	m.wg.Add(2)
    92  	process := func(r *bufio.Reader) {
    93  		for {
    94  			s, err := r.ReadString('\n')
    95  			if len(s) > 0 {
    96  				m.mutex.Lock()
    97  				io.WriteString(m.inner, s)
    98  				m.mutex.Unlock()
    99  			}
   100  			if err == io.EOF {
   101  				break
   102  			}
   103  		}
   104  		m.wg.Done()
   105  	}
   106  	go process(m.outR)
   107  	go process(m.errR)
   108  }
   109  
   110  func (m *streamMerger) Wait() {
   111  	m.wg.Wait()
   112  }
   113  
   114  func Wrap(pkg string) error {
   115  	var jsonBuffer bytes.Buffer
   116  	jsonConverter := NewConverter(&jsonBuffer, pkg, Timestamp)
   117  	streamMerger := NewStreamMerger(jsonConverter)
   118  
   119  	args := os.Args[1:]
   120  	if shouldAddTestV() {
   121  		args = append([]string{"-test.v"}, args...)
   122  	}
   123  	exePath := os.Args[0]
   124  	if !filepath.IsAbs(exePath) && strings.ContainsRune(exePath, filepath.Separator) && chdir.TestExecDir != "" {
   125  		exePath = filepath.Join(chdir.TestExecDir, exePath)
   126  	}
   127  
   128  	// If Bazel sends a SIGTERM because the test timed out, it sends it to all child processes. As
   129  	// a result, the child process will print stack traces of all Go routines and we want the
   130  	// wrapper to be around to capute and forward this output. Thus, we need to ignore the signal
   131  	// and will be killed by Bazel after the grace period instead.
   132  	signal.Ignore(syscall.SIGTERM)
   133  
   134  	cmd := exec.Command(exePath, args...)
   135  	cmd.Env = append(os.Environ(), "GO_TEST_WRAP=0")
   136  	cmd.Stderr = io.MultiWriter(os.Stderr, streamMerger.ErrW)
   137  	cmd.Stdout = io.MultiWriter(os.Stdout, streamMerger.OutW)
   138  	streamMerger.Start()
   139  	err := cmd.Run()
   140  	streamMerger.ErrW.Close()
   141  	streamMerger.OutW.Close()
   142  	streamMerger.Wait()
   143  	jsonConverter.Close()
   144  	if out, ok := os.LookupEnv("XML_OUTPUT_FILE"); ok {
   145  		werr := writeReport(jsonBuffer, pkg, out)
   146  		if werr != nil {
   147  			if err != nil {
   148  				return fmt.Errorf("error while generating testreport: %s, (error wrapping test execution: %s)", werr, err)
   149  			}
   150  			return fmt.Errorf("error while generating testreport: %s", werr)
   151  		}
   152  	}
   153  	return err
   154  }
   155  
   156  func writeReport(jsonBuffer bytes.Buffer, pkg string, path string) error {
   157  	xml, cerr := json2xml(&jsonBuffer, pkg)
   158  	if cerr != nil {
   159  		return fmt.Errorf("error converting test output to xml: %s", cerr)
   160  	}
   161  	if err := ioutil.WriteFile(path, xml, 0664); err != nil {
   162  		return fmt.Errorf("error writing test xml: %s", err)
   163  	}
   164  	return nil
   165  }
   166  

View as plain text