github.com/mem/u-root@v2.0.1-0.20181004165302-9b18b4636a33+incompatible/pkg/testutil/testutil.go (about)

     1  // Copyright 2017 the u-root 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 testutil
     6  
     7  import (
     8  	"fmt"
     9  	"io/ioutil"
    10  	"log"
    11  	"os"
    12  	"os/exec"
    13  	"path/filepath"
    14  	"strings"
    15  	"syscall"
    16  	"testing"
    17  
    18  	"github.com/u-root/u-root/pkg/golang"
    19  )
    20  
    21  var binary string
    22  
    23  // Command returns an exec.Cmd appropriate for testing the u-root command.
    24  //
    25  // Command decides which executable to call based on environment variables:
    26  // - EXECPATH="executable args" overrides any other test subject.
    27  // - UROOT_TEST_BUILD=1 will force compiling the u-root command in question.
    28  func Command(t testing.TB, args ...string) *exec.Cmd {
    29  	// If EXECPATH is set, just use that.
    30  	execPath := os.Getenv("EXECPATH")
    31  	if len(execPath) > 0 {
    32  		exe := strings.Split(os.Getenv("EXECPATH"), " ")
    33  		return exec.Command(exe[0], append(exe[1:], args...)...)
    34  	}
    35  
    36  	// Should be cached by Run if os.Executable is going to fail.
    37  	if len(binary) > 0 {
    38  		t.Logf("binary: %v", binary)
    39  		return exec.Command(binary, args...)
    40  	}
    41  
    42  	execPath, err := os.Executable()
    43  	if err != nil {
    44  		// Run should have prevented this case by caching something in
    45  		// `binary`.
    46  		t.Fatal("You must call testutil.Run() in your TestMain.")
    47  	}
    48  
    49  	c := exec.Command(execPath, args...)
    50  	c.Env = append(c.Env, append(os.Environ(), "UROOT_CALL_MAIN=1")...)
    51  	return c
    52  }
    53  
    54  // IsExitCode takes err and checks whether it represents the given process exit
    55  // code.
    56  //
    57  // IsExitCode assumes that `err` is the return value of a successful call to
    58  // exec.Cmd.Run/Output/CombinedOutput and hence an *exec.ExitError.
    59  func IsExitCode(err error, exitCode int) error {
    60  	if err == nil {
    61  		if exitCode != 0 {
    62  			return fmt.Errorf("got code 0, want %d", exitCode)
    63  		}
    64  		return nil
    65  	}
    66  
    67  	exitErr, ok := err.(*exec.ExitError)
    68  	if !ok {
    69  		return fmt.Errorf("encountered error other than ExitError: %#v", err)
    70  	}
    71  	ws, ok := exitErr.Sys().(syscall.WaitStatus)
    72  	if !ok {
    73  		return fmt.Errorf("sys() is not a syscall WaitStatus: %v", err)
    74  	}
    75  	if es := ws.ExitStatus(); es != exitCode {
    76  		return fmt.Errorf("got exit status %d, want %d", es, exitCode)
    77  	}
    78  	return nil
    79  }
    80  
    81  func run(m *testing.M, mainFn func()) int {
    82  	// UROOT_CALL_MAIN=1 /proc/self/exe should be the same as just running
    83  	// the command we are testing.
    84  	if len(os.Getenv("UROOT_CALL_MAIN")) > 0 {
    85  		mainFn()
    86  		return 0
    87  	}
    88  
    89  	// Normally, /proc/self/exe (and equivalents) are used to test u-root
    90  	// commands.
    91  	//
    92  	// Such a symlink isn't available on Plan 9, OS X, or FreeBSD. On these
    93  	// systems, we compile the u-root command in question on the fly
    94  	// instead.
    95  	//
    96  	// Here, we decide whether to compile or not and cache the executable.
    97  	// Do this here, so that when m.Run() returns, we can remove the
    98  	// executable using the functor returned.
    99  	_, err := os.Executable()
   100  	if err != nil || len(os.Getenv("UROOT_TEST_BUILD")) > 0 {
   101  		// We can't find ourselves? Probably FreeBSD or something. Try to go
   102  		// build the command.
   103  		//
   104  		// This is NOT build-system-independent, and hence the fallback.
   105  		tmpDir, err := ioutil.TempDir("", "uroot-build")
   106  		if err != nil {
   107  			log.Fatal(err)
   108  		}
   109  		defer os.RemoveAll(tmpDir)
   110  
   111  		wd, err := os.Getwd()
   112  		if err != nil {
   113  			log.Fatal(err)
   114  		}
   115  
   116  		execPath := filepath.Join(tmpDir, "binary")
   117  		// Build the stuff.
   118  		if err := golang.Default().BuildDir(wd, execPath, golang.BuildOpts{}); err != nil {
   119  			log.Fatal(err)
   120  		}
   121  
   122  		// Cache dat.
   123  		binary = execPath
   124  	}
   125  
   126  	return m.Run()
   127  }
   128  
   129  // Run sets up necessary commands to be compiled, if necessary, and calls
   130  // m.Run.
   131  func Run(m *testing.M, mainFn func()) {
   132  	os.Exit(run(m, mainFn))
   133  }