internal/testutil/runmain.go (66 lines of code) (raw):
// Copyright 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package testutil
import (
"bytes"
"context"
"fmt"
"os"
"os/exec"
"path/filepath"
"testing"
"time"
)
// BuildMain builds the main package in the current working directory.
// If it doesn't build, t.Fatal is called.
// Test methods calling BuildMain should run Runner.Cleanup.
func BuildMain(t *testing.T) *Runner {
wd, err := os.Getwd()
if err != nil {
t.Fatal(err)
}
tmp, err := os.MkdirTemp("", "runmain-"+filepath.Base(wd)+"-")
if err != nil {
t.Fatal(err)
}
r := &Runner{t: t, tmp: tmp}
bin := filepath.Join(tmp, "a.out")
cmd := exec.Command("go", "build", "-o", bin)
if out, err := cmd.CombinedOutput(); err != nil {
t.Errorf("go build: %v\n%s", err, out)
return r
}
r.bin = bin
return r
}
// Runner holds the result of `go build`
type Runner struct {
t *testing.T
tmp string
bin string
}
// Built reports whether the build was successful.
func (r *Runner) Built() bool {
return r.bin != ""
}
// Cleanup removes the built binary.
func (r *Runner) Cleanup() {
if err := os.RemoveAll(r.tmp); err != nil {
r.t.Error(err)
}
}
// Run executes runs the built binary until terminated or timeout has
// been reached, and indicates successful execution on return. You can
// supply extra arguments for the binary via args.
func (r *Runner) Run(env map[string]string, timeout time.Duration, args ...string) (stdout, stderr []byte, err error) {
if !r.Built() {
return nil, nil, fmt.Errorf("tried to run when binary not built")
}
environ := os.Environ()
for k, v := range env {
environ = append(environ, k+"="+v)
}
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
cmd := exec.CommandContext(ctx, r.bin, args...)
cmd.Env = environ
var bufOut, bufErr bytes.Buffer
cmd.Stdout = &bufOut
cmd.Stderr = &bufErr
if err := cmd.Start(); err != nil {
return nil, nil, fmt.Errorf("could not execute binary: %w", err)
}
if err := cmd.Wait(); err != nil {
return bufOut.Bytes(), bufErr.Bytes(), err
}
return bufOut.Bytes(), bufErr.Bytes(), nil
}