/
test_support.go
181 lines (154 loc) · 5.47 KB
/
test_support.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
// Copyright 2023 The AthanorLabs/atomic-swap Authors
// SPDX-License-Identifier: LGPL-3.0-only
//go:build !prod
package daemon
import (
"context"
"crypto/ecdsa"
"fmt"
"net"
"path"
"sync"
"syscall"
"testing"
"time"
"github.com/stretchr/testify/require"
"github.com/athanorlabs/atomic-swap/bootnode"
"github.com/athanorlabs/atomic-swap/common"
"github.com/athanorlabs/atomic-swap/common/types"
contracts "github.com/athanorlabs/atomic-swap/ethereum"
"github.com/athanorlabs/atomic-swap/ethereum/extethclient"
"github.com/athanorlabs/atomic-swap/monero"
"github.com/athanorlabs/atomic-swap/rpcclient"
"github.com/athanorlabs/atomic-swap/tests"
)
// This file is only for test support. Use the build tag "prod" to prevent
// symbols in this file from consuming space in production binaries.
// CreateTestConf creates a localhost-only dev environment SwapdConfig config
// for testing
func CreateTestConf(t *testing.T, ethKey *ecdsa.PrivateKey) *SwapdConfig {
ctx := context.Background()
ec, err := extethclient.NewEthClient(ctx, common.Development, common.DefaultGanacheEndpoint, ethKey)
require.NoError(t, err)
t.Cleanup(func() {
ec.Close()
})
rpcPort, err := common.GetFreeTCPPort()
require.NoError(t, err)
// We need a copy of the environment conf, as it is no longer a singleton
// when we are testing it here.
envConf := new(common.Config)
*envConf = *common.ConfigDefaultsForEnv(common.Development)
envConf.DataDir = t.TempDir()
// Passed in ETH key may not have funds, deploy contract with the funded taker key
envConf.SwapCreatorAddr, _ = contracts.DevDeploySwapCreator(t, ec.Raw(), tests.GetTakerTestKey(t))
return &SwapdConfig{
EnvConf: envConf,
MoneroClient: monero.CreateWalletClient(t),
EthereumClient: ec,
Libp2pPort: 0,
Libp2pKeyfile: "",
RPCPort: uint16(rpcPort),
IsRelayer: false,
NoTransferBack: false,
}
}
// CreateTestBootnode creates a bootnode for unit tests that is automatically
// cleaned up when the test completes. Returns the local RPC port and P2P
// address for the node.
func CreateTestBootnode(t *testing.T) (uint16, string) {
rpcPort, err := common.GetFreeTCPPort()
require.NoError(t, err)
// The bootnode uses an independent context from any swapd instances and
// will not exit until the end of the test. To shut it down early, you can
// use the shutdown RPC method on it.
ctx, cancel := context.WithCancel(context.Background())
var wg sync.WaitGroup
t.Cleanup(func() {
cancel()
wg.Wait()
})
dataDir := t.TempDir()
conf := &bootnode.Config{
DataDir: t.TempDir(),
Bootnodes: nil,
P2PListenIP: "127.0.0.1",
Libp2pPort: 0,
Libp2pKeyFile: path.Join(dataDir, common.DefaultLibp2pKeyFileName),
RPCPort: uint16(rpcPort),
}
wg.Add(1)
go func() {
defer wg.Done()
err := bootnode.RunBootnode(ctx, conf) //nolint:govet
require.ErrorIs(t, err, context.Canceled)
t.Log("bootnode exited")
}()
WaitForSwapdStart(t, conf.RPCPort)
addresses, err := rpcclient.NewClient(ctx, conf.RPCPort).Addresses()
require.NoError(t, err)
require.NotEmpty(t, addresses)
return conf.RPCPort, addresses.Addrs[0]
}
// LaunchDaemons launches one or more swapd daemons and blocks until they are
// started. If more than one config is passed, the bootnode settings of the
// passed config are modified to make the first daemon the bootnode for the
// remaining daemons.
func LaunchDaemons(t *testing.T, timeout time.Duration, configs ...*SwapdConfig) (context.Context, context.CancelFunc) {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
// We'll use the bootnode(s) in the first config for any config that does
// not have the bootnodes field set. If the first config has no configured
// bootnodes, we create one.
require.NotEmpty(t, configs)
bootnodes := configs[0].EnvConf.Bootnodes
if len(bootnodes) == 0 {
_, node := CreateTestBootnode(t)
bootnodes = []string{node}
}
// ensure all swapd instances have exited before we let the test complete
var wg sync.WaitGroup
t.Cleanup(func() {
cancel()
wg.Wait()
})
for n, conf := range configs {
if len(conf.EnvConf.Bootnodes) == 0 {
conf.EnvConf.Bootnodes = bootnodes
}
wg.Add(1)
go func(confIndex int) {
defer wg.Done()
err := RunSwapDaemon(ctx, conf)
require.ErrorIs(t, err, context.Canceled)
t.Logf("swapd#%d exited", confIndex)
}(n)
WaitForSwapdStart(t, conf.RPCPort)
}
return ctx, cancel
}
// WaitForSwapdStart takes the rpcPort of a swapd instance and waits for it to
// be in a listening state. Fails the test if the server isn't listening after a
// little over 60 seconds.
func WaitForSwapdStart(t *testing.T, rpcPort uint16) {
const maxSeconds = 60
addr := fmt.Sprintf("127.0.0.1:%d", rpcPort)
startTime := time.Now()
for i := 0; i < maxSeconds; i++ {
conn, err := net.DialTimeout("tcp", addr, time.Second)
if err == nil {
startupTime := time.Since(startTime).Round(time.Second)
t.Logf("daemon on rpc port %d started after %s", rpcPort, startupTime)
require.NoError(t, conn.Close())
return
}
// DialTimeout doesn't do retries. If the connection was refused, it happened
// almost immediately, so we still need to sleep.
require.ErrorIs(t, err, syscall.ECONNREFUSED)
time.Sleep(time.Second)
}
t.Fatalf("giving up, swapd RPC port %d is not listening after %d seconds", rpcPort, maxSeconds)
}
func getMockTetherAsset(t *testing.T, ec extethclient.EthClient) types.EthAsset {
token := contracts.GetMockTether(t, ec.Raw(), ec.PrivateKey())
return types.EthAsset(token.Address)
}