kubo/test/cli/harness/harness.go
Gus Eggert 676e557daf test: port peering test from sharness to Go
This is the slowest test in the sharness test suite, because it has
very long sleeps. It usually takes 2+ minutes to run.

This new impl runs all peering tests in about 20 seconds, since it
polls for conditions instead of sleeping, and runs the tests in
parallel.

This also has an additional test case for a peer that was never online
and then connects.
2023-03-13 10:54:53 -04:00

212 lines
5.0 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"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/multiformats/go-multiaddr"
)
// 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 must be a relative path, or this panics.
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.MkdirAll(filepath.Dir(absPath), 0777)
if err != nil {
log.Panicf("creating intermediate dirs for %q: %s", filename, err.Error())
}
err = os.WriteFile(absPath, []byte(contents), 0644)
if err != nil {
log.Panicf("writing %q (%q): %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:
return fmt.Errorf("timeout waiting for %s after %v", path, time.Since(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)
}
}
// ExtractPeerID extracts a peer ID from the given multiaddr, and fatals if it does not contain a peer ID.
func (h *Harness) ExtractPeerID(m multiaddr.Multiaddr) peer.ID {
var peerIDStr string
multiaddr.ForEach(m, func(c multiaddr.Component) bool {
if c.Protocol().Code == multiaddr.P_P2P {
peerIDStr = c.Value()
}
return true
})
if peerIDStr == "" {
panic(multiaddr.ErrProtocolNotFound)
}
peerID, err := peer.Decode(peerIDStr)
if err != nil {
panic(err)
}
return peerID
}