Skip to content

Commit ba26ae7

Browse files
authored
Merge pull request #159943 from rickystewart/backport24.3-159863
release-24.3: ci: add `github-actions-extended-ci` workflow and `maybe_stress` job
2 parents 8037fcf + d6f6cde commit ba26ae7

File tree

15 files changed

+392
-10
lines changed

15 files changed

+392
-10
lines changed
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
name: GitHub Actions Extended CI
2+
on:
3+
pull_request:
4+
types: [opened, reopened, synchronize]
5+
branches:
6+
- "master"
7+
- "release-*"
8+
- "staging-*"
9+
- "!release-1.0*"
10+
- "!release-1.1*"
11+
- "!release-2.0*"
12+
- "!release-2.1*"
13+
- "!release-19.1*"
14+
- "!release-19.2*"
15+
- "!release-20.1*"
16+
- "!release-20.2*"
17+
- "!release-21.1*"
18+
- "!release-21.2*"
19+
- "!release-22.1*"
20+
- "!release-22.2*"
21+
- "!release-23.1*"
22+
- "!release-23.2*"
23+
- "!staging-v22.2*"
24+
- "!staging-v23.1*"
25+
- "!staging-v23.2*"
26+
concurrency:
27+
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
28+
cancel-in-progress: true
29+
jobs:
30+
maybe_stress:
31+
if: github.event.pull_request.draft == false
32+
runs-on: [self-hosted, ubuntu_2004]
33+
timeout-minutes: 180
34+
steps:
35+
- name: calculate commit depth
36+
run: echo COMMIT_DEPTH=$(echo '${{ github.event.pull_request.commits }} + 1' | bc) >> "$GITHUB_ENV"
37+
- uses: actions/checkout@v4
38+
with:
39+
ref: ${{ github.event.pull_request.head.sha || github.ref }}
40+
fetch-depth: ${{ env.COMMIT_DEPTH }}
41+
- name: Fetch the base commit
42+
run: git fetch origin ${{ github.event.pull_request.base.sha }} --depth 100
43+
- name: compute metadata
44+
run: echo GITHUB_ACTIONS_BRANCH=${{ github.event.pull_request.number || github.ref_name}} >> "$GITHUB_ENV"
45+
- run: ./build/github/get-engflow-keys.sh
46+
- run: ./build/github/prepare-summarize-build.sh
47+
- name: run tests
48+
run: ./build/github/maybe-stress.sh ${{ github.event.pull_request.base.sha }} ${{ github.event.pull_request.head.sha }}
49+
- name: upload build results
50+
run: ./build/github/summarize-build.sh bes.bin
51+
if: always()
52+
env:
53+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
54+
- name: clean up
55+
run: ./build/github/cleanup-engflow-keys.sh
56+
if: always()

build/github/maybe-stress.sh

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
#!/usr/bin/env bash
2+
3+
# Copyright 2025 The Cockroach Authors.
4+
#
5+
# Use of this software is governed by the CockroachDB Software License
6+
# included in the /LICENSE file.
7+
8+
set -euxo pipefail
9+
10+
# Usage: must provide a base SHA and a PR head SHA as arguments.
11+
12+
if [ -z "$1" ]
13+
then
14+
echo 'Usage: build.sh BASESHA HEADSHA'
15+
exit 1
16+
fi
17+
18+
if [ -z "$2" ]
19+
then
20+
echo 'Usage: build.sh BASESHA HEADSHA'
21+
exit 1
22+
fi
23+
24+
bazel build --config crosslinux //pkg/cmd/ci-stress \
25+
--jobs 50 $(./build/github/engflow-args.sh)
26+
27+
BASESHA="$1"
28+
HEADSHA="$2"
29+
30+
set +e
31+
MERGEBASE=$(git merge-base $BASESHA $HEADSHA)
32+
set -e
33+
if [ -z "$MERGEBASE" ]
34+
then
35+
echo 'Could not calculate merge base. You may have to rebase your in-progress PR.'
36+
exit 1
37+
fi
38+
39+
# NB: These jobs will run at a lower priority to ensure that the Essential CI
40+
# jobs (required to merge) will be minimally impacted.
41+
$(bazel info bazel-bin --config=crosslinux)/pkg/cmd/ci-stress/ci-stress_/ci-stress \
42+
$MERGEBASE --config crosslinux --jobs 100 \
43+
--remote_execution_priority=-1 \
44+
--remote_download_minimal \
45+
--bes_keywords ci-stress --config=use_ci_timeouts \
46+
--build_event_binary_file=bes.bin \
47+
$(./build/github/engflow-args.sh)

pkg/BUILD.bazel

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ ALL_TESTS = [
136136
"//pkg/cmd/bazci/githubpost:githubpost_test",
137137
"//pkg/cmd/bazci/testfilter:testfilter_test",
138138
"//pkg/cmd/bazci:bazci_lib_disallowed_imports_test",
139+
"//pkg/cmd/ci-stress:ci-stress_test",
139140
"//pkg/cmd/cmpconn:cmpconn_test",
140141
"//pkg/cmd/cockroach:cockroach_lib_disallowed_imports_test",
141142
"//pkg/cmd/dev:dev_lib_disallowed_imports_test",
@@ -1087,6 +1088,9 @@ GO_TARGETS = [
10871088
"//pkg/cmd/bazci/testfilter:testfilter_test",
10881089
"//pkg/cmd/bazci:bazci",
10891090
"//pkg/cmd/bazci:bazci_lib",
1091+
"//pkg/cmd/ci-stress:ci-stress",
1092+
"//pkg/cmd/ci-stress:ci-stress_lib",
1093+
"//pkg/cmd/ci-stress:ci-stress_test",
10901094
"//pkg/cmd/cloudupload:cloudupload",
10911095
"//pkg/cmd/cloudupload:cloudupload_lib",
10921096
"//pkg/cmd/cmdutil:cmdutil",

pkg/cmd/ci-stress/BUILD.bazel

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library", "go_test")
2+
3+
filegroup(
4+
name = "testdata",
5+
srcs = glob(["testdata/**"]),
6+
visibility = ["//pkg/cmd/github-pull-request-make:__pkg__"],
7+
)
8+
9+
go_library(
10+
name = "ci-stress_lib",
11+
srcs = ["main.go"],
12+
importpath = "github.com/cockroachdb/cockroach/pkg/cmd/ci-stress",
13+
visibility = ["//visibility:private"],
14+
deps = ["//pkg/util/buildutil"],
15+
)
16+
17+
go_binary(
18+
name = "ci-stress",
19+
embed = [":ci-stress_lib"],
20+
visibility = ["//visibility:public"],
21+
)
22+
23+
go_test(
24+
name = "ci-stress_test",
25+
srcs = ["main_test.go"],
26+
data = [":testdata"], # keep
27+
embed = [":ci-stress_lib"],
28+
deps = ["//pkg/testutils/datapathutils"],
29+
)

pkg/cmd/ci-stress/main.go

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
// Copyright 2025 The Cockroach Authors.
2+
//
3+
// Use of this software is governed by the CockroachDB Software License
4+
// included in the /LICENSE file.
5+
6+
package main
7+
8+
import (
9+
"context"
10+
"errors"
11+
"fmt"
12+
"math/rand/v2"
13+
"os"
14+
"os/exec"
15+
"path/filepath"
16+
"regexp"
17+
"slices"
18+
"strings"
19+
20+
"github.com/cockroachdb/cockroach/pkg/util/buildutil"
21+
)
22+
23+
// It is a Test if there is a character after Test that is not a lower-case letter.
24+
const goTestStr = `func (Test[^a-z]\w*)\(.*\*testing\.TB?\) {$`
25+
26+
var currentGoTestRE = regexp.MustCompile(`.*` + goTestStr)
27+
28+
// getDiff returns the output of `git diff` from the given baseRef to the
29+
// current `HEAD`.
30+
func getDiff(ctx context.Context, baseRef string) (string, error) {
31+
cmd := exec.CommandContext(ctx, "git", "diff", "--no-ext-diff", fmt.Sprintf("%s..HEAD", baseRef))
32+
outputBytes, err := cmd.Output()
33+
if err != nil {
34+
return "", fmt.Errorf("unable to get diff: git diff %s..HEAD [...]: %w", baseRef, err)
35+
}
36+
return strings.TrimSpace(string(outputBytes)), nil
37+
}
38+
39+
// getPkgToTests parses a git-style diff and returns a mapping from directories
40+
// to affected tests in those directories in the given diff.
41+
func getPkgToTests(diff string) map[string][]string {
42+
const newFilePrefix = "+++ b/"
43+
44+
ret := make(map[string][]string)
45+
46+
var curPkgName string
47+
48+
for _, line := range strings.Split(diff, "\n") {
49+
if strings.HasPrefix(line, newFilePrefix) {
50+
if strings.HasSuffix(line, ".go") {
51+
curPkgName = filepath.Dir(strings.TrimPrefix(line, newFilePrefix))
52+
} else {
53+
curPkgName = ""
54+
}
55+
} else if currentGoTestRE.MatchString(line) && curPkgName != "" {
56+
curTestName := ""
57+
if !strings.HasPrefix(line, "-") {
58+
curTestName = currentGoTestRE.ReplaceAllString(line, "$1")
59+
}
60+
if curPkgName != "" && curTestName != "" {
61+
ret[curPkgName] = append(ret[curPkgName], curTestName)
62+
}
63+
}
64+
}
65+
66+
// Sanity-check: Make sure there is a `BUILD.bazel` file in each pkg,
67+
// or else it's not a real Go test. (Could be testdata for a different
68+
// package, etc.) Don't do this in test builds however, as we would
69+
// never find those files.
70+
//
71+
// We also take the opportunity to limit stressing to a constant
72+
// number of packages in case this PR changed a ton of packages
73+
// (find-and-replace, bulk changes, etc.)
74+
if !buildutil.CrdbTestBuild {
75+
taken := 0
76+
for pkg := range ret {
77+
_, err := os.Stat(filepath.Join(pkg, "BUILD.bazel"))
78+
if taken >= 10 {
79+
delete(ret, pkg)
80+
continue
81+
}
82+
if err != nil && errors.Is(err, os.ErrNotExist) {
83+
fmt.Printf("skipping testing package %s as we could not find a BUILD.bazel file in that directory\n", pkg)
84+
delete(ret, pkg)
85+
continue
86+
} else if err != nil {
87+
panic(err)
88+
}
89+
taken += 1
90+
}
91+
}
92+
93+
for _, tests := range ret {
94+
slices.Sort(tests)
95+
}
96+
// De-duplicate.
97+
for pkg := range ret {
98+
ret[pkg] = slices.Compact(ret[pkg])
99+
}
100+
101+
// We arbitrarily limit the number of tests per package. We randomize
102+
// the tests selected.
103+
for pkg := range ret {
104+
const maxTests = 10
105+
tests := ret[pkg]
106+
if len(tests) > maxTests {
107+
rand.Shuffle(len(tests), func(i, j int) {
108+
tests[i], tests[j] = tests[j], tests[i]
109+
})
110+
tests = tests[:maxTests]
111+
slices.Sort(tests)
112+
ret[pkg] = tests
113+
}
114+
}
115+
116+
return ret
117+
}
118+
119+
func runTests(ctx context.Context, pkgToTests map[string][]string, extraBazelArgs []string) error {
120+
var testPackages []string
121+
for pkg := range pkgToTests {
122+
testPackages = append(testPackages, fmt.Sprintf("//%s:%s_test", pkg, filepath.Base(pkg)))
123+
}
124+
allTests := make(map[string]struct{})
125+
for _, tests := range pkgToTests {
126+
for _, test := range tests {
127+
allTests[test] = struct{}{}
128+
}
129+
}
130+
allTestsSlice := make([]string, 0, len(allTests))
131+
for test := range allTests {
132+
allTestsSlice = append(allTestsSlice, test)
133+
}
134+
slices.Sort(allTestsSlice)
135+
testFilter := strings.Join(allTestsSlice, "|")
136+
testFilter = "^(" + testFilter + ")$"
137+
// Run each test multiple times.
138+
bazelArgs := []string{"test", "--test_filter", testFilter, "--runs_per_test", "10"}
139+
bazelArgs = append(bazelArgs, testPackages...)
140+
bazelArgs = append(bazelArgs, extraBazelArgs...)
141+
fmt.Printf("running `bazel` with args %+v\n", bazelArgs)
142+
cmd := exec.CommandContext(ctx, "bazel", bazelArgs...)
143+
cmd.Stdout = os.Stdout
144+
cmd.Stderr = os.Stderr
145+
return cmd.Run()
146+
}
147+
148+
func mainImpl(baseRef string, bazelArgs []string) error {
149+
ctx := context.Background()
150+
diff, err := getDiff(ctx, baseRef)
151+
if err != nil {
152+
return err
153+
}
154+
pkgToTests := getPkgToTests(diff)
155+
return runTests(ctx, pkgToTests, bazelArgs)
156+
}
157+
158+
func main() {
159+
if len(os.Args) == 1 {
160+
panic("expected at least one argument (the ref of the base brach)")
161+
}
162+
baseRef := os.Args[1]
163+
bazelArgs := os.Args[2:]
164+
if err := mainImpl(baseRef, bazelArgs); err != nil {
165+
panic(err)
166+
}
167+
}

pkg/cmd/ci-stress/main_test.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// Copyright 2025 The Cockroach Authors.
2+
//
3+
// Use of this software is governed by the CockroachDB Software License
4+
// included in the /LICENSE file.
5+
6+
package main
7+
8+
import (
9+
"os"
10+
"reflect"
11+
"testing"
12+
13+
"github.com/cockroachdb/cockroach/pkg/testutils/datapathutils"
14+
)
15+
16+
func TestPkgToTests(t *testing.T) {
17+
for filename, expPkgs := range map[string]map[string][]string{
18+
datapathutils.TestDataPath(t, "skip.diff"): {
19+
"pkg/ccl/storageccl": []string{"TestPutS3"},
20+
},
21+
datapathutils.TestDataPath(t, "modified.diff"): {
22+
"pkg/ccl/crosscluster/physical": []string{"TestStreamingAutoReplan"},
23+
},
24+
datapathutils.TestDataPath(t, "removed.diff"): {},
25+
datapathutils.TestDataPath(t, "not_go.diff"): {},
26+
datapathutils.TestDataPath(t, "new_test.diff"): {
27+
"pkg/ccl/crosscluster/streamclient": []string{
28+
"TestExternalConnectionClient",
29+
"TestGetFirstActiveClientEmpty",
30+
},
31+
},
32+
datapathutils.TestDataPath(t, "27595.diff"): {
33+
"pkg/storage/closedts/container": []string{
34+
"TestTwoNodes",
35+
},
36+
"pkg/storage/closedts/minprop": []string{
37+
"TestTrackerConcurrentUse",
38+
},
39+
"pkg/storage/closedts/storage": []string{
40+
"TestConcurrent",
41+
},
42+
"pkg/storage/closedts/transport": []string{
43+
"TestTransportClientReceivesEntries",
44+
"TestTransportConnectOnRequest",
45+
},
46+
},
47+
} {
48+
t.Run(filename, func(t *testing.T) {
49+
content, err := os.ReadFile(filename)
50+
if err != nil {
51+
t.Fatal(err)
52+
}
53+
pkgToTests := getPkgToTests(string(content))
54+
if err != nil {
55+
t.Fatal(err)
56+
}
57+
if !reflect.DeepEqual(pkgToTests, expPkgs) {
58+
t.Errorf("expected %s, got %s", expPkgs, pkgToTests)
59+
}
60+
})
61+
}
62+
}
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.

0 commit comments

Comments
 (0)