Compare commits

...
Author SHA1 Message Date
Mzack9999 41665ef78f drop sharding 2026-07-22 23:40:10 +04:00
Mzack9999 672730567e deflake durations 2026-07-22 22:25:39 +04:00
Mzack9999 e9f854161b Merge remote-tracking branch 'origin/dev' into ci-faster-checks 2026-07-22 22:21:58 +04:00
Mzack9999 c020a69f4e shard windows 2026-07-22 21:46:07 +04:00
Mzack9999 c8a98159ab slim release 2026-07-22 21:18:14 +04:00
Mzack9999 6594a4e9d9 drop sharding 2026-07-22 21:14:15 +04:00
Mzack9999 eddbded1d4 standard runners 2026-07-22 20:46:30 +04:00
Mzack9999 79ab2eff84 fix deflake 2026-07-22 20:09:35 +04:00
Mzack9999 4a59c20833 balance shards 2026-07-22 19:51:29 +04:00
Mzack9999 8dd8ee9f9e deflake clone 2026-07-22 19:48:55 +04:00
Mzack9999 2b04783ed1 shard integration 2026-07-22 19:27:31 +04:00
Mzack9999 7dbf468972 address review 2026-07-22 18:53:55 +04:00
Mzack9999 d2af651ca5 windows speedup 2026-07-22 18:45:42 +04:00
Mzack9999 e8c96ed4a0 drop families 2026-07-22 18:33:43 +04:00
Mzack9999 81a72c4f8a parallelize families 2026-07-22 17:47:34 +04:00
Mzack9999 28e8f47c6e parallelize ci 2026-07-22 16:54:44 +04:00
7 changed files with 131 additions and 22 deletions

No files matched your search

+48 -9
View File
@@ -54,6 +54,17 @@ jobs:
misc-packages: 'false'
docker-images: 'false'
tools-cache: 'false'
# Windows Defender's real-time scan of Go's many small build artifacts is a
# heavy tax on the runner; exclude the build/module caches, the workspace,
# and the Go toolchain processes so compile/test I/O isn't scanned.
- name: "Defender exclusions (Windows Go speedup)"
if: runner.os == 'Windows'
shell: pwsh
run: |
$paths = @("$(go env GOCACHE)", "$(go env GOMODCACHE)", "$env:GITHUB_WORKSPACE", "$env:RUNNER_TEMP")
Add-MpPreference -ExclusionPath $paths -ErrorAction SilentlyContinue
Add-MpPreference -ExclusionProcess "go.exe","compile.exe","link.exe","cgo.exe","gcc.exe","git.exe","nuclei.exe","nuclei.test.exe","python.exe" -ErrorAction SilentlyContinue
Write-Host "Defender exclusions applied (best-effort)"
- run: make build
# The data-race detector is OS-independent, so we only pay its ~2-3x cost
# on ubuntu; windows/macOS run the same suite without instrumentation.
@@ -85,7 +96,7 @@ jobs:
sdk:
name: "Run example SDK"
needs: ["tests"]
needs: ["lint"]
runs-on: ubuntu-latest
env:
GITHUB_TOKEN: "${{ github.token }}"
@@ -106,9 +117,12 @@ jobs:
integration:
name: "Integration tests"
needs: ["tests"]
needs: ["lint"]
env:
GITHUB_TOKEN: "${{ github.token }}"
# The suite semaphore defaults to min(GOMAXPROCS,4); integration cases are
# I/O-bound (loopback servers), so oversubscribe to speed the slow cells.
PARALLELISM: "8"
strategy:
fail-fast: false
matrix:
@@ -119,6 +133,14 @@ jobs:
- uses: projectdiscovery/actions/setup/python@v1
- uses: projectdiscovery/actions/setup/go@v1
- uses: projectdiscovery/nuclei-action/cache@v3
- name: "Defender exclusions (Windows Go speedup)"
if: runner.os == 'Windows'
shell: pwsh
run: |
$paths = @("$(go env GOCACHE)", "$(go env GOMODCACHE)", "$env:GITHUB_WORKSPACE", "$env:RUNNER_TEMP")
Add-MpPreference -ExclusionPath $paths -ErrorAction SilentlyContinue
Add-MpPreference -ExclusionProcess "go.exe","compile.exe","link.exe","cgo.exe","gcc.exe","git.exe","nuclei.exe","nuclei.test.exe","python.exe" -ErrorAction SilentlyContinue
Write-Host "Defender exclusions applied (best-effort)"
- run: make integration
env:
PDCP_API_KEY: "${{ secrets.PDCP_API_KEY }}"
@@ -126,9 +148,12 @@ jobs:
functional:
name: "Functional tests"
needs: ["tests"]
needs: ["lint"]
env:
GITHUB_TOKEN: "${{ github.token }}"
# Functional cases honor FUNCTIONAL_PARALLELISM/PARALLELISM (see
# functional_test.go), so oversubscribe like the integration job.
PARALLELISM: "8"
strategy:
fail-fast: false
matrix:
@@ -139,6 +164,14 @@ jobs:
- uses: projectdiscovery/actions/setup/python@v1
- uses: projectdiscovery/actions/setup/go@v1
- uses: projectdiscovery/nuclei-action/cache@v3
- name: "Defender exclusions (Windows Go speedup)"
if: runner.os == 'Windows'
shell: pwsh
run: |
$paths = @("$(go env GOCACHE)", "$(go env GOMODCACHE)", "$env:GITHUB_WORKSPACE", "$env:RUNNER_TEMP")
Add-MpPreference -ExclusionPath $paths -ErrorAction SilentlyContinue
Add-MpPreference -ExclusionProcess "go.exe","compile.exe","link.exe","cgo.exe","gcc.exe","git.exe","nuclei.exe","nuclei.test.exe","python.exe" -ErrorAction SilentlyContinue
Write-Host "Defender exclusions applied (best-effort)"
- uses: projectdiscovery/nuclei-action@v3
with:
version: latest
@@ -147,7 +180,7 @@ jobs:
validate:
name: "Template validate"
needs: ["tests"]
needs: ["lint"]
runs-on: ubuntu-latest
env:
GITHUB_TOKEN: "${{ github.token }}"
@@ -159,7 +192,7 @@ jobs:
codeql:
name: "CodeQL analysis"
needs: ["tests"]
needs: ["lint"]
runs-on: ubuntu-latest
permissions:
actions: read
@@ -201,21 +234,27 @@ jobs:
release:
name: "Release test"
needs: ["tests"]
needs: ["lint"]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: projectdiscovery/actions/setup/go@v1
with:
go-version: "stable"
- uses: projectdiscovery/actions/goreleaser@v1
# PR compile smoke only: `build` (no archives/docker) over one target per OS
# via the snapshot config — fast on a standard runner. The full multi-arch
# build + docker + PGO runs on tags via release.yaml.
- uses: goreleaser/goreleaser-action@v7
with:
version: "~> v2"
args: build --snapshot --clean --config .goreleaser.snapshot.yml
flamegraph:
name: "Flamegraph"
needs: ["tests"]
needs: ["lint"]
uses: ./.github/workflows/flamegraph.yaml
# perf-regression:
# name: "Performance regression"
# needs: ["tests"]
# needs: ["lint"]
# uses: ./.github/workflows/perf-regression.yaml
+24
View File
@@ -0,0 +1,24 @@
version: 2
# CI-only config for the PR "Release test". It's a fast goreleaser compile smoke
# over the main platforms (one per OS) that verifies the goreleaser build path
# and that nuclei still cross-compiles, without paying for all 8 targets + the
# multi-arch docker build on a standard runner. The authoritative full build
# (every goos/goarch + docker + PGO) is .goreleaser.yml and runs on tags via
# .github/workflows/release.yaml. Keep the build block in sync with that file.
builds:
- main: cmd/nuclei/main.go
binary: nuclei
id: nuclei-cli
env:
- CGO_ENABLED=0
- GOEXPERIMENT=greenteagc
targets:
- linux_amd64
- windows_amd64
- darwin_arm64
flags:
- -trimpath
ldflags:
- -s
- -w
+47 -9
View File
@@ -3,9 +3,11 @@ package customtemplates
import (
"bytes"
"context"
"os"
"path/filepath"
"strings"
"testing"
"time"
"github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/gologger/levels"
@@ -26,17 +28,53 @@ func TestDownloadCustomTemplatesFromGitHub(t *testing.T) {
options := testutils.DefaultOptions
options.GitHubTemplateRepo = []string{"projectdiscovery/nuclei-templates-test"}
// Authenticate the GitHub API repo lookup when a token is available (CI sets
// GITHUB_TOKEN). The unauthenticated API is capped at 60 req/hr per IP, which
// the shared CI egress exhausts and is the main source of this test's flake;
// an authenticated call gets 5000/hr and resolves reliably.
options.GitHubToken = os.Getenv("GITHUB_TOKEN")
ctm, err := NewCustomTemplatesManager(options)
require.Nil(t, err, "could not create custom templates manager")
clonedDir := filepath.Join(templatesDirectory, "github", "projectdiscovery", "nuclei-templates-test")
ctm.Download(context.Background())
// Check if output contains rate limit error and skip test if so
output := outputBuffer.String()
if strings.Contains(output, "API rate limit exceeded") {
t.Skip("GitHub API rate limit exceeded, skipping test")
// The lookup + clone hit the live repo, so a transient GitHub/network hiccup
// must not fail CI. Recreate the manager (re-resolving the repo) and retry a
// few times; if it still fails only because of a transient remote error,
// skip instead of failing. The captured log is NOT reset between attempts so
// a rate-limit/API error logged at manager creation is still detectable.
var cloned bool
for attempt := 1; attempt <= 3; attempt++ {
_ = os.RemoveAll(clonedDir) // force a real re-clone on retry
ctm, err := NewCustomTemplatesManager(options)
require.Nil(t, err, "could not create custom templates manager")
ctm.Download(context.Background())
if _, statErr := os.Stat(clonedDir); statErr == nil {
cloned = true
break
}
time.Sleep(time.Duration(attempt) * time.Second)
}
require.DirExists(t, filepath.Join(templatesDirectory, "github", "projectdiscovery", "nuclei-templates-test"), "cloned directory does not exists")
if !cloned && isTransientRemoteError(outputBuffer.String()) {
t.Skipf("skipping: transient GitHub error cloning test repo:\n%s", outputBuffer.String())
}
require.True(t, cloned, "cloned directory does not exist:\n%s", outputBuffer.String())
}
// isTransientRemoteError reports whether the captured log output indicates a
// transient network/GitHub failure (as opposed to a real regression), so the
// test can be skipped rather than failed on flaky CI networking.
func isTransientRemoteError(output string) bool {
lower := strings.ToLower(output)
for _, needle := range []string{
"api rate limit exceeded",
"timeout", "timed out", "deadline exceeded", "i/o timeout",
"connection reset", "connection refused", "no such host",
"tls handshake", "eof", "temporary failure", "dial tcp",
} {
if strings.Contains(lower, needle) {
return true
}
}
return false
}
@@ -98,7 +98,9 @@ func requireDurationField(t *testing.T, event output.InternalEvent, key string)
value, ok := event[key].(float64)
require.Truef(t, ok, "expected %s to be a float64 duration", key)
require.Greater(t, value, float64(0))
// A sub-millisecond lookup can measure as exactly 0 on low-resolution
// clocks (notably Windows), so only require a non-negative duration.
require.GreaterOrEqual(t, value, float64(0))
require.Less(t, value, float64(60))
}
+3 -1
View File
@@ -56,6 +56,8 @@ func requireDurationField(t *testing.T, event output.InternalEvent, key string)
value, ok := event[key].(float64)
require.Truef(t, ok, "expected %s to be a float64 duration", key)
require.Greater(t, value, float64(0))
// A sub-millisecond lookup can measure as exactly 0 on low-resolution
// clocks (notably Windows), so only require a non-negative duration.
require.GreaterOrEqual(t, value, float64(0))
require.Less(t, value, float64(60))
}
+3 -1
View File
@@ -396,6 +396,8 @@ func requireWebsocketDurationField(t *testing.T, event output.InternalEvent, key
value, ok := event[key].(float64)
require.Truef(t, ok, "expected %s to be a float64 duration", key)
require.Greater(t, value, float64(0))
// A sub-millisecond lookup can measure as exactly 0 on low-resolution
// clocks (notably Windows), so only require a non-negative duration.
require.GreaterOrEqual(t, value, float64(0))
require.Less(t, value, float64(60))
}
+3 -1
View File
@@ -119,6 +119,8 @@ func requireWhoisDurationField(t *testing.T, event output.InternalEvent, key str
value, ok := event[key].(float64)
require.Truef(t, ok, "expected %s to be a float64 duration", key)
require.Greater(t, value, float64(0))
// A sub-millisecond lookup can measure as exactly 0 on low-resolution
// clocks (notably Windows), so only require a non-negative duration.
require.GreaterOrEqual(t, value, float64(0))
require.Less(t, value, float64(60))
}