github.com/mvdan/u-root-coreutils@v0.0.0-20230122170626-c2eef2898555/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  	"log"
    10  	"os"
    11  	"os/exec"
    12  	"path/filepath"
    13  	"strings"
    14  	"testing"
    15  	"time"
    16  
    17  	"github.com/u-root/gobusybox/src/pkg/golang"
    18  	"github.com/mvdan/u-root-coreutils/pkg/cmdline"
    19  )
    20  
    21  // CheckError is a helper function for tests
    22  // It is common to check if an err is expected in the form of errStr, then
    23  // there should be an actual error reported. This is an if and only if condition
    24  // that needs to be verified.
    25  func CheckError(err error, errStr string) error {
    26  	if err != nil && errStr == "" {
    27  		return fmt.Errorf("no error expected, got: \n%v", err)
    28  	} else if err == nil && errStr != "" {
    29  		return fmt.Errorf("error \n%v\nexpected, got nil error", errStr)
    30  	} else if err != nil && err.Error() != errStr {
    31  		return fmt.Errorf("error \n%v\nexpected, got: \n%v", errStr, err)
    32  	}
    33  	return nil
    34  }
    35  
    36  // NowLog returns the current time formatted like the standard log package's
    37  // timestamp.
    38  func NowLog() string {
    39  	return time.Now().Format("2006/01/02 15:04:05")
    40  }
    41  
    42  var binary string
    43  
    44  // Command returns an exec.Cmd appropriate for testing the u-root command.
    45  //
    46  // Command decides which executable to call based on environment variables:
    47  // - EXECPATH="executable args" overrides any other test subject.
    48  // - UROOT_TEST_BUILD=1 will force compiling the u-root command in question.
    49  func Command(t testing.TB, args ...string) *exec.Cmd {
    50  	// If EXECPATH is set, just use that.
    51  	execPath := os.Getenv("EXECPATH")
    52  	if len(execPath) > 0 {
    53  		exe := strings.Split(os.Getenv("EXECPATH"), " ")
    54  		return exec.Command(exe[0], append(exe[1:], args...)...)
    55  	}
    56  
    57  	// Should be cached by Run if os.Executable is going to fail.
    58  	if len(binary) > 0 {
    59  		t.Logf("binary: %v", binary)
    60  		return exec.Command(binary, args...)
    61  	}
    62  
    63  	execPath, err := os.Executable()
    64  	if err != nil {
    65  		// Run should have prevented this case by caching something in
    66  		// `binary`.
    67  		t.Fatal("You must call testutil.Run() in your TestMain.")
    68  	}
    69  
    70  	c := exec.Command(execPath, args...)
    71  	c.Env = append(c.Env, append(os.Environ(), "UROOT_CALL_MAIN=1")...)
    72  	return c
    73  }
    74  
    75  // IsExitCode takes err and checks whether it represents the given process exit
    76  // code.
    77  //
    78  // IsExitCode assumes that `err` is the return value of a successful call to
    79  // exec.Cmd.Run/Output/CombinedOutput and hence an *exec.ExitError.
    80  func IsExitCode(err error, exitCode int) error {
    81  	if err == nil {
    82  		if exitCode != 0 {
    83  			return fmt.Errorf("got code 0, want %d", exitCode)
    84  		}
    85  		return nil
    86  	}
    87  
    88  	exitErr, ok := err.(*exec.ExitError)
    89  	if !ok {
    90  		return fmt.Errorf("encountered error other than ExitError: %#v", err)
    91  	}
    92  	es, err := exitStatus(exitErr)
    93  	if err != nil {
    94  		return err
    95  	}
    96  	if es != exitCode {
    97  		return fmt.Errorf("got exit status %d, want %d", es, exitCode)
    98  	}
    99  	return nil
   100  }
   101  
   102  func run(m *testing.M, mainFn func()) int {
   103  	// UROOT_CALL_MAIN=1 /proc/self/exe should be the same as just running
   104  	// the command we are testing.
   105  	if len(os.Getenv("UROOT_CALL_MAIN")) > 0 {
   106  		mainFn()
   107  		return 0
   108  	}
   109  
   110  	// Normally, /proc/self/exe (and equivalents) are used to test u-root
   111  	// commands.
   112  	//
   113  	// Such a symlink isn't available on Plan 9, OS X, or FreeBSD. On these
   114  	// systems, we compile the u-root command in question on the fly
   115  	// instead.
   116  	//
   117  	// Here, we decide whether to compile or not and cache the executable.
   118  	// Do this here, so that when m.Run() returns, we can remove the
   119  	// executable using the functor returned.
   120  	_, err := os.Executable()
   121  	if err != nil || len(os.Getenv("UROOT_TEST_BUILD")) > 0 {
   122  		// We can't find ourselves? Probably FreeBSD or something. Try to go
   123  		// build the command.
   124  		//
   125  		// This is NOT build-system-independent, and hence the fallback.
   126  		tmpDir, err := os.MkdirTemp("", "uroot-build")
   127  		if err != nil {
   128  			log.Fatal(err)
   129  		}
   130  		defer os.RemoveAll(tmpDir)
   131  
   132  		wd, err := os.Getwd()
   133  		if err != nil {
   134  			log.Fatal(err)
   135  		}
   136  
   137  		execPath := filepath.Join(tmpDir, "binary")
   138  		// Build the stuff.
   139  		if err := golang.Default().BuildDir(wd, execPath, nil); err != nil {
   140  			log.Fatal(err)
   141  		}
   142  
   143  		// Cache dat.
   144  		binary = execPath
   145  	}
   146  
   147  	return m.Run()
   148  }
   149  
   150  // Run sets up necessary commands to be compiled, if necessary, and calls
   151  // m.Run.
   152  func Run(m *testing.M, mainFn func()) {
   153  	os.Exit(run(m, mainFn))
   154  }
   155  
   156  // SkipIfInVMTest skips a test if it's being executed in a u-root test VM.
   157  //
   158  // See pkg/vmtest/integration.go which starts the VM with the uroot.vmtest in
   159  // the kernel cmdline.
   160  func SkipIfInVMTest(t *testing.T) {
   161  	if cmdline.ContainsFlag("uroot.vmtest") {
   162  		t.Skipf("Skipping test since we are in a u-root test VM")
   163  	}
   164  }
   165  
   166  // SkipIfNotRoot skips the calling test if uid != 0.
   167  func SkipIfNotRoot(t *testing.T) {
   168  	if os.Getuid() != 0 {
   169  		t.Skipf("Skipping test since we are not root")
   170  	}
   171  }