2021-02-25 23:32:43 +05:30
package main
import (
2023-01-24 22:04:52 +05:30
"flag"
2021-02-25 23:32:43 +05:30
"fmt"
"os"
2023-07-28 18:50:57 +03:00
"runtime"
2021-02-25 23:32:43 +05:30
"strings"
2024-11-14 19:19:49 +07:00
"github.com/kitabisa/go-ci"
2021-02-25 23:32:43 +05:30
"github.com/logrusorgru/aurora"
2021-11-25 17:09:20 +02:00
2024-03-14 03:08:53 +05:30
"github.com/projectdiscovery/gologger"
2023-10-17 17:44:13 +05:30
"github.com/projectdiscovery/nuclei/v3/pkg/testutils"
2024-03-14 03:08:53 +05:30
"github.com/projectdiscovery/nuclei/v3/pkg/testutils/fuzzplayground"
2023-03-17 17:31:28 +01:00
sliceutil "github.com/projectdiscovery/utils/slice"
2021-02-25 23:32:43 +05:30
)
2023-07-28 18:50:57 +03:00
type TestCaseInfo struct {
Path string
TestCase testutils . TestCase
DisableOn func ( ) bool
}
2021-02-25 23:32:43 +05:30
var (
2024-11-14 19:19:49 +07:00
debug = os . Getenv ( "DEBUG" ) == "true"
customTests = os . Getenv ( "TESTS" )
protocol = os . Getenv ( "PROTO" )
2021-07-30 15:29:12 +05:30
2021-12-14 18:13:53 +02:00
success = aurora . Green ( "[✓]" ) . String ( )
failed = aurora . Red ( "[✘]" ) . String ( )
2021-02-25 23:32:43 +05:30
2023-07-28 18:50:57 +03:00
protocolTests = map [ string ] [ ] TestCaseInfo {
2023-03-17 16:56:14 +01:00
"http" : httpTestcases ,
2023-04-16 19:49:35 +02:00
"interactsh" : interactshTestCases ,
2023-03-17 16:56:14 +01:00
"network" : networkTestcases ,
"dns" : dnsTestCases ,
"workflow" : workflowTestcases ,
"loader" : loaderTestcases ,
2024-05-04 21:53:50 +05:30
"profile-loader" : profileLoaderTestcases ,
2023-03-17 16:56:14 +01:00
"websocket" : websocketTestCases ,
"headless" : headlessTestcases ,
"whois" : whoisTestCases ,
"ssl" : sslTestcases ,
2023-06-09 17:24:24 +02:00
"library" : libraryTestcases ,
2023-03-17 16:56:14 +01:00
"templatesPath" : templatesPathTestCases ,
"templatesDir" : templatesDirTestCases ,
"file" : fileTestcases ,
"offlineHttp" : offlineHttpTestcases ,
"customConfigDir" : customConfigDirTestCases ,
"fuzzing" : fuzzingTestCases ,
2023-06-09 17:24:24 +02:00
"code" : codeTestCases ,
2023-06-09 19:52:56 +05:30
"multi" : multiProtoTestcases ,
2023-06-26 14:15:12 +02:00
"generic" : genericTestcases ,
2023-07-28 21:04:02 +05:30
"dsl" : dslTestcases ,
2023-08-31 18:03:01 +05:30
"flow" : flowTestcases ,
2023-09-16 16:02:17 +05:30
"javascript" : jsTestcases ,
2024-08-28 13:57:43 +03:00
"matcher-status" : matcherStatusTestcases ,
2021-02-27 02:23:06 +05:30
}
2024-03-14 03:08:53 +05:30
// flakyTests are run with a retry count of 3
flakyTests = map [ string ] bool {
"protocols/http/self-contained-file-input.yaml" : true ,
}
2023-01-24 22:04:52 +05:30
// For debug purposes
2023-04-16 19:49:35 +02:00
runProtocol = ""
runTemplate = ""
extraArgs = [ ] string { }
interactshRetryCount = 3
2021-12-14 18:13:53 +02:00
)
2021-02-27 02:23:06 +05:30
2021-12-14 18:13:53 +02:00
func main ( ) {
2023-01-24 22:04:52 +05:30
flag . StringVar ( & runProtocol , "protocol" , "" , "run integration tests of given protocol" )
flag . StringVar ( & runTemplate , "template" , "" , "run integration test of given template" )
flag . Parse ( )
2023-02-01 17:23:28 +05:30
// allows passing extra args to nuclei
eargs := os . Getenv ( "DebugExtraArgs" )
if eargs != "" {
extraArgs = strings . Split ( eargs , " " )
testutils . ExtraDebugArgs = extraArgs
}
2023-01-24 22:04:52 +05:30
if runProtocol != "" {
debugTests ( )
os . Exit ( 1 )
}
2024-03-14 03:08:53 +05:30
// start fuzz playground server
defer fuzzplayground . Cleanup ( )
server := fuzzplayground . GetPlaygroundServer ( )
defer server . Close ( )
go func ( ) {
if err := server . Start ( "localhost:8082" ) ; err != nil {
if ! strings . Contains ( err . Error ( ) , "Server closed" ) {
gologger . Fatal ( ) . Msgf ( "Could not start server: %s\n" , err )
}
}
} ( )
2023-03-17 17:31:28 +01:00
customTestsList := normalizeSplit ( customTests )
failedTestTemplatePaths := runTests ( customTestsList )
2021-12-14 18:13:53 +02:00
if len ( failedTestTemplatePaths ) > 0 {
2024-11-14 19:19:49 +07:00
if ci . IsCI ( ) {
2024-10-25 23:35:03 +05:30
// run failed tests again assuming they are flaky
// if they fail as well only then we assume that there is an actual issue
fmt . Println ( "::group::Running failed tests again" )
failedTestTemplatePaths = runTests ( failedTestTemplatePaths )
2021-12-14 18:13:53 +02:00
fmt . Println ( "::endgroup::" )
2024-10-25 23:35:03 +05:30
if len ( failedTestTemplatePaths ) > 0 {
debug = true
fmt . Println ( "::group::Failed integration tests in debug mode" )
_ = runTests ( failedTestTemplatePaths )
fmt . Println ( "::endgroup::" )
2025-01-31 18:53:55 +05:30
} else {
fmt . Println ( "::group::All tests passed" )
fmt . Println ( "::endgroup::" )
os . Exit ( 0 )
2024-10-25 23:35:03 +05:30
}
2021-12-14 18:13:53 +02:00
}
os . Exit ( 1 )
}
}
2023-04-16 19:49:35 +02:00
// execute a testcase with retry and consider best of N
// intended for flaky tests like interactsh
func executeWithRetry ( testCase testutils . TestCase , templatePath string , retryCount int ) ( string , error ) {
var err error
for i := 0 ; i < retryCount ; i ++ {
err = testCase . Execute ( templatePath )
if err == nil {
fmt . Printf ( "%s Test \"%s\" passed!\n" , success , templatePath )
return "" , nil
}
}
_ , _ = fmt . Fprintf ( os . Stderr , "%s Test \"%s\" failed after %v attempts : %s\n" , failed , templatePath , retryCount , err )
return templatePath , err
}
2023-01-24 22:04:52 +05:30
func debugTests ( ) {
2023-07-28 18:50:57 +03:00
testCaseInfos := protocolTests [ runProtocol ]
for _ , testCaseInfo := range testCaseInfos {
if ( runTemplate != "" && ! strings . Contains ( testCaseInfo . Path , runTemplate ) ) ||
( testCaseInfo . DisableOn != nil && testCaseInfo . DisableOn ( ) ) {
2023-01-24 22:04:52 +05:30
continue
}
2023-04-16 19:49:35 +02:00
if runProtocol == "interactsh" {
2023-07-28 18:50:57 +03:00
if _ , err := executeWithRetry ( testCaseInfo . TestCase , testCaseInfo . Path , interactshRetryCount ) ; err != nil {
2023-04-16 19:49:35 +02:00
fmt . Printf ( "\n%v" , err . Error ( ) )
}
} else {
2023-07-28 18:50:57 +03:00
if _ , err := execute ( testCaseInfo . TestCase , testCaseInfo . Path ) ; err != nil {
2023-04-16 19:49:35 +02:00
fmt . Printf ( "\n%v" , err . Error ( ) )
}
2023-01-24 22:04:52 +05:30
}
}
}
2023-03-17 17:31:28 +01:00
func runTests ( customTemplatePaths [ ] string ) [ ] string {
var failedTestTemplatePaths [ ] string
2021-12-14 18:13:53 +02:00
2023-07-28 18:50:57 +03:00
for proto , testCaseInfos := range protocolTests {
2023-10-13 13:17:27 +05:30
if protocol != "" {
if ! strings . EqualFold ( proto , protocol ) {
continue
}
}
2021-12-14 18:13:53 +02:00
if len ( customTemplatePaths ) == 0 {
fmt . Printf ( "Running test cases for %q protocol\n" , aurora . Blue ( proto ) )
}
2023-07-28 18:50:57 +03:00
for _ , testCaseInfo := range testCaseInfos {
if testCaseInfo . DisableOn != nil && testCaseInfo . DisableOn ( ) {
fmt . Printf ( "skipping test case %v. disabled on %v.\n" , aurora . Blue ( testCaseInfo . Path ) , runtime . GOOS )
continue
}
if len ( customTemplatePaths ) == 0 || sliceutil . Contains ( customTemplatePaths , testCaseInfo . Path ) {
2023-04-16 19:49:35 +02:00
var failedTemplatePath string
var err error
2023-08-04 20:21:22 +05:30
if proto == "interactsh" || strings . Contains ( testCaseInfo . Path , "interactsh" ) {
2023-07-28 18:50:57 +03:00
failedTemplatePath , err = executeWithRetry ( testCaseInfo . TestCase , testCaseInfo . Path , interactshRetryCount )
2024-03-14 03:08:53 +05:30
} else if flakyTests [ testCaseInfo . Path ] {
failedTemplatePath , err = executeWithRetry ( testCaseInfo . TestCase , testCaseInfo . Path , interactshRetryCount )
2023-04-16 19:49:35 +02:00
} else {
2023-07-28 18:50:57 +03:00
failedTemplatePath , err = execute ( testCaseInfo . TestCase , testCaseInfo . Path )
2023-04-16 19:49:35 +02:00
}
if err != nil {
2023-03-17 17:31:28 +01:00
failedTestTemplatePaths = append ( failedTestTemplatePaths , failedTemplatePath )
2021-02-27 02:23:06 +05:30
}
}
2021-02-25 23:32:43 +05:30
}
}
2021-12-14 18:13:53 +02:00
return failedTestTemplatePaths
}
2022-10-13 11:05:10 -05:00
func execute ( testCase testutils . TestCase , templatePath string ) ( string , error ) {
2021-12-14 18:13:53 +02:00
if err := testCase . Execute ( templatePath ) ; err != nil {
_ , _ = fmt . Fprintf ( os . Stderr , "%s Test \"%s\" failed: %s\n" , failed , templatePath , err )
2022-10-13 11:05:10 -05:00
return templatePath , err
2021-07-30 15:29:12 +05:30
}
2021-12-14 18:13:53 +02:00
fmt . Printf ( "%s Test \"%s\" passed!\n" , success , templatePath )
2022-10-13 11:05:10 -05:00
return "" , nil
2021-02-25 23:32:43 +05:30
}
2023-04-16 19:49:35 +02:00
func expectResultsCount ( results [ ] string , expectedNumbers ... int ) error {
2023-11-18 16:25:37 +05:30
results = filterHeadlessLogs ( results )
2023-04-16 19:49:35 +02:00
match := sliceutil . Contains ( expectedNumbers , len ( results ) )
if ! match {
2023-10-13 13:17:27 +05:30
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
2021-12-15 16:03:57 +02:00
}
return nil
2021-02-25 23:32:43 +05:30
}
2021-12-14 18:13:53 +02:00
2023-03-17 17:31:28 +01:00
func normalizeSplit ( str string ) [ ] string {
2023-04-04 07:39:52 +05:30
return strings . FieldsFunc ( str , func ( r rune ) bool {
return r == ','
} )
2021-12-14 18:13:53 +02:00
}
2023-11-18 16:25:37 +05:30
// if chromium is not installed go-rod installs it in .cache directory
// this function filters out the logs from download and installation
func filterHeadlessLogs ( results [ ] string ) [ ] string {
// [launcher.Browser] 2021/09/23 15:24:05 [launcher] [info] Starting browser
filtered := [ ] string { }
for _ , result := range results {
if strings . Contains ( result , "[launcher.Browser]" ) {
continue
}
filtered = append ( filtered , result )
}
return filtered
}