Skip to content

Commit d7e0340

Browse files
committed
itest: add itest framework and basic tests
1 parent f794333 commit d7e0340

File tree

4 files changed

+233
-7
lines changed

4 files changed

+233
-7
lines changed

itest/chantools_harness.go

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
package itest
2+
3+
import (
4+
"bufio"
5+
"bytes"
6+
"errors"
7+
"io"
8+
"os"
9+
"os/exec"
10+
"strings"
11+
"testing"
12+
"time"
13+
14+
"github.com/stretchr/testify/require"
15+
)
16+
17+
// ChantoolsProcess wraps a running chantools process for integration testing.
18+
type ChantoolsProcess struct {
19+
cmd *exec.Cmd
20+
stdin io.WriteCloser
21+
stdout *os.File
22+
stdoutReader *bufio.Reader
23+
stderr *bufio.Reader
24+
}
25+
26+
// StartChantools starts the chantools binary with the given arguments.
27+
func StartChantools(t *testing.T, args ...string) *ChantoolsProcess {
28+
t.Helper()
29+
30+
args = append([]string{"--nologfile"}, args...)
31+
cmd := exec.Command("chantools", args...)
32+
stdin, err := cmd.StdinPipe()
33+
require.NoError(t, err)
34+
35+
stdoutPipe, err := cmd.StdoutPipe()
36+
require.NoError(t, err)
37+
stderrPipe, err := cmd.StderrPipe()
38+
require.NoError(t, err)
39+
40+
stdoutFile, ok := stdoutPipe.(*os.File)
41+
require.True(t, ok)
42+
43+
require.NoError(t, cmd.Start())
44+
45+
return &ChantoolsProcess{
46+
cmd: cmd,
47+
stdin: stdin,
48+
stdout: stdoutFile,
49+
stdoutReader: bufio.NewReader(stdoutFile),
50+
stderr: bufio.NewReader(stderrPipe),
51+
}
52+
}
53+
54+
// WriteInput writes input to the process's stdin.
55+
func (p *ChantoolsProcess) WriteInput(t *testing.T, input string) {
56+
t.Helper()
57+
58+
_, err := io.WriteString(p.stdin, input)
59+
require.NoError(t, err, "failed to write input to chantools")
60+
}
61+
62+
// ReadAllOutput reads all output from the process's stdout until EOF.
63+
func (p *ChantoolsProcess) ReadAllOutput(t *testing.T) string {
64+
t.Helper()
65+
66+
resp, err := io.ReadAll(p.stdout)
67+
require.NoError(t, err, "failed to read chantools output")
68+
69+
log.Debugf("[CHANTOOLS]: %s", resp)
70+
71+
return string(resp)
72+
}
73+
74+
// ReadOutputUntil reads from stdout until the given substring is found or
75+
// timeout.
76+
func (p *ChantoolsProcess) ReadOutputUntil(t *testing.T, substr string,
77+
timeout time.Duration) string {
78+
79+
t.Helper()
80+
81+
var out bytes.Buffer
82+
deadline := time.Now().Add(timeout)
83+
for {
84+
if time.Now().After(deadline) {
85+
t.Fatal("timeout waiting for chantools output")
86+
}
87+
line, err := p.stdoutReader.ReadString('\n')
88+
out.WriteString(line)
89+
90+
log.Debugf("[CHANTOOLS]: %s", line)
91+
92+
if strings.Contains(out.String(), substr) {
93+
return out.String()
94+
}
95+
96+
require.NoError(t, err)
97+
}
98+
}
99+
100+
// ReadAvailableOutput reads as many bytes as possible from stdout until the
101+
// timeout elapses.
102+
func (p *ChantoolsProcess) ReadAvailableOutput(t *testing.T,
103+
timeout time.Duration) string {
104+
105+
t.Helper()
106+
107+
err := p.stdout.SetReadDeadline(time.Now().Add(timeout))
108+
require.NoError(t, err, "failed to set read deadline")
109+
110+
defer func() {
111+
_ = p.stdout.SetReadDeadline(time.Time{})
112+
}()
113+
114+
var out bytes.Buffer
115+
for {
116+
buf := make([]byte, 1024)
117+
n, err := p.stdoutReader.Read(buf)
118+
if n > 0 {
119+
chunk := string(buf[:n])
120+
out.WriteString(chunk)
121+
}
122+
if err != nil {
123+
if errors.Is(err, io.EOF) ||
124+
errors.Is(err, os.ErrDeadlineExceeded) {
125+
126+
break
127+
}
128+
129+
time.Sleep(50 * time.Millisecond)
130+
}
131+
}
132+
133+
log.Debugf("[CHANTOOLS]: %s", out.String())
134+
return out.String()
135+
}
136+
137+
// Wait waits for the process to exit.
138+
func (p *ChantoolsProcess) Wait(t *testing.T) {
139+
t.Helper()
140+
141+
require.NoError(t, p.cmd.Wait())
142+
}
143+
144+
// Kill kills the process.
145+
func (p *ChantoolsProcess) Kill(t *testing.T) {
146+
t.Helper()
147+
148+
require.NoError(t, p.cmd.Process.Kill())
149+
}

itest/log.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package itest
2+
3+
import (
4+
"os"
5+
6+
"github.com/btcsuite/btclog/v2"
7+
)
8+
9+
var log btclog.Logger
10+
11+
//nolint:gochecknoinits
12+
func init() {
13+
logger := btclog.NewSLogger(btclog.NewDefaultHandler(os.Stdout))
14+
logger.SetLevel(btclog.LevelTrace)
15+
log = logger.SubSystem("ITEST")
16+
}

itest/standalone_test.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package itest
2+
3+
import (
4+
"testing"
5+
"time"
6+
7+
"github.com/stretchr/testify/require"
8+
)
9+
10+
const (
11+
testXPriv = "xprv9s21ZrQH143K2ZzuN99NjD7oJruBomAVbNDXzPmhHYcwf8WXCsML" +
12+
"63azyS1rzzfpUsLifeDkM4Q6U9PF9RP7frSGKkMfDTDiiyQjH2PUj2z"
13+
14+
testMnemonic = "about wolf boost other battle asthma refuse wedding " +
15+
"few purchase track one smooth tunnel immune glass infant " +
16+
"tag manual multiply diagram orient wrist agent"
17+
)
18+
19+
var (
20+
readTimeout = 100 * time.Millisecond
21+
defaultTimeout = 5 * time.Second
22+
)
23+
24+
func TestChantoolsShowRootKeyXPriv(t *testing.T) {
25+
proc := StartChantools(t, "showrootkey", "--rootkey", testXPriv)
26+
defer proc.Kill(t)
27+
28+
output := proc.ReadOutputUntil(
29+
t, "Your BIP32 HD root key is:", defaultTimeout,
30+
)
31+
require.Contains(t, output, "Your BIP32 HD root key is: "+testXPriv)
32+
}
33+
34+
func TestChantoolsShowRootKeyMnemonic(t *testing.T) {
35+
proc := StartChantools(t, "showrootkey")
36+
defer proc.Kill(t)
37+
38+
go func() {
39+
errString, err := proc.stderr.ReadString('\n')
40+
log.Errorf("chantools stderr: %v, error: %v", errString, err)
41+
}()
42+
43+
mnemonicPrompt := proc.ReadAvailableOutput(t, readTimeout)
44+
require.Contains(t, mnemonicPrompt, "Input your 24-word mnemonic")
45+
proc.WriteInput(t, testMnemonic+"\n")
46+
47+
passphrasePrompt := proc.ReadAvailableOutput(t, readTimeout)
48+
require.Contains(
49+
t, passphrasePrompt, "Input your cipher seed passphrase",
50+
)
51+
proc.WriteInput(t, "\n")
52+
53+
output := proc.ReadOutputUntil(
54+
t, "Your BIP32 HD root key is:", defaultTimeout,
55+
)
56+
require.Contains(t, output, "Your BIP32 HD root key is: "+testXPriv)
57+
}

lnd/aezeed.go

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package lnd
22

33
import (
44
"bufio"
5+
"bytes"
56
"errors"
67
"fmt"
78
"os"
@@ -139,16 +140,15 @@ func ReadPassphrase(verb string) ([]byte, error) {
139140
// The environment variable didn't contain anything, we'll read the
140141
// passphrase from the terminal.
141142
case passphrase == "":
142-
fmt.Printf("Input your cipher seed passphrase (press enter "+
143-
"if your seed %s a passphrase): ", verb)
144143
var err error
145-
passphraseBytes, err = terminal.ReadPassword(
146-
int(syscall.Stdin), //nolint
144+
passphraseBytes, err = PasswordFromConsole(
145+
fmt.Sprintf("Input your cipher seed passphrase "+
146+
"(press enter if your seed %s a passphrase): ",
147+
verb),
147148
)
148149
if err != nil {
149150
return nil, err
150151
}
151-
fmt.Println()
152152

153153
// There was a password in the environment, just convert it to bytes.
154154
default:
@@ -160,13 +160,15 @@ func ReadPassphrase(verb string) ([]byte, error) {
160160

161161
// PasswordFromConsole reads a password from the console or stdin.
162162
func PasswordFromConsole(userQuery string) ([]byte, error) {
163+
fmt.Print(userQuery)
164+
163165
// Read from terminal (if there is one).
164166
if terminal.IsTerminal(int(syscall.Stdin)) { //nolint
165-
fmt.Print(userQuery)
166167
pw, err := terminal.ReadPassword(int(syscall.Stdin)) //nolint
167168
if err != nil {
168169
return nil, err
169170
}
171+
170172
fmt.Println()
171173
return pw, nil
172174
}
@@ -177,7 +179,9 @@ func PasswordFromConsole(userQuery string) ([]byte, error) {
177179
if err != nil {
178180
return nil, err
179181
}
180-
return pw, nil
182+
183+
fmt.Println()
184+
return bytes.TrimSpace(pw), nil
181185
}
182186

183187
// OpenWallet opens a lnd compatible wallet and returns it, along with the

0 commit comments

Comments
 (0)