-
Notifications
You must be signed in to change notification settings - Fork 1.6k
/
node_coverage_helper.go
162 lines (137 loc) · 5.43 KB
/
node_coverage_helper.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
package docker
import (
"context"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"sync"
"github.com/pkg/errors"
tc "github.com/testcontainers/testcontainers-go"
)
type NodeCoverageHelper struct {
Nodes []tc.Container
GoCoverSrcDir string // Path to the source directory on the chainlink image with go coverage data
NodeCoverageDirs []string // Paths to individual node coverage directories
CoverageDir string // Path to the base directory with all coverage
MergedDir string // Path to the directory where all coverage will be merged
ChainlinkDir string // Path to the root chainlink directory
}
func NewNodeCoverageHelper(ctx context.Context, nodes []tc.Container, chainlinkDir, coverageDir string) (*NodeCoverageHelper, error) {
coverSrcDir := os.Getenv("GO_COVERAGE_SRC_DIR")
if coverSrcDir == "" {
coverSrcDir = "/var/tmp/go-coverage" // Default path
}
helper := &NodeCoverageHelper{
Nodes: nodes,
GoCoverSrcDir: coverSrcDir,
CoverageDir: coverageDir,
MergedDir: filepath.Join(coverageDir, "merged"),
ChainlinkDir: chainlinkDir,
}
if err := os.MkdirAll(coverageDir, 0755); err != nil {
return nil, errors.Wrap(err, "failed to create base directory for node coverage")
}
// Copy coverage data from nodes
if err := helper.copyCoverageFromNodes(ctx); err != nil {
return nil, errors.Wrap(err, "failed to copy coverage from nodes during initialization")
}
// Merge the coverage data
if err := helper.mergeCoverage(); err != nil {
return nil, errors.Wrap(err, "failed to merge coverage data")
}
return helper, nil
}
func (c *NodeCoverageHelper) SaveMergedHTMLReport() (string, error) {
// Generate the textual coverage report
txtCommand := exec.Command("go", "tool", "covdata", "textfmt", "-i=.", "-o=cov.txt")
txtCommand.Dir = c.MergedDir
if txtOutput, err := txtCommand.CombinedOutput(); err != nil {
return "", errors.Wrapf(err, "failed to generate textual coverage report: %s", string(txtOutput))
}
// Generate the HTML coverage report
htmlFilePath := filepath.Join(c.CoverageDir, "coverage.html")
// #nosec G204
htmlCommand := exec.Command("go", "tool", "cover", "-html="+filepath.Join(c.MergedDir, "cov.txt"), "-o="+htmlFilePath)
htmlCommand.Dir = c.ChainlinkDir
if htmlOutput, err := htmlCommand.CombinedOutput(); err != nil {
return "", errors.Wrapf(err, "failed to generate HTML coverage report: %s", string(htmlOutput))
}
return htmlFilePath, nil
}
func (c *NodeCoverageHelper) SaveMergedCoveragePercentage() (string, error) {
filePath := filepath.Join(c.CoverageDir, "percentage.txt")
// Calculate coverage percentage from the merged data
percentCmd := exec.Command("go", "tool", "covdata", "percent", "-i=.")
percentCmd.Dir = c.MergedDir // Ensure the command runs in the directory with the merged data
output, err := percentCmd.CombinedOutput()
if err != nil {
return "", fmt.Errorf("failed to get merged coverage percentage report: %w, output: %s", err, string(output))
}
// Save the cmd output to a file
if err := os.WriteFile(filePath, output, 0600); err != nil {
return "", errors.Wrap(err, "failed to write coverage percentage to file")
}
return filePath, nil
}
func (c *NodeCoverageHelper) mergeCoverage() error {
if err := os.MkdirAll(c.MergedDir, 0755); err != nil {
return fmt.Errorf("failed to create merged directory: %w", err)
}
// Merge the coverage data from all chainlink nodes
dirInput := strings.Join(c.NodeCoverageDirs, ",")
// #nosec G204
mergeCmd := exec.Command("go", "tool", "covdata", "merge", "-o", c.MergedDir, "-i="+dirInput)
mergeCmd.Dir = filepath.Dir(c.MergedDir)
output, err := mergeCmd.CombinedOutput()
if err != nil {
return fmt.Errorf("error executing merge command: %w, output: %s", err, string(output))
}
// Remove the coverage dirs after merging
for _, dir := range c.NodeCoverageDirs {
if err := os.RemoveAll(dir); err != nil {
return fmt.Errorf("failed to remove directory %s: %w", dir, err)
}
}
c.NodeCoverageDirs = []string{} // Reset the coverage paths after merging
return nil
}
func (c *NodeCoverageHelper) copyCoverageFromNodes(ctx context.Context) error {
var wg sync.WaitGroup
errorsChan := make(chan error, len(c.Nodes))
for i, node := range c.Nodes {
wg.Add(1)
go func(n tc.Container, id int) {
defer wg.Done()
finalDestPath := filepath.Join(c.CoverageDir, fmt.Sprintf("node_%d", id))
if err := os.MkdirAll(finalDestPath, 0755); err != nil {
errorsChan <- fmt.Errorf("failed to create directory for node %d: %w", id, err)
return
}
err := copyFolderFromContainerUsingDockerCP(ctx, n.GetContainerID(), c.GoCoverSrcDir, finalDestPath)
if err != nil {
errorsChan <- fmt.Errorf("failed to copy folder from container for node %d: %w", id, err)
return
}
finalDestPath = filepath.Join(finalDestPath, "go-coverage") // Assuming path structure /var/tmp/go-coverage/TestName/node_X/go-coverage
c.NodeCoverageDirs = append(c.NodeCoverageDirs, finalDestPath)
}(node, i)
}
wg.Wait()
close(errorsChan)
for err := range errorsChan {
if err != nil {
return err
}
}
return nil
}
func copyFolderFromContainerUsingDockerCP(ctx context.Context, containerID, srcPath, destPath string) error {
source := fmt.Sprintf("%s:%s", containerID, srcPath)
cmd := exec.CommandContext(ctx, "docker", "cp", source, destPath)
if output, err := cmd.CombinedOutput(); err != nil {
return errors.Wrapf(err, "docker cp command failed: %s, output: %s", cmd, string(output))
}
return nil
}