Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
projectdiscovery
GitHub Repository: projectdiscovery/nuclei
Path: blob/dev/cmd/integration-test/integration-test.go
2843 views
1
package main
2
3
import (
4
"flag"
5
"fmt"
6
"os"
7
"regexp"
8
"runtime"
9
"slices"
10
"strings"
11
12
"github.com/kitabisa/go-ci"
13
"github.com/logrusorgru/aurora"
14
15
"github.com/projectdiscovery/gologger"
16
"github.com/projectdiscovery/nuclei/v3/pkg/testutils"
17
"github.com/projectdiscovery/nuclei/v3/pkg/testutils/fuzzplayground"
18
sliceutil "github.com/projectdiscovery/utils/slice"
19
)
20
21
type TestCaseInfo struct {
22
Path string
23
TestCase testutils.TestCase
24
DisableOn func() bool
25
}
26
27
var (
28
debug = isDebugMode()
29
customTests = os.Getenv("TESTS")
30
protocol = os.Getenv("PROTO")
31
32
success = aurora.Green("[✓]").String()
33
failed = aurora.Red("[✘]").String()
34
35
protocolTests = map[string][]TestCaseInfo{
36
"http": httpTestcases,
37
"interactsh": interactshTestCases,
38
"network": networkTestcases,
39
"dns": dnsTestCases,
40
"workflow": workflowTestcases,
41
"loader": loaderTestcases,
42
"profile-loader": profileLoaderTestcases,
43
"websocket": websocketTestCases,
44
"headless": headlessTestcases,
45
"whois": whoisTestCases,
46
"ssl": sslTestcases,
47
"library": libraryTestcases,
48
"templatesPath": templatesPathTestCases,
49
"templatesDir": templatesDirTestCases,
50
"env_vars": templatesDirEnvTestCases,
51
"file": fileTestcases,
52
"offlineHttp": offlineHttpTestcases,
53
"customConfigDir": customConfigDirTestCases,
54
"fuzzing": fuzzingTestCases,
55
"code": codeTestCases,
56
"multi": multiProtoTestcases,
57
"generic": genericTestcases,
58
"dsl": dslTestcases,
59
"flow": flowTestcases,
60
"javascript": jsTestcases,
61
"matcher-status": matcherStatusTestcases,
62
"exporters": exportersTestCases,
63
}
64
65
// flakyTests are run with a retry count of 3
66
flakyTests = map[string]bool{
67
"protocols/http/self-contained-file-input.yaml": true,
68
}
69
70
// For debug purposes
71
runProtocol = ""
72
runTemplate = ""
73
extraArgs = []string{}
74
interactshRetryCount = 3
75
)
76
77
func main() {
78
flag.StringVar(&runProtocol, "protocol", "", "run integration tests of given protocol")
79
flag.StringVar(&runTemplate, "template", "", "run integration test of given template")
80
flag.Parse()
81
82
// allows passing extra args to nuclei
83
eargs := os.Getenv("DebugExtraArgs")
84
if eargs != "" {
85
extraArgs = strings.Split(eargs, " ")
86
testutils.ExtraDebugArgs = extraArgs
87
}
88
89
if runProtocol != "" {
90
debugTests()
91
os.Exit(1)
92
}
93
94
// start fuzz playground server
95
server := fuzzplayground.GetPlaygroundServer()
96
defer func() {
97
fuzzplayground.Cleanup()
98
_ = server.Close()
99
}()
100
101
go func() {
102
if err := server.Start("localhost:8082"); err != nil {
103
if !strings.Contains(err.Error(), "Server closed") {
104
gologger.Fatal().Msgf("Could not start server: %s\n", err)
105
}
106
}
107
}()
108
109
customTestsList := normalizeSplit(customTests)
110
failedTestTemplatePaths := runTests(customTestsList)
111
112
if len(failedTestTemplatePaths) > 0 {
113
if ci.IsCI() {
114
// run failed tests again assuming they are flaky
115
// if they fail as well only then we assume that there is an actual issue
116
fmt.Println("::group::Running failed tests again")
117
failedTestTemplatePaths = runTests(failedTestTemplatePaths)
118
fmt.Println("::endgroup::")
119
120
if len(failedTestTemplatePaths) > 0 {
121
debug = true
122
fmt.Println("::group::Failed integration tests in debug mode")
123
_ = runTests(failedTestTemplatePaths)
124
fmt.Println("::endgroup::")
125
} else {
126
fmt.Println("::group::All tests passed")
127
fmt.Println("::endgroup::")
128
os.Exit(0)
129
}
130
}
131
132
os.Exit(1)
133
}
134
}
135
136
// isDebugMode checks if debug mode is enabled via any of the supported debug
137
// environment variables.
138
func isDebugMode() bool {
139
debugEnvVars := []string{
140
"DEBUG",
141
"ACTIONS_RUNNER_DEBUG", // GitHub Actions runner debug
142
// Add more debug environment variables here as needed
143
}
144
145
truthyValues := []string{"true", "1", "yes", "on", "enabled"}
146
147
for _, envVar := range debugEnvVars {
148
envValue := strings.ToLower(strings.TrimSpace(os.Getenv(envVar)))
149
if slices.Contains(truthyValues, envValue) {
150
return true
151
}
152
}
153
154
return false
155
}
156
157
// execute a testcase with retry and consider best of N
158
// intended for flaky tests like interactsh
159
func executeWithRetry(testCase testutils.TestCase, templatePath string, retryCount int) (string, error) {
160
var err error
161
for i := 0; i < retryCount; i++ {
162
err = testCase.Execute(templatePath)
163
if err == nil {
164
fmt.Printf("%s Test \"%s\" passed!\n", success, templatePath)
165
return "", nil
166
}
167
}
168
_, _ = fmt.Fprintf(os.Stderr, "%s Test \"%s\" failed after %v attempts : %s\n", failed, templatePath, retryCount, err)
169
return templatePath, err
170
}
171
172
func debugTests() {
173
testCaseInfos := protocolTests[runProtocol]
174
for _, testCaseInfo := range testCaseInfos {
175
if (runTemplate != "" && !strings.Contains(testCaseInfo.Path, runTemplate)) ||
176
(testCaseInfo.DisableOn != nil && testCaseInfo.DisableOn()) {
177
continue
178
}
179
if runProtocol == "interactsh" {
180
if _, err := executeWithRetry(testCaseInfo.TestCase, testCaseInfo.Path, interactshRetryCount); err != nil {
181
fmt.Printf("\n%v", err.Error())
182
}
183
} else {
184
if _, err := execute(testCaseInfo.TestCase, testCaseInfo.Path); err != nil {
185
fmt.Printf("\n%v", err.Error())
186
}
187
}
188
}
189
}
190
191
func runTests(customTemplatePaths []string) []string {
192
var failedTestTemplatePaths []string
193
194
for proto, testCaseInfos := range protocolTests {
195
if protocol != "" {
196
if !strings.EqualFold(proto, protocol) {
197
continue
198
}
199
}
200
if len(customTemplatePaths) == 0 {
201
fmt.Printf("Running test cases for %q protocol\n", aurora.Blue(proto))
202
}
203
for _, testCaseInfo := range testCaseInfos {
204
if testCaseInfo.DisableOn != nil && testCaseInfo.DisableOn() {
205
fmt.Printf("skipping test case %v. disabled on %v.\n", aurora.Blue(testCaseInfo.Path), runtime.GOOS)
206
continue
207
}
208
if len(customTemplatePaths) == 0 || sliceutil.Contains(customTemplatePaths, testCaseInfo.Path) {
209
var failedTemplatePath string
210
var err error
211
if proto == "interactsh" || strings.Contains(testCaseInfo.Path, "interactsh") {
212
failedTemplatePath, err = executeWithRetry(testCaseInfo.TestCase, testCaseInfo.Path, interactshRetryCount)
213
} else if flakyTests[testCaseInfo.Path] {
214
failedTemplatePath, err = executeWithRetry(testCaseInfo.TestCase, testCaseInfo.Path, interactshRetryCount)
215
} else {
216
failedTemplatePath, err = execute(testCaseInfo.TestCase, testCaseInfo.Path)
217
}
218
if err != nil {
219
failedTestTemplatePaths = append(failedTestTemplatePaths, failedTemplatePath)
220
}
221
}
222
}
223
}
224
225
return failedTestTemplatePaths
226
}
227
228
func execute(testCase testutils.TestCase, templatePath string) (string, error) {
229
if err := testCase.Execute(templatePath); err != nil {
230
_, _ = fmt.Fprintf(os.Stderr, "%s Test \"%s\" failed: %s\n", failed, templatePath, err)
231
return templatePath, err
232
}
233
234
fmt.Printf("%s Test \"%s\" passed!\n", success, templatePath)
235
return "", nil
236
}
237
238
func expectResultsCount(results []string, expectedNumbers ...int) error {
239
results = filterLines(results)
240
match := sliceutil.Contains(expectedNumbers, len(results))
241
if !match {
242
return fmt.Errorf("incorrect number of results: %d (actual) vs %v (expected) \nResults:\n\t%s\n", len(results), expectedNumbers, strings.Join(results, "\n\t")) // nolint:all
243
}
244
return nil
245
}
246
247
func normalizeSplit(str string) []string {
248
return strings.FieldsFunc(str, func(r rune) bool {
249
return r == ','
250
})
251
}
252
253
// filterLines applies all filtering functions to the results
254
func filterLines(results []string) []string {
255
results = filterHeadlessLogs(results)
256
results = filterUnsignedTemplatesWarnings(results)
257
return results
258
}
259
260
// if chromium is not installed go-rod installs it in .cache directory
261
// this function filters out the logs from download and installation
262
func filterHeadlessLogs(results []string) []string {
263
// [launcher.Browser] 2021/09/23 15:24:05 [launcher] [info] Starting browser
264
filtered := []string{}
265
for _, result := range results {
266
if strings.Contains(result, "[launcher.Browser]") {
267
continue
268
}
269
filtered = append(filtered, result)
270
}
271
return filtered
272
}
273
274
// filterUnsignedTemplatesWarnings filters out warning messages about unsigned templates
275
func filterUnsignedTemplatesWarnings(results []string) []string {
276
filtered := []string{}
277
unsignedTemplatesRegex := regexp.MustCompile(`Loading \d+ unsigned templates for scan\. Use with caution\.`)
278
for _, result := range results {
279
if unsignedTemplatesRegex.MatchString(result) {
280
continue
281
}
282
filtered = append(filtered, result)
283
}
284
return filtered
285
}
286
287