diff --git a/.github/workflows/functional-test.yml b/.github/workflows/functional-test.yml new file mode 100644 index 00000000..027f3c7e --- /dev/null +++ b/.github/workflows/functional-test.yml @@ -0,0 +1,25 @@ +name: ๐Ÿงช Functional Test +on: + push: + pull_request: + workflow_dispatch: + + +jobs: + functional: + name: Functional Test + runs-on: ubuntu-latest + steps: + - name: Set up Go + uses: actions/setup-go@v2 + with: + go-version: 1.17 + + - name: Check out code + uses: actions/checkout@v2 + + - name: Functional Tests + run: | + chmod +x run.sh + bash run.sh + working-directory: cmd/functional-test diff --git a/.gitignore b/.gitignore index 3b6ed0bc..2390b4e6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,9 @@ cmd/httpx/httpx integration_tests/httpx -integration_tests/integration-test \ No newline at end of file +integration_tests/integration-test +cmd/functional-test/httpx_dev +cmd/functional-test/functional-test +cmd/functional-test/httpx +cmd/functional-test/*.cfg + diff --git a/cmd/functional-test/main.go b/cmd/functional-test/main.go new file mode 100644 index 00000000..52a3db30 --- /dev/null +++ b/cmd/functional-test/main.go @@ -0,0 +1,83 @@ +package main + +import ( + "bufio" + "flag" + "fmt" + "log" + "os" + "strings" + + "github.com/logrusorgru/aurora" + "github.com/pkg/errors" + + "github.com/projectdiscovery/httpx/internal/testutils" +) + +var ( + debug = os.Getenv("DEBUG") == "true" + success = aurora.Green("[โœ“]").String() + failed = aurora.Red("[โœ˜]").String() + errored = false + + mainHttpxBinary = flag.String("main", "", "Main Branch Httpx Binary") + devHttpxBinary = flag.String("dev", "", "Dev Branch Httpx Binary") + testcases = flag.String("testcases", "", "Test cases file for Httpx functional tests") +) + +func main() { + flag.Parse() + + if err := runFunctionalTests(); err != nil { + log.Fatalf("Could not run functional tests: %s\n", err) + } + if errored { + os.Exit(1) + } +} + +func runFunctionalTests() error { + file, err := os.Open(*testcases) + if err != nil { + return errors.Wrap(err, "could not open test cases") + } + defer file.Close() + + scanner := bufio.NewScanner(file) + for scanner.Scan() { + text := strings.TrimSpace(scanner.Text()) + if text == "" { + continue + } + if err := runIndividualTestCase(text); err != nil { + errored = true + fmt.Fprintf(os.Stderr, "%s Test \"%s\" failed: %s\n", failed, text, err) + } else { + fmt.Printf("%s Test \"%s\" passed!\n", success, text) + } + } + return nil +} + +func runIndividualTestCase(testcase string) error { + parts := strings.Fields(testcase) + + var finalArgs []string + var target string + if len(parts) > 1 { + finalArgs = parts[2:] + target = parts[0] + } + mainOutput, err := testutils.RunHttpxBinaryAndGetResults(target, *mainHttpxBinary, debug, finalArgs) + if err != nil { + return errors.Wrap(err, "could not run httpx main test") + } + devOutput, err := testutils.RunHttpxBinaryAndGetResults(target, *devHttpxBinary, debug, finalArgs) + if err != nil { + return errors.Wrap(err, "could not run httpx dev test") + } + if len(mainOutput) == len(devOutput) { + return nil + } + return fmt.Errorf("%s main is not equal to %s dev", mainOutput, devOutput) +} diff --git a/cmd/functional-test/run.sh b/cmd/functional-test/run.sh new file mode 100755 index 00000000..be5e8c7a --- /dev/null +++ b/cmd/functional-test/run.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +echo 'Building functional-test binary' +go build + +echo 'Building HTTPX binary from current branch' +go build -o httpx_dev ../httpx + +echo 'Installing latest release of HTTPX' +GO111MODULE=on go build -v github.com/projectdiscovery/httpx/cmd/httpx + +echo 'Starting HTTPX functional test' +./functional-test -main ./httpx -dev ./httpx_dev -testcases testcases.txt diff --git a/cmd/functional-test/test-data/raw-request.txt b/cmd/functional-test/test-data/raw-request.txt new file mode 100644 index 00000000..94b072c0 --- /dev/null +++ b/cmd/functional-test/test-data/raw-request.txt @@ -0,0 +1 @@ +GET /search?q=test HTTP/2 diff --git a/cmd/functional-test/test-data/request.txt b/cmd/functional-test/test-data/request.txt new file mode 100644 index 00000000..284d3f2c --- /dev/null +++ b/cmd/functional-test/test-data/request.txt @@ -0,0 +1 @@ +https://www.example.com diff --git a/cmd/functional-test/testcases.txt b/cmd/functional-test/testcases.txt new file mode 100644 index 00000000..e95bdc3a --- /dev/null +++ b/cmd/functional-test/testcases.txt @@ -0,0 +1,16 @@ +www.example.com {{binary}} -silent +www.example.com {{binary}} -silent -l test-data/request.txt +www.example.com {{binary}} -silent -request test-data/raw-request.txt +www.example.com {{binary}} -silent -title +www.example.com {{binary}} -silent -sc +www.example.com {{binary}} -silent -td +www.example.com {{binary}} -silent -probe +www.example.com {{binary}} -silent -no-fallback +www.example.com {{binary}} -silent -cl +www.example.com {{binary}} -silent -server +www.example.com {{binary}} -silent -ip +www.example.com {{binary}} -silent -tls-grab +www.example.com {{binary}} -silent -unsafe +www.example.com {{binary}} -silent -x all +www.example.com {{binary}} -silent -body 'a=b' +www.example.com {{binary}} -silent -exclude-cdn diff --git a/internal/testutils/integration.go b/internal/testutils/integration.go index 6f7b28b0..d058952d 100644 --- a/internal/testutils/integration.go +++ b/internal/testutils/integration.go @@ -4,6 +4,7 @@ import ( "os" "os/exec" "strings" + "fmt" ) // RunNucleiAndGetResults returns a list of results for a template @@ -33,6 +34,31 @@ func RunHttpxAndGetResults(url string, debug bool, extra ...string) ([]string, e } return parts, nil } +func RunHttpxBinaryAndGetResults(target string, httpxBinary string, debug bool, args []string) ([]string, error) { + cmd := exec.Command("bash", "-c") + cmdLine := fmt.Sprintf(`echo %s | %s `, target, httpxBinary) + cmdLine += strings.Join(args, " ") + if debug { + cmdLine += " -debug" + cmd.Stderr = os.Stderr + } else { + cmdLine += " -silent" + } + + cmd.Args = append(cmd.Args, cmdLine) + data, err := cmd.Output() + if err != nil { + return nil, err + } + parts := []string{} + items := strings.Split(string(data), "\n") + for _, i := range items { + if i != "" { + parts = append(parts, i) + } + } + return parts,nil +} // TestCase is a single integration test case type TestCase interface {