mirror of
https://github.com/ipfs/kubo.git
synced 2026-03-01 14:28:02 +08:00
This is intended as a replacement for sharness. These are vanilla Go tests which can be run in your IDE for quick iteration on end-to-end CLI tests. This also removes IPTB by duplicating its functionality in the test harness. This isn't a big deal...IPTB's complexity is mostly around the fact that its state needs to be saved to disk in between `iptb` command invocations, and that it uses Go plugins to inject functionality, neither of which are relevant here. If we merge this, we'll have to live with bifurcated tests for a while until they are all migrated. I'd recommend we self-enforce a rule that, if we need to touch a sharness test, we migrate it and one more test over to Go tests first. Then eventually we will have migrated everything.
188 lines
4.3 KiB
Go
188 lines
4.3 KiB
Go
package harness
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
logging "github.com/ipfs/go-log/v2"
|
|
. "github.com/ipfs/kubo/test/cli/testutils"
|
|
)
|
|
|
|
// Harness tracks state for a test, such as temp dirs and IFPS nodes, and cleans them up after the test.
|
|
type Harness struct {
|
|
Dir string
|
|
IPFSBin string
|
|
Runner *Runner
|
|
NodesRoot string
|
|
Nodes Nodes
|
|
}
|
|
|
|
// TODO: use zaptest.NewLogger(t) instead
|
|
func EnableDebugLogging() {
|
|
err := logging.SetLogLevel("testharness", "DEBUG")
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
// NewT constructs a harness that cleans up after the given test is done.
|
|
func NewT(t *testing.T, options ...func(h *Harness)) *Harness {
|
|
h := New(options...)
|
|
t.Cleanup(h.Cleanup)
|
|
return h
|
|
}
|
|
|
|
func New(options ...func(h *Harness)) *Harness {
|
|
h := &Harness{Runner: &Runner{Env: osEnviron()}}
|
|
|
|
// walk up to find the root dir, from which we can locate the binary
|
|
wd, err := os.Getwd()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
goMod := FindUp("go.mod", wd)
|
|
if goMod == "" {
|
|
panic("unable to find root dir")
|
|
}
|
|
rootDir := filepath.Dir(goMod)
|
|
h.IPFSBin = filepath.Join(rootDir, "cmd", "ipfs", "ipfs")
|
|
|
|
// setup working dir
|
|
tmpDir, err := os.MkdirTemp("", "")
|
|
if err != nil {
|
|
log.Panicf("error creating temp dir: %s", err)
|
|
}
|
|
h.Dir = tmpDir
|
|
h.Runner.Dir = h.Dir
|
|
|
|
h.NodesRoot = filepath.Join(h.Dir, ".nodes")
|
|
|
|
// apply any customizations
|
|
// this should happen after all initialization
|
|
for _, o := range options {
|
|
o(h)
|
|
}
|
|
|
|
return h
|
|
}
|
|
|
|
func osEnviron() map[string]string {
|
|
m := map[string]string{}
|
|
for _, entry := range os.Environ() {
|
|
split := strings.Split(entry, "=")
|
|
m[split[0]] = split[1]
|
|
}
|
|
return m
|
|
}
|
|
|
|
func (h *Harness) NewNode() *Node {
|
|
nodeID := len(h.Nodes)
|
|
node := BuildNode(h.IPFSBin, h.NodesRoot, nodeID)
|
|
h.Nodes = append(h.Nodes, node)
|
|
return node
|
|
}
|
|
|
|
func (h *Harness) NewNodes(count int) Nodes {
|
|
var newNodes []*Node
|
|
for i := 0; i < count; i++ {
|
|
newNodes = append(newNodes, h.NewNode())
|
|
}
|
|
return newNodes
|
|
}
|
|
|
|
// WriteToTemp writes the given contents to a guaranteed-unique temp file, returning its path.
|
|
func (h *Harness) WriteToTemp(contents string) string {
|
|
f := h.TempFile()
|
|
_, err := f.WriteString(contents)
|
|
if err != nil {
|
|
log.Panicf("writing to temp file: %s", err.Error())
|
|
}
|
|
err = f.Close()
|
|
if err != nil {
|
|
log.Panicf("closing temp file: %s", err.Error())
|
|
}
|
|
return f.Name()
|
|
}
|
|
|
|
// TempFile creates a new unique temp file.
|
|
func (h *Harness) TempFile() *os.File {
|
|
f, err := os.CreateTemp(h.Dir, "")
|
|
if err != nil {
|
|
log.Panicf("creating temp file: %s", err.Error())
|
|
}
|
|
return f
|
|
}
|
|
|
|
// WriteFile writes a file given a filename and its contents.
|
|
// The filename should be a relative path.
|
|
func (h *Harness) WriteFile(filename, contents string) {
|
|
if filepath.IsAbs(filename) {
|
|
log.Panicf("%s must be a relative path", filename)
|
|
}
|
|
absPath := filepath.Join(h.Runner.Dir, filename)
|
|
err := os.WriteFile(absPath, []byte(contents), 0644)
|
|
if err != nil {
|
|
log.Panicf("writing '%s' ('%s'): %s", filename, absPath, err.Error())
|
|
}
|
|
}
|
|
|
|
func WaitForFile(path string, timeout time.Duration) error {
|
|
start := time.Now()
|
|
timer := time.NewTimer(timeout)
|
|
ticker := time.NewTicker(1 * time.Millisecond)
|
|
defer timer.Stop()
|
|
defer ticker.Stop()
|
|
for {
|
|
select {
|
|
case <-timer.C:
|
|
end := time.Now()
|
|
return fmt.Errorf("timeout waiting for %s after %v", path, end.Sub(start))
|
|
case <-ticker.C:
|
|
_, err := os.Stat(path)
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
if errors.Is(err, os.ErrNotExist) {
|
|
continue
|
|
}
|
|
return fmt.Errorf("error waiting for %s: %w", path, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (h *Harness) Mkdirs(paths ...string) {
|
|
for _, path := range paths {
|
|
if filepath.IsAbs(path) {
|
|
log.Panicf("%s must be a relative path when making dirs", path)
|
|
}
|
|
absPath := filepath.Join(h.Runner.Dir, path)
|
|
err := os.MkdirAll(absPath, 0777)
|
|
if err != nil {
|
|
log.Panicf("recursively making dirs under %s: %s", absPath, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (h *Harness) Sh(expr string) RunResult {
|
|
return h.Runner.Run(RunRequest{
|
|
Path: "bash",
|
|
Args: []string{"-c", expr},
|
|
})
|
|
}
|
|
|
|
func (h *Harness) Cleanup() {
|
|
log.Debugf("cleaning up cluster")
|
|
h.Nodes.StopDaemons()
|
|
// TODO: don't do this if test fails, not sure how?
|
|
log.Debugf("removing harness dir")
|
|
err := os.RemoveAll(h.Dir)
|
|
if err != nil {
|
|
log.Panicf("removing temp dir %s: %s", h.Dir, err)
|
|
}
|
|
}
|