Skip to content

Commit

Permalink
test: port gateway sharness tests to Go tests
Browse files Browse the repository at this point in the history
  • Loading branch information
guseggert committed Jan 17, 2023
1 parent d90a9b5 commit 5d864fa
Show file tree
Hide file tree
Showing 9 changed files with 727 additions and 378 deletions.
492 changes: 492 additions & 0 deletions test/cli/gateway_test.go

Large diffs are not rendered by default.

13 changes: 8 additions & 5 deletions test/cli/harness/harness.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,15 +119,19 @@ func (h *Harness) TempFile() *os.File {
}

// WriteFile writes a file given a filename and its contents.
// The filename should be a relative path.
// 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.WriteFile(absPath, []byte(contents), 0644)
err := os.MkdirAll(filepath.Dir(absPath), 0777)
if err != nil {
log.Panicf("writing '%s' ('%s'): %s", filename, absPath, err.Error())
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())
}
}

Expand All @@ -140,8 +144,7 @@ func WaitForFile(path string, timeout time.Duration) error {
for {
select {
case <-timer.C:
end := time.Now()
return fmt.Errorf("timeout waiting for %s after %v", path, end.Sub(start))
return fmt.Errorf("timeout waiting for %s after %v", path, time.Since(start))
case <-ticker.C:
_, err := os.Stat(path)
if err == nil {
Expand Down
116 changes: 116 additions & 0 deletions test/cli/harness/http_client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package harness

import (
"io"
"net/http"
"strings"
"text/template"
"time"
)

// HTTPClient is an HTTP client with some conveniences for testing.
// URLs are constructed from a base URL.
// The response body is buffered into a string.
// Internal errors cause panics so that tests don't need to check errors.
// The paths are evaluated as Go templates for readable string interpolation.
type HTTPClient struct {
Client *http.Client
BaseURL string

Timeout time.Duration
TemplateData any
}

type HTTPResponse struct {
Body string
StatusCode int
Headers http.Header

// The raw response. The body will be closed on this response.
Resp *http.Response
}

func (c *HTTPClient) WithHeader(k, v string) func(h *http.Request) {
return func(h *http.Request) {
h.Header.Add(k, v)
}
}

func (c *HTTPClient) DisableRedirects() *HTTPClient {
c.Client.CheckRedirect = func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
}
return c
}

// Do executes the request unchanged.
func (c *HTTPClient) Do(req *http.Request) *HTTPResponse {
log.Debugf("making HTTP req %s to %q with headers %+v", req.Method, req.URL.String(), req.Header)
resp, err := c.Client.Do(req)
if resp != nil && resp.Body != nil {
defer resp.Body.Close()
}
if err != nil {
panic(err)
}
bodyStr, err := io.ReadAll(resp.Body)
if err != nil {
panic(err)
}

return &HTTPResponse{
Body: string(bodyStr),
StatusCode: resp.StatusCode,
Headers: resp.Header,
Resp: resp,
}
}

// BuildURL constructs a request URL from the given path by interpolating the string and then appending it to the base URL.
func (c *HTTPClient) BuildURL(urlPath string) string {
sb := &strings.Builder{}
err := template.Must(template.New("test").Parse(urlPath)).Execute(sb, c.TemplateData)
if err != nil {
panic(err)
}
renderedPath := sb.String()
return c.BaseURL + renderedPath
}

func (c *HTTPClient) Get(urlPath string, opts ...func(*http.Request)) *HTTPResponse {
req, err := http.NewRequest(http.MethodGet, c.BuildURL(urlPath), nil)
if err != nil {
panic(err)
}
for _, o := range opts {
o(req)
}
return c.Do(req)
}

func (c *HTTPClient) Post(urlPath string, body io.Reader, opts ...func(*http.Request)) *HTTPResponse {
req, err := http.NewRequest(http.MethodPost, c.BuildURL(urlPath), body)
if err != nil {
panic(err)
}
for _, o := range opts {
o(req)
}
return c.Do(req)
}

func (c *HTTPClient) PostStr(urlpath, body string, opts ...func(*http.Request)) *HTTPResponse {
r := strings.NewReader(body)
return c.Post(urlpath, r, opts...)
}

func (c *HTTPClient) Head(urlPath string, opts ...func(*http.Request)) *HTTPResponse {
req, err := http.NewRequest(http.MethodHead, c.BuildURL(urlPath), nil)
if err != nil {
panic(err)
}
for _, o := range opts {
o(req)
}
return c.Do(req)
}
80 changes: 68 additions & 12 deletions test/cli/harness/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"io"
"io/fs"
"net/http"
"os"
"os/exec"
Expand All @@ -19,6 +20,7 @@ import (
serial "github.com/ipfs/kubo/config/serialize"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/multiformats/go-multiaddr"
manet "github.com/multiformats/go-multiaddr/net"
)

var log = logging.Logger("testharness")
Expand All @@ -29,14 +31,15 @@ type Node struct {
ID int
Dir string

APIListenAddr multiaddr.Multiaddr
SwarmAddr multiaddr.Multiaddr
EnableMDNS bool
APIListenAddr multiaddr.Multiaddr
GatewayListenAddr multiaddr.Multiaddr
SwarmAddr multiaddr.Multiaddr
EnableMDNS bool

IPFSBin string
Runner *Runner

daemon *RunResult
Daemon *RunResult
}

func BuildNode(ipfsBin, baseDir string, id int) *Node {
Expand Down Expand Up @@ -134,11 +137,19 @@ func (n *Node) Init(ipfsArgs ...string) *Node {
n.APIListenAddr = apiAddr
}

if n.GatewayListenAddr == nil {
gatewayAddr, err := multiaddr.NewMultiaddr("/ip4/127.0.0.1/tcp/0")
if err != nil {
panic(err)
}
n.GatewayListenAddr = gatewayAddr
}

n.UpdateConfig(func(cfg *config.Config) {
cfg.Bootstrap = []string{}
cfg.Addresses.Swarm = []string{n.SwarmAddr.String()}
cfg.Addresses.API = []string{n.APIListenAddr.String()}
cfg.Addresses.Gateway = []string{""}
cfg.Addresses.Gateway = []string{n.GatewayListenAddr.String()}
cfg.Swarm.DisableNatPortMap = true
cfg.Discovery.MDNS.Enabled = n.EnableMDNS
})
Expand All @@ -159,15 +170,15 @@ func (n *Node) StartDaemon(ipfsArgs ...string) *Node {
RunFunc: (*exec.Cmd).Start,
})

n.daemon = &res
n.Daemon = &res

log.Debugf("node %d started, checking API", n.ID)
n.WaitOnAPI()
return n
}

func (n *Node) signalAndWait(watch <-chan struct{}, signal os.Signal, t time.Duration) bool {
err := n.daemon.Cmd.Process.Signal(signal)
err := n.Daemon.Cmd.Process.Signal(signal)
if err != nil {
if errors.Is(err, os.ErrProcessDone) {
log.Debugf("process for node %d has already finished", n.ID)
Expand All @@ -187,13 +198,13 @@ func (n *Node) signalAndWait(watch <-chan struct{}, signal os.Signal, t time.Dur

func (n *Node) StopDaemon() *Node {
log.Debugf("stopping node %d", n.ID)
if n.daemon == nil {
if n.Daemon == nil {
log.Debugf("didn't stop node %d since no daemon present", n.ID)
return n
}
watch := make(chan struct{}, 1)
go func() {
_, _ = n.daemon.Cmd.Process.Wait()
_, _ = n.Daemon.Cmd.Process.Wait()
watch <- struct{}{}
}()
log.Debugf("signaling node %d with SIGTERM", n.ID)
Expand Down Expand Up @@ -224,6 +235,15 @@ func (n *Node) APIAddr() multiaddr.Multiaddr {
return ma
}

func (n *Node) APIURL() string {
apiAddr := n.APIAddr()
netAddr, err := manet.ToNetAddr(apiAddr)
if err != nil {
panic(err)
}
return "http://" + netAddr.String()
}

func (n *Node) TryAPIAddr() (multiaddr.Multiaddr, error) {
b, err := os.ReadFile(filepath.Join(n.Dir, "api"))
if err != nil {
Expand Down Expand Up @@ -305,20 +325,21 @@ func (n *Node) WaitOnAPI() *Node {
log.Debugf("waiting on API for node %d", n.ID)
for i := 0; i < 50; i++ {
if n.checkAPI() {
log.Debugf("daemon API found, daemon stdout: %s", n.Daemon.Stdout.String())
return n
}
time.Sleep(400 * time.Millisecond)
}
log.Panicf("node %d with peer ID %s failed to come online: \n%s\n\n%s", n.ID, n.PeerID(), n.daemon.Stderr.String(), n.daemon.Stdout.String())
log.Panicf("node %d with peer ID %s failed to come online: \n%s\n\n%s", n.ID, n.PeerID(), n.Daemon.Stderr.String(), n.Daemon.Stdout.String())
return n
}

func (n *Node) IsAlive() bool {
if n.daemon == nil || n.daemon.Cmd == nil || n.daemon.Cmd.Process == nil {
if n.Daemon == nil || n.Daemon.Cmd == nil || n.Daemon.Cmd.Process == nil {
return false
}
log.Debugf("signaling node %d daemon process for liveness check", n.ID)
err := n.daemon.Cmd.Process.Signal(syscall.Signal(0))
err := n.Daemon.Cmd.Process.Signal(syscall.Signal(0))
if err == nil {
log.Debugf("node %d daemon is alive", n.ID)
return true
Expand Down Expand Up @@ -381,3 +402,38 @@ func (n *Node) Peers() []multiaddr.Multiaddr {
}
return addrs
}

// GatewayURL waits for the gateway file and then returns its contents or times out.
func (n *Node) GatewayURL() string {
timer := time.NewTimer(1 * time.Second)
defer timer.Stop()
for {
select {
case <-timer.C:
panic("timeout waiting for gateway file")
default:
b, err := os.ReadFile(filepath.Join(n.Dir, "gateway"))
if err == nil {
return strings.TrimSpace(string(b))
}
if !errors.Is(err, fs.ErrNotExist) {
panic(err)
}
time.Sleep(1 * time.Millisecond)
}
}
}

func (n *Node) GatewayClient() *HTTPClient {
return &HTTPClient{
Client: http.DefaultClient,
BaseURL: n.GatewayURL(),
}
}

func (n *Node) APIClient() *HTTPClient {
return &HTTPClient{
Client: http.DefaultClient,
BaseURL: n.APIURL(),
}
}
21 changes: 19 additions & 2 deletions test/cli/harness/nodes.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package harness

import (
"sync"

"github.com/multiformats/go-multiaddr"
)

Expand All @@ -15,14 +17,22 @@ func (n Nodes) Init(args ...string) Nodes {
}

func (n Nodes) Connect() Nodes {
wg := sync.WaitGroup{}
for i, node := range n {
for j, otherNode := range n {
if i == j {
continue
}
node.Connect(otherNode)
node := node
otherNode := otherNode
wg.Add(1)
go func() {
defer wg.Done()
node.Connect(otherNode)
}()
}
}
wg.Wait()
for _, node := range n {
firstPeer := node.Peers()[0]
if _, err := firstPeer.ValueForProtocol(multiaddr.P_P2P); err != nil {
Expand All @@ -33,9 +43,16 @@ func (n Nodes) Connect() Nodes {
}

func (n Nodes) StartDaemons() Nodes {
wg := sync.WaitGroup{}
for _, node := range n {
node.StartDaemon()
wg.Add(1)
node := node
go func() {
defer wg.Done()
node.StartDaemon()
}()
}
wg.Wait()
return n
}

Expand Down
Loading

0 comments on commit 5d864fa

Please sign in to comment.