github.com/thediveo/gons@v0.9.9/reexec/testing/pritipratel.go (about)

     1  // Copyright 2020 Harald Albrecht.
     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 testing
    16  
    17  import (
    18  	"bufio"
    19  	"io"
    20  	"os"
    21  	"strings"
    22  )
    23  
    24  // pritiPratel runs function f and passes only harmless error output destined
    25  // to os.Stderr really on to os.Stderr. All dangerous talk about unwanted
    26  // error truths will be sent into early retirement. Such as testing-related
    27  // messages, which we silently drop. This is necessary as some applications
    28  // using gons/reexec expect the re-executed child to return their results via
    29  // stdout without any stderr output, so we don't want Golang's testing output
    30  // to interfere here.
    31  func pritiPratel(f func()) {
    32  	realStderr := os.Stderr
    33  	// Unfortunately, we cannot make use of the in-memory io.Pipe()s here, as
    34  	// os.Stderr is a *os.File, so it needs to have a file descriptor. In
    35  	// consequence, that leaves us with the sole option of a "real" pipe.
    36  	reader, writer, err := os.Pipe()
    37  	if err != nil {
    38  		panic("gons/reexec/testing: cannot create filtering pipe: " + err.Error())
    39  	}
    40  	os.Stderr = writer
    41  	defer func() {
    42  		os.Stderr = realStderr
    43  		reader.Close()
    44  		writer.Close()
    45  	}()
    46  	// The stdout filter for filtering out unwanted Golang testing messages.
    47  	// It runs as a separate Go routine, which only terminates on (real) read
    48  	// errors.
    49  	done := make(chan struct{})
    50  	go prattlepiper(reader, realStderr, done)
    51  	// Now run the desired function f() while scanning its output for unwanted
    52  	// rogue lies which we need to suppress. Definitely a Johnson function
    53  	// here.
    54  	f()
    55  	writer.Close()
    56  	<-done
    57  }
    58  
    59  var hide = []string{
    60  	"coverage:",
    61  	"testing:",
    62  }
    63  
    64  // prattlepiper reads from the specified reader, passing the read prattle to
    65  // the specified writer, but excluding certain topics, as specified in the
    66  // hide list.
    67  func prattlepiper(reader io.Reader, writer io.Writer, done chan struct{}) {
    68  	defer close(done)
    69  	r := bufio.NewReaderSize(reader, 1024)
    70  new_line:
    71  	for {
    72  		// Assume that we're starting with a new line here, so sort out
    73  		// any output beginning with "coverage:" or "testing:".
    74  		line, isprefix, err := r.ReadLine()
    75  		if err != nil {
    76  			// bufio.Reader.ReadLine() either returns a non-nil line or it
    77  			// returns an error, never both.
    78  			return
    79  		}
    80  		for _, h := range hide {
    81  			if strings.HasPrefix(string(line), h) {
    82  				// This line of output should be dropped. In case this
    83  				// line is longer than the buffer, drop until the end of
    84  				// line chunk by chunk.
    85  				for isprefix {
    86  					_, isprefix, err = r.ReadLine()
    87  					if err != nil {
    88  						return
    89  					}
    90  				}
    91  				continue new_line
    92  			}
    93  		}
    94  		// It's output we should better pass on...
    95  		if _, err := writer.Write(line); err != nil {
    96  			return
    97  		}
    98  		for isprefix {
    99  			line, isprefix, err = r.ReadLine()
   100  			if err != nil {
   101  				return
   102  			}
   103  			if _, err := writer.Write(line); err != nil {
   104  				return
   105  			}
   106  		}
   107  		// Handle the case where we have read the final line before EOF,
   108  		// which doesn't end in \n: in this situation, we must not append
   109  		// any \n to the output.
   110  		if err := r.UnreadByte(); err != nil {
   111  			return
   112  		}
   113  		if b, err := r.ReadByte(); err == nil && b == '\n' {
   114  			if _, err := writer.Write([]byte{'\n'}); err != nil {
   115  				return
   116  			}
   117  		}
   118  	}
   119  	// unreachable
   120  }