Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion pkg/cmd/tests/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,10 @@ func runTestsRun(cmd *cobra.Command, opts runOptions) error {
key: opts.splitKey,
output: splitOutputText,
}
splitOpts, err := resolveSplitOptions(cmd, splitOpts)
if err != nil {
return cancellationErrorOr(err)
}
splitRequested := runSplitRequested(splitOpts)
mode, candidates, selectedCandidates, splitResponse, err := selectRunCandidates(cmd, splitOpts)
if err != nil {
Expand Down Expand Up @@ -193,7 +197,7 @@ func selectRunCandidates(cmd *cobra.Command, opts splitOptions) (splitMode, []st
}

func runSplitRequested(opts splitOptions) bool {
return opts.index >= 0 || opts.total != 0
return opts.total > 1
}

func uploadAndSummarizeTestReports(cmd *cobra.Command, reportPaths []string, key string, baseline reportFileBaseline) error {
Expand Down
136 changes: 130 additions & 6 deletions pkg/cmd/tests/run_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,92 @@ func TestRunUsesDefaultsForOmittedKeys(t *testing.T) {
}
}

func TestRunUsesMatrixEnvironmentDefaults(t *testing.T) {
resetTestHooks(t)
t.Setenv("DEPOT_MATRIX_JOB_INDEX", "1")
t.Setenv("DEPOT_MATRIX_JOB_TOTAL", "2")
workspace := t.TempDir()
t.Chdir(workspace)
writeTempFileAt(t, workspace, "reports/junit.xml", "<testsuite/>")

var splitReq *testresultsv1.SplitTestsRequest
resolveOIDCCredentialFunc = func(context.Context) (string, error) { return "oidc-token", nil }
splitTestsFunc = func(_ context.Context, _ string, req *testresultsv1.SplitTestsRequest) (*testresultsv1.SplitTestsResponse, error) {
splitReq = req
return &testresultsv1.SplitTestsResponse{
Candidates: []string{"b.test.ts"},
CandidatesWithTimings: 1,
}, nil
}
var commandCandidates []string
runShellCommandFunc = func(_ context.Context, _ string, candidates []string, _ io.Writer, _ io.Writer) (int, error) {
commandCandidates = append([]string(nil), candidates...)
writeRunReport(t, workspace)
return 0, nil
}
reportTestResultsFunc = func(context.Context, string, *testresultsv1.ReportTestResultsRequest) (*testresultsv1.ReportTestResultsResponse, error) {
return &testresultsv1.ReportTestResultsResponse{FilesProcessed: 1}, nil
}

_, _, err := executeCommandWithInputOutput(
"a.test.ts\nb.test.ts\n",
"run",
"--command", "test-command",
"--report-path", "reports/junit.xml",
)
if err != nil {
t.Fatal(err)
}
if splitReq == nil || splitReq.GetShardIndex() != 1 || splitReq.GetShardTotal() != 2 {
t.Fatalf("expected matrix shard 1/2, got %#v", splitReq)
}
if !equalStrings(commandCandidates, []string{"b.test.ts"}) {
t.Fatalf("expected selected candidates on command stdin, got %v", commandCandidates)
}
}

func TestRunMatrixSingleShardPreservesUnsplitBehavior(t *testing.T) {
resetTestHooks(t)
t.Setenv("DEPOT_MATRIX_JOB_INDEX", "0")
t.Setenv("DEPOT_MATRIX_JOB_TOTAL", "1")
workspace := t.TempDir()
t.Chdir(workspace)
writeTempFileAt(t, workspace, "reports/junit.xml", "<testsuite/>")
resolveOIDCCredentialFunc = func(context.Context) (string, error) { return "oidc-token", nil }
splitTestsFunc = func(context.Context, string, *testresultsv1.SplitTestsRequest) (*testresultsv1.SplitTestsResponse, error) {
t.Fatal("single-shard run should not call SplitTests")
return nil, nil
}

var commandRan bool
runShellCommandFunc = func(_ context.Context, _ string, candidates []string, _ io.Writer, _ io.Writer) (int, error) {
if candidates != nil {
t.Fatalf("expected no command candidates, got %v", candidates)
}
commandRan = true
writeRunReport(t, workspace)
return 0, nil
}
reportTestResultsFunc = func(context.Context, string, *testresultsv1.ReportTestResultsRequest) (*testresultsv1.ReportTestResultsResponse, error) {
return &testresultsv1.ReportTestResultsResponse{FilesProcessed: 1}, nil
}

_, stderr, err := executeCommandOutput(
"run",
"--command", "test-command",
"--report-path", "reports/junit.xml",
)
if err != nil {
t.Fatal(err)
}
if !commandRan {
t.Fatal("expected test command to run")
}
if !strings.Contains(stderr, "Depot is running against a single shard.") {
t.Fatalf("expected single-shard summary, got %q", stderr)
}
}

func TestRunWithoutShardFlagsRunsAllCandidatesWithoutSplitting(t *testing.T) {
resetTestHooks(t)
workspace := t.TempDir()
Expand Down Expand Up @@ -1034,22 +1120,60 @@ func TestRunRejectsUnsupportedTimingIdentityPair(t *testing.T) {
}
}

func TestRunRequiresCandidatesWhenStdinIsTerminal(t *testing.T) {
func TestRunTotalOneDoesNotRequireCandidatesWhenStdinIsTerminal(t *testing.T) {
resetTestHooks(t)
runShellCommandFunc = func(context.Context, string, []string, io.Writer, io.Writer) (int, error) {
t.Fatal("command should not run")
workspace := t.TempDir()
t.Chdir(workspace)
writeTempFileAt(t, workspace, "reports/junit.xml", "<testsuite/>")
splitTestsFunc = func(context.Context, string, *testresultsv1.SplitTestsRequest) (*testresultsv1.SplitTestsResponse, error) {
t.Fatal("single-shard run should not call SplitTests")
return nil, nil
}
resolveOIDCCredentialFunc = func(context.Context) (string, error) { return "oidc-token", nil }

var commandRan bool
runShellCommandFunc = func(_ context.Context, _ string, candidates []string, _ io.Writer, _ io.Writer) (int, error) {
if candidates != nil {
t.Fatalf("expected no command candidates, got %v", candidates)
}
commandRan = true
writeRunReport(t, workspace)
return 0, nil
}
reportTestResultsFunc = func(context.Context, string, *testresultsv1.ReportTestResultsRequest) (*testresultsv1.ReportTestResultsResponse, error) {
return &testresultsv1.ReportTestResultsResponse{FilesProcessed: 1}, nil
}

_, _, err := executeCommandOutput(
"run",
"--index", "0",
"--total", "1",
"--command", "test-command",
"--report-path", "junit.xml",
"--report-path", "reports/junit.xml",
)
if err == nil || !strings.Contains(err.Error(), "pipe newline-delimited candidates") || !strings.Contains(err.Error(), "--candidates-file") {
t.Fatalf("expected missing candidates error, got %v", err)
if err != nil {
t.Fatal(err)
}
if !commandRan {
t.Fatal("expected test command to run")
}
}

func TestRunSplitRequested(t *testing.T) {
for _, tt := range []struct {
name string
opts splitOptions
want bool
}{
{name: "no shard configuration", opts: splitOptions{index: -1}, want: false},
{name: "single shard", opts: splitOptions{index: 0, total: 1}, want: false},
{name: "multiple shards", opts: splitOptions{index: 0, total: 2}, want: true},
} {
t.Run(tt.name, func(t *testing.T) {
if got := runSplitRequested(tt.opts); got != tt.want {
t.Fatalf("runSplitRequested(%+v) = %t, want %t", tt.opts, got, tt.want)
}
})
}
}

Expand Down
72 changes: 72 additions & 0 deletions pkg/cmd/tests/selection.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"os"
"path/filepath"
"strconv"
"strings"
"time"

Expand All @@ -21,6 +22,77 @@ var (

const splitTestsRequestTimeout = 2 * time.Minute

const (
matrixJobIndexEnv = "DEPOT_MATRIX_JOB_INDEX"
matrixJobTotalEnv = "DEPOT_MATRIX_JOB_TOTAL"
)

func resolveSplitOptions(cmd *cobra.Command, opts splitOptions) (splitOptions, error) {
flags := cmd.Flags()
indexExplicit := flags.Changed("index")
totalExplicit := flags.Changed("total")
explicitTotalOne := totalExplicit && opts.total == 1 && !indexExplicit
shardConfigured := indexExplicit || totalExplicit

indexFromEnv := false
if !indexExplicit && !explicitTotalOne {
index, set, err := matrixShardOption(matrixJobIndexEnv)
if err != nil {
return opts, err
}
if set {
opts.index = index
indexFromEnv = true
shardConfigured = true
}
}

totalFromEnv := false
if !totalExplicit {
total, set, err := matrixShardOption(matrixJobTotalEnv)
if err != nil {
return opts, err
}
if set {
opts.total = total
totalFromEnv = true
shardConfigured = true
}
}

if indexFromEnv && !totalExplicit && !totalFromEnv {
return opts, fmt.Errorf("%s must be set when %s is set", matrixJobTotalEnv, matrixJobIndexEnv)
}
if totalFromEnv && !indexExplicit && !indexFromEnv {
return opts, fmt.Errorf("%s must be set when %s is set", matrixJobIndexEnv, matrixJobTotalEnv)
}

if explicitTotalOne {
opts.index = 0
}
if !shardConfigured {
return opts, nil
}
if err := validateShard(opts.index, opts.total); err != nil {
return opts, err
}

return opts, nil
}

func matrixShardOption(name string) (int, bool, error) {
value, set := os.LookupEnv(name)
if !set {
return 0, false, nil
}

parsed, err := strconv.Atoi(strings.TrimSpace(value))
if err != nil {
return 0, true, fmt.Errorf("invalid %s value %q: must be an integer", name, value)
}
return parsed, true, nil
}

func selectTestCandidates(cmd *cobra.Command, opts splitOptions) (splitMode, []string, []string, *testresultsv1.SplitTestsResponse, error) {
if err := validateShard(opts.index, opts.total); err != nil {
return "", nil, nil, nil, err
Expand Down
Loading
Loading