Skip to content

Commit 85380a9

Browse files
committed
asserting logs
1 parent 837e0f6 commit 85380a9

File tree

17 files changed

+718
-135
lines changed

17 files changed

+718
-135
lines changed

book/src/SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
- [NodeSet (Local Docker builds)](./framework/nodeset_docker_rebuild.md)
1212
- [NodeSet Compat Environment](./framework/nodeset_compatibility.md)
1313
- [Creating your own components](./developing/developing_components.md)
14+
- [Asserting Logs](./developing/asserting_logs.md)
1415
- [Fork Testing](./framework/fork.md)
1516
- [Quick Contracts Deployment](./framework/quick_deployment.md)
1617
- [Verifying Contracts](./framework/verify.md)
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Asserting Container Logs
2+
3+
You can either assert that CL nodes have no errors like that, we check `(CRIT|PANIC|FATAL)` levels by default for all the nodes
4+
5+
```golang
6+
in, err := framework.Load[Cfg](t)
7+
require.NoError(t, err)
8+
t.Cleanup(func() {
9+
err := framework.SaveAndCheckLogs(t)
10+
require.NoError(t, err)
11+
})
12+
```
13+
14+
or customize file assertions
15+
16+
```golang
17+
in, err := framework.Load[Cfg](t)
18+
require.NoError(t, err)
19+
t.Cleanup(func() {
20+
// save all the logs to default directory "logs/docker-$test_name"
21+
logs, err := framework.SaveContainerLogs(fmt.Sprintf("%s-%s", framework.DefaultCTFLogsDir, t.Name()))
22+
require.NoError(t, err)
23+
// check that CL nodes has no errors (CRIT|PANIC|FATAL) levels
24+
err = framework.CheckCLNodeContainerErrors()
25+
require.NoError(t, err)
26+
// do custom assertions
27+
for _, l := range logs {
28+
matches, err := framework.SearchLogFile(l, " name=HeadReporter version=\\d")
29+
require.NoError(t, err)
30+
_ = matches
31+
}
32+
})
33+
```
34+
35+
Full [example](https://github.com/smartcontractkit/chainlink-testing-framework/blob/main/framework/examples/myproject/smoke_logs_test.go)

framework/.changeset/v0.4.8.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
- Do not expose 40k ports by default, only with CTF_CLNODE_DLV flag
2+
- Remove default log checking API, make it simpler to customize

framework/components/clnode/clnode.go

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -135,20 +135,22 @@ func generateEntryPoint() []string {
135135
// exposes custom_ports in format "host:docker" or map 1-to-1 if only "host" port is provided
136136
func generatePortBindings(in *Input) ([]string, nat.PortMap, error) {
137137
httpPort := fmt.Sprintf("%s/tcp", DefaultHTTPPort)
138-
innerDebuggerPort := fmt.Sprintf("%d/tcp", DefaultDebuggerPort)
138+
exposedPorts := []string{httpPort}
139139
portBindings := nat.PortMap{
140140
nat.Port(httpPort): []nat.PortBinding{
141141
{
142142
HostIP: "0.0.0.0",
143143
HostPort: strconv.Itoa(in.Node.HTTPPort),
144144
},
145145
},
146-
nat.Port(innerDebuggerPort): []nat.PortBinding{
147-
{
148-
HostIP: "0.0.0.0",
149-
HostPort: strconv.Itoa(in.Node.DebuggerPort),
150-
},
151-
},
146+
}
147+
if os.Getenv("CTF_CLNODE_DLV") == "true" {
148+
innerDebuggerPort := fmt.Sprintf("%d/tcp", DefaultDebuggerPort)
149+
portBindings[nat.Port(innerDebuggerPort)] = append(portBindings[nat.Port(innerDebuggerPort)], nat.PortBinding{
150+
HostIP: "0.0.0.0",
151+
HostPort: strconv.Itoa(in.Node.DebuggerPort),
152+
})
153+
exposedPorts = append(exposedPorts, strconv.Itoa(DefaultDebuggerPort))
152154
}
153155
customPorts := make([]string, 0)
154156
for _, p := range in.Node.CustomPorts {
@@ -180,7 +182,6 @@ func generatePortBindings(in *Input) ([]string, nat.PortMap, error) {
180182
}
181183
}
182184
}
183-
exposedPorts := []string{httpPort, strconv.Itoa(DefaultDebuggerPort)}
184185
exposedPorts = append(exposedPorts, customPorts...)
185186
return exposedPorts, portBindings, nil
186187
}

framework/components/simple_node_set/reload.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import (
1414
// this API is discouraged, however, you can use it if nodes require restart or configuration updates, temporarily!
1515
func UpgradeNodeSet(t *testing.T, in *Input, bc *blockchain.Output, wait time.Duration) (*Output, error) {
1616
uniq := fmt.Sprintf("%s-%s-%s", framework.DefaultCTFLogsDir, t.Name(), uuid.NewString()[0:4])
17-
if err := framework.WriteAllContainersLogs(uniq); err != nil {
17+
if _, err := framework.SaveContainerLogs(uniq); err != nil {
1818
return nil, err
1919
}
2020
_, err := chaos.ExecPumba("rm --volumes=false re2:^node.*|ns-postgresql.*", wait)

framework/config.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -130,10 +130,6 @@ func Load[X any](t *testing.T) (*X, error) {
130130
t.Cleanup(func() {
131131
err := Store[X](input)
132132
require.NoError(t, err)
133-
err = WriteAllContainersLogs(fmt.Sprintf("%s-%s", DefaultCTFLogsDir, t.Name()))
134-
require.NoError(t, err)
135-
err = checkAllNodeLogErrors()
136-
require.NoError(t, err)
137133
})
138134
// TODO: not all the people have AWS access, sadly enough, uncomment when granted
139135
//if os.Getenv(EnvVarAWSSecretsManager) == "true" {

framework/docker.go

Lines changed: 55 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@ package framework
22

33
import (
44
"archive/tar"
5+
"bufio"
56
"bytes"
67
"context"
78
"encoding/binary"
89
"fmt"
910
"github.com/docker/docker/api/types/container"
10-
filters2 "github.com/docker/docker/api/types/filters"
11+
dfilter "github.com/docker/docker/api/types/filters"
1112
"github.com/docker/docker/client"
1213
"github.com/docker/go-connections/nat"
1314
"github.com/google/uuid"
@@ -17,8 +18,10 @@ import (
1718
"os"
1819
"os/exec"
1920
"path/filepath"
21+
"regexp"
2022
"strings"
2123
"sync"
24+
"testing"
2225
)
2326

2427
const (
@@ -176,30 +179,70 @@ func (dc *DockerClient) copyToContainer(containerID, sourceFile, targetPath stri
176179
return nil
177180
}
178181

179-
// WriteAllContainersLogs writes all Docker container logs to the default logs directory
180-
func WriteAllContainersLogs(dir string) error {
182+
// SearchLogFile searches logfile using regex and return matches or error
183+
func SearchLogFile(fp string, regex string) ([]string, error) {
184+
file, err := os.Open(fp)
185+
if err != nil {
186+
return nil, err
187+
}
188+
defer file.Close()
189+
scanner := bufio.NewScanner(file)
190+
re, err := regexp.Compile(regex)
191+
if err != nil {
192+
return nil, err
193+
}
194+
matches := make([]string, 0)
195+
for scanner.Scan() {
196+
line := scanner.Text()
197+
if re.MatchString(line) {
198+
L.Info().Str("Regex", regex).Msg("Log match found")
199+
matches = append(matches, line)
200+
}
201+
}
202+
203+
if err := scanner.Err(); err != nil {
204+
return matches, err
205+
}
206+
return matches, nil
207+
}
208+
209+
func SaveAndCheckLogs(t *testing.T) error {
210+
_, err := SaveContainerLogs(fmt.Sprintf("%s-%s", DefaultCTFLogsDir, t.Name()))
211+
if err != nil {
212+
return err
213+
}
214+
err = CheckCLNodeContainerErrors()
215+
if err != nil {
216+
return err
217+
}
218+
return nil
219+
}
220+
221+
// SaveContainerLogs writes all Docker container logs to some directory
222+
func SaveContainerLogs(dir string) ([]string, error) {
181223
L.Info().Msg("Writing Docker containers logs")
182224
if _, err := os.Stat(dir); os.IsNotExist(err) {
183225
if err := os.MkdirAll(dir, 0755); err != nil {
184-
return fmt.Errorf("failed to create directory %s: %w", dir, err)
226+
return nil, fmt.Errorf("failed to create directory %s: %w", dir, err)
185227
}
186228
}
187229
provider, err := tc.NewDockerProvider()
188230
if err != nil {
189-
return fmt.Errorf("failed to create Docker provider: %w", err)
231+
return nil, fmt.Errorf("failed to create Docker provider: %w", err)
190232
}
191233
containers, err := provider.Client().ContainerList(context.Background(), container.ListOptions{
192234
All: true,
193-
Filters: filters2.NewArgs(filters2.KeyValuePair{
235+
Filters: dfilter.NewArgs(dfilter.KeyValuePair{
194236
Key: "label",
195237
Value: "framework=ctf",
196238
}),
197239
})
198240
if err != nil {
199-
return fmt.Errorf("failed to list Docker containers: %w", err)
241+
return nil, fmt.Errorf("failed to list Docker containers: %w", err)
200242
}
201243

202244
eg := &errgroup.Group{}
245+
logFilePaths := make([]string, 0)
203246

204247
for _, containerInfo := range containers {
205248
eg.Go(func() error {
@@ -217,6 +260,7 @@ func WriteAllContainersLogs(dir string) error {
217260
L.Error().Err(err).Str("Container", containerName).Msg("failed to create container log file")
218261
return err
219262
}
263+
logFilePaths = append(logFilePaths, logFilePath)
220264
// Parse and write logs
221265
header := make([]byte, 8) // Docker stream header is 8 bytes
222266
for {
@@ -249,7 +293,10 @@ func WriteAllContainersLogs(dir string) error {
249293
return nil
250294
})
251295
}
252-
return eg.Wait()
296+
if err := eg.Wait(); err != nil {
297+
return nil, err
298+
}
299+
return logFilePaths, nil
253300
}
254301

255302
func BuildImageOnce(once *sync.Once, dctx, dfile, nameAndTag string) error {
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package examples
2+
3+
import (
4+
"github.com/smartcontractkit/chainlink-testing-framework/framework/rpc"
5+
"github.com/stretchr/testify/require"
6+
"os"
7+
"testing"
8+
"time"
9+
)
10+
11+
func TestBlockchainChaos(t *testing.T) {
12+
srcChain := os.Getenv("CTF_CHAOS_SRC_CHAIN_RPC_HTTP_URL")
13+
require.NotEmpty(t, srcChain, "source chain RPC must be set")
14+
dstChain := os.Getenv("CTF_CHAOS_DST_CHAIN_RPC_HTTP_URL")
15+
require.NotEmpty(t, dstChain, "destination chain RPC must be set")
16+
17+
recoveryIntervalDuration := 120 * time.Second
18+
19+
testCases := []struct {
20+
name string
21+
chainURL string
22+
reorgDepth int
23+
}{
24+
{
25+
name: "Reorg src with depth: 1",
26+
chainURL: srcChain,
27+
reorgDepth: 1,
28+
},
29+
{
30+
name: "Reorg dst with depth: 1",
31+
chainURL: dstChain,
32+
reorgDepth: 1,
33+
},
34+
{
35+
name: "Reorg src with depth: 5",
36+
chainURL: srcChain,
37+
reorgDepth: 5,
38+
},
39+
{
40+
name: "Reorg dst with depth: 5",
41+
chainURL: dstChain,
42+
reorgDepth: 5,
43+
},
44+
}
45+
46+
// Run test cases
47+
for _, tc := range testCases {
48+
t.Run(tc.name, func(t *testing.T) {
49+
t.Log(tc.name)
50+
r := rpc.New(tc.chainURL, nil)
51+
err := r.GethSetHead(tc.reorgDepth)
52+
require.NoError(t, err)
53+
t.Logf("Awaiting chaos recovery: %s", tc.name)
54+
time.Sleep(recoveryIntervalDuration)
55+
56+
// Validate Chainlink product here, use load test assertions
57+
})
58+
}
59+
}

0 commit comments

Comments
 (0)