Path: blob/dev/cmd/integration-test/integration-test.go
2843 views
package main12import (3"flag"4"fmt"5"os"6"regexp"7"runtime"8"slices"9"strings"1011"github.com/kitabisa/go-ci"12"github.com/logrusorgru/aurora"1314"github.com/projectdiscovery/gologger"15"github.com/projectdiscovery/nuclei/v3/pkg/testutils"16"github.com/projectdiscovery/nuclei/v3/pkg/testutils/fuzzplayground"17sliceutil "github.com/projectdiscovery/utils/slice"18)1920type TestCaseInfo struct {21Path string22TestCase testutils.TestCase23DisableOn func() bool24}2526var (27debug = isDebugMode()28customTests = os.Getenv("TESTS")29protocol = os.Getenv("PROTO")3031success = aurora.Green("[✓]").String()32failed = aurora.Red("[✘]").String()3334protocolTests = map[string][]TestCaseInfo{35"http": httpTestcases,36"interactsh": interactshTestCases,37"network": networkTestcases,38"dns": dnsTestCases,39"workflow": workflowTestcases,40"loader": loaderTestcases,41"profile-loader": profileLoaderTestcases,42"websocket": websocketTestCases,43"headless": headlessTestcases,44"whois": whoisTestCases,45"ssl": sslTestcases,46"library": libraryTestcases,47"templatesPath": templatesPathTestCases,48"templatesDir": templatesDirTestCases,49"env_vars": templatesDirEnvTestCases,50"file": fileTestcases,51"offlineHttp": offlineHttpTestcases,52"customConfigDir": customConfigDirTestCases,53"fuzzing": fuzzingTestCases,54"code": codeTestCases,55"multi": multiProtoTestcases,56"generic": genericTestcases,57"dsl": dslTestcases,58"flow": flowTestcases,59"javascript": jsTestcases,60"matcher-status": matcherStatusTestcases,61"exporters": exportersTestCases,62}6364// flakyTests are run with a retry count of 365flakyTests = map[string]bool{66"protocols/http/self-contained-file-input.yaml": true,67}6869// For debug purposes70runProtocol = ""71runTemplate = ""72extraArgs = []string{}73interactshRetryCount = 374)7576func main() {77flag.StringVar(&runProtocol, "protocol", "", "run integration tests of given protocol")78flag.StringVar(&runTemplate, "template", "", "run integration test of given template")79flag.Parse()8081// allows passing extra args to nuclei82eargs := os.Getenv("DebugExtraArgs")83if eargs != "" {84extraArgs = strings.Split(eargs, " ")85testutils.ExtraDebugArgs = extraArgs86}8788if runProtocol != "" {89debugTests()90os.Exit(1)91}9293// start fuzz playground server94server := fuzzplayground.GetPlaygroundServer()95defer func() {96fuzzplayground.Cleanup()97_ = server.Close()98}()99100go func() {101if err := server.Start("localhost:8082"); err != nil {102if !strings.Contains(err.Error(), "Server closed") {103gologger.Fatal().Msgf("Could not start server: %s\n", err)104}105}106}()107108customTestsList := normalizeSplit(customTests)109failedTestTemplatePaths := runTests(customTestsList)110111if len(failedTestTemplatePaths) > 0 {112if ci.IsCI() {113// run failed tests again assuming they are flaky114// if they fail as well only then we assume that there is an actual issue115fmt.Println("::group::Running failed tests again")116failedTestTemplatePaths = runTests(failedTestTemplatePaths)117fmt.Println("::endgroup::")118119if len(failedTestTemplatePaths) > 0 {120debug = true121fmt.Println("::group::Failed integration tests in debug mode")122_ = runTests(failedTestTemplatePaths)123fmt.Println("::endgroup::")124} else {125fmt.Println("::group::All tests passed")126fmt.Println("::endgroup::")127os.Exit(0)128}129}130131os.Exit(1)132}133}134135// isDebugMode checks if debug mode is enabled via any of the supported debug136// environment variables.137func isDebugMode() bool {138debugEnvVars := []string{139"DEBUG",140"ACTIONS_RUNNER_DEBUG", // GitHub Actions runner debug141// Add more debug environment variables here as needed142}143144truthyValues := []string{"true", "1", "yes", "on", "enabled"}145146for _, envVar := range debugEnvVars {147envValue := strings.ToLower(strings.TrimSpace(os.Getenv(envVar)))148if slices.Contains(truthyValues, envValue) {149return true150}151}152153return false154}155156// execute a testcase with retry and consider best of N157// intended for flaky tests like interactsh158func executeWithRetry(testCase testutils.TestCase, templatePath string, retryCount int) (string, error) {159var err error160for i := 0; i < retryCount; i++ {161err = testCase.Execute(templatePath)162if err == nil {163fmt.Printf("%s Test \"%s\" passed!\n", success, templatePath)164return "", nil165}166}167_, _ = fmt.Fprintf(os.Stderr, "%s Test \"%s\" failed after %v attempts : %s\n", failed, templatePath, retryCount, err)168return templatePath, err169}170171func debugTests() {172testCaseInfos := protocolTests[runProtocol]173for _, testCaseInfo := range testCaseInfos {174if (runTemplate != "" && !strings.Contains(testCaseInfo.Path, runTemplate)) ||175(testCaseInfo.DisableOn != nil && testCaseInfo.DisableOn()) {176continue177}178if runProtocol == "interactsh" {179if _, err := executeWithRetry(testCaseInfo.TestCase, testCaseInfo.Path, interactshRetryCount); err != nil {180fmt.Printf("\n%v", err.Error())181}182} else {183if _, err := execute(testCaseInfo.TestCase, testCaseInfo.Path); err != nil {184fmt.Printf("\n%v", err.Error())185}186}187}188}189190func runTests(customTemplatePaths []string) []string {191var failedTestTemplatePaths []string192193for proto, testCaseInfos := range protocolTests {194if protocol != "" {195if !strings.EqualFold(proto, protocol) {196continue197}198}199if len(customTemplatePaths) == 0 {200fmt.Printf("Running test cases for %q protocol\n", aurora.Blue(proto))201}202for _, testCaseInfo := range testCaseInfos {203if testCaseInfo.DisableOn != nil && testCaseInfo.DisableOn() {204fmt.Printf("skipping test case %v. disabled on %v.\n", aurora.Blue(testCaseInfo.Path), runtime.GOOS)205continue206}207if len(customTemplatePaths) == 0 || sliceutil.Contains(customTemplatePaths, testCaseInfo.Path) {208var failedTemplatePath string209var err error210if proto == "interactsh" || strings.Contains(testCaseInfo.Path, "interactsh") {211failedTemplatePath, err = executeWithRetry(testCaseInfo.TestCase, testCaseInfo.Path, interactshRetryCount)212} else if flakyTests[testCaseInfo.Path] {213failedTemplatePath, err = executeWithRetry(testCaseInfo.TestCase, testCaseInfo.Path, interactshRetryCount)214} else {215failedTemplatePath, err = execute(testCaseInfo.TestCase, testCaseInfo.Path)216}217if err != nil {218failedTestTemplatePaths = append(failedTestTemplatePaths, failedTemplatePath)219}220}221}222}223224return failedTestTemplatePaths225}226227func execute(testCase testutils.TestCase, templatePath string) (string, error) {228if err := testCase.Execute(templatePath); err != nil {229_, _ = fmt.Fprintf(os.Stderr, "%s Test \"%s\" failed: %s\n", failed, templatePath, err)230return templatePath, err231}232233fmt.Printf("%s Test \"%s\" passed!\n", success, templatePath)234return "", nil235}236237func expectResultsCount(results []string, expectedNumbers ...int) error {238results = filterLines(results)239match := sliceutil.Contains(expectedNumbers, len(results))240if !match {241return 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:all242}243return nil244}245246func normalizeSplit(str string) []string {247return strings.FieldsFunc(str, func(r rune) bool {248return r == ','249})250}251252// filterLines applies all filtering functions to the results253func filterLines(results []string) []string {254results = filterHeadlessLogs(results)255results = filterUnsignedTemplatesWarnings(results)256return results257}258259// if chromium is not installed go-rod installs it in .cache directory260// this function filters out the logs from download and installation261func filterHeadlessLogs(results []string) []string {262// [launcher.Browser] 2021/09/23 15:24:05 [launcher] [info] Starting browser263filtered := []string{}264for _, result := range results {265if strings.Contains(result, "[launcher.Browser]") {266continue267}268filtered = append(filtered, result)269}270return filtered271}272273// filterUnsignedTemplatesWarnings filters out warning messages about unsigned templates274func filterUnsignedTemplatesWarnings(results []string) []string {275filtered := []string{}276unsignedTemplatesRegex := regexp.MustCompile(`Loading \d+ unsigned templates for scan\. Use with caution\.`)277for _, result := range results {278if unsignedTemplatesRegex.MatchString(result) {279continue280}281filtered = append(filtered, result)282}283return filtered284}285286287