github.com/rminnich/u-root@v7.0.0+incompatible/pkg/vmtest/gotest.go (about)

     1  // Copyright 2018 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 vmtest
     6  
     7  import (
     8  	"fmt"
     9  	"io"
    10  	"io/ioutil"
    11  	"os"
    12  	"os/exec"
    13  	"path"
    14  	"path/filepath"
    15  	"testing"
    16  
    17  	"github.com/u-root/u-root/pkg/golang"
    18  	"github.com/u-root/u-root/pkg/uio"
    19  	"github.com/u-root/u-root/pkg/uroot"
    20  	"github.com/u-root/u-root/pkg/vmtest/internal/json2test"
    21  )
    22  
    23  // GolangTest compiles the unit tests found in pkgs and runs them in a QEMU VM.
    24  func GolangTest(t *testing.T, pkgs []string, o *Options) {
    25  	SkipWithoutQEMU(t)
    26  	// TODO: support arm
    27  	if TestArch() != "amd64" && TestArch() != "arm64" {
    28  		t.Skipf("test not supported on %s", TestArch())
    29  	}
    30  
    31  	if o == nil {
    32  		o = &Options{}
    33  	}
    34  
    35  	// Create a temporary directory.
    36  	if len(o.TmpDir) == 0 {
    37  		tmpDir, err := ioutil.TempDir("", "uroot-integration")
    38  		if err != nil {
    39  			t.Fatal(err)
    40  		}
    41  		o.TmpDir = tmpDir
    42  	}
    43  
    44  	// Set up u-root build options.
    45  	env := golang.Default()
    46  	env.CgoEnabled = false
    47  	env.GOARCH = TestArch()
    48  	o.BuildOpts.Env = env
    49  
    50  	// Statically build tests and add them to the temporary directory.
    51  	var tests []string
    52  	os.Setenv("CGO_ENABLED", "0")
    53  	os.Setenv("GOARCH", TestArch())
    54  	testDir := filepath.Join(o.TmpDir, "tests")
    55  	for _, pkg := range pkgs {
    56  		pkgDir := filepath.Join(testDir, pkg)
    57  		if err := os.MkdirAll(pkgDir, 0755); err != nil {
    58  			t.Fatal(err)
    59  		}
    60  
    61  		testFile := filepath.Join(pkgDir, fmt.Sprintf("%s.test", path.Base(pkg)))
    62  
    63  		cmd := exec.Command("go", "test",
    64  			"-gcflags=all=-l",
    65  			"-ldflags", "-s -w",
    66  			"-c", pkg,
    67  			"-o", testFile,
    68  		)
    69  		if stderr, err := cmd.CombinedOutput(); err != nil {
    70  			t.Fatalf("could not build %s: %v\n%s", pkg, err, string(stderr))
    71  		}
    72  
    73  		// When a package does not contain any tests, the test
    74  		// executable is not generated, so it is not included in the
    75  		// `tests` list.
    76  		if _, err := os.Stat(testFile); !os.IsNotExist(err) {
    77  			tests = append(tests, pkg)
    78  
    79  			p, err := o.BuildOpts.Env.Package(pkg)
    80  			if err != nil {
    81  				t.Fatal(err)
    82  			}
    83  			// Optimistically copy any files in the pkg's
    84  			// directory, in case e.g. a testdata dir is there.
    85  			if err := copyRelativeFiles(p.Dir, filepath.Join(testDir, pkg)); err != nil {
    86  				t.Fatal(err)
    87  			}
    88  		}
    89  	}
    90  
    91  	// Create the CPIO and start QEMU.
    92  	o.BuildOpts.AddBusyBoxCommands("github.com/u-root/u-root/cmds/core/*")
    93  	o.BuildOpts.AddCommands(uroot.BinaryCmds("cmd/test2json")...)
    94  
    95  	// Specify the custom gotest uinit.
    96  	o.Uinit = "github.com/u-root/u-root/integration/testcmd/gotest/uinit"
    97  
    98  	tc := json2test.NewTestCollector()
    99  	serial := []io.Writer{
   100  		// Collect JSON test events in tc.
   101  		json2test.EventParser(tc),
   102  		// Write non-JSON output to log.
   103  		JSONLessTestLineWriter(t, "serial"),
   104  	}
   105  	if o.QEMUOpts.SerialOutput != nil {
   106  		serial = append(serial, o.QEMUOpts.SerialOutput)
   107  	}
   108  	o.QEMUOpts.SerialOutput = uio.MultiWriteCloser(serial...)
   109  
   110  	q, cleanup := QEMUTest(t, o)
   111  	defer cleanup()
   112  
   113  	if err := q.Expect("GoTest Done"); err != nil {
   114  		t.Errorf("Waiting for GoTest Done: %v", err)
   115  	}
   116  
   117  	// TODO: check that tc.Tests == tests
   118  	for pkg, test := range tc.Tests {
   119  		switch test.State {
   120  		case json2test.StateFail:
   121  			t.Errorf("Test %v failed:\n%v", pkg, test.FullOutput)
   122  		case json2test.StateSkip:
   123  			t.Logf("Test %v skipped", pkg)
   124  		case json2test.StatePass:
   125  			// Nothing.
   126  		default:
   127  			t.Errorf("Test %v left in state %v:\n%v", pkg, test.State, test.FullOutput)
   128  		}
   129  	}
   130  }
   131  
   132  func copyRelativeFiles(src string, dst string) error {
   133  	return filepath.Walk(src, func(path string, fi os.FileInfo, err error) error {
   134  		if err != nil {
   135  			return err
   136  		}
   137  
   138  		rel, err := filepath.Rel(src, path)
   139  		if err != nil {
   140  			return err
   141  		}
   142  
   143  		if fi.Mode().IsDir() {
   144  			return os.MkdirAll(filepath.Join(dst, rel), fi.Mode().Perm())
   145  		} else if fi.Mode().IsRegular() {
   146  			srcf, err := os.Open(path)
   147  			if err != nil {
   148  				return err
   149  			}
   150  			defer srcf.Close()
   151  			dstf, err := os.Create(filepath.Join(dst, rel))
   152  			if err != nil {
   153  				return err
   154  			}
   155  			defer dstf.Close()
   156  			_, err = io.Copy(dstf, srcf)
   157  			return err
   158  		}
   159  		return nil
   160  	})
   161  }