Skip to content

Commit d6c876c

Browse files
mrnuggetLawnGnome
andauthored
Introduce executor.Coordinator and split up responsibilities between Coordinator/Service/executor (#536)
* Broken build with TaskStatusHubThing * Introduce Coordinator * Replace TaskStatusHubThing with TaskStatusCollection * Clean up the executor * Clean up handling of ExecutionOpts * Fix missing rename * Make executor property and status a runtime arg * Create separate tests for Coordinator * Fix tests for Coordinator * More test cleanup * Re-add missing test * Re-add transfromGroup test but its passing, what * Add test for transform group * Add test helpers * Backport fix from #539 * Update internal/batches/executor/coordinator.go Co-authored-by: Adam Harvey <[email protected]> Co-authored-by: Adam Harvey <[email protected]>
1 parent 8310871 commit d6c876c

File tree

11 files changed

+1314
-1015
lines changed

11 files changed

+1314
-1015
lines changed

cmd/src/batch_common.go

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -285,31 +285,27 @@ func executeBatchSpec(ctx context.Context, opts executeBatchSpecOpts) error {
285285
}
286286

287287
pending = batchCreatePending(opts.out, "Determining workspaces")
288-
tb, err := executor.NewTaskBuilder(batchSpec, svc)
289-
if err != nil {
290-
return errors.Wrap(err, "Parsing batch spec to determine workspaces")
291-
}
292-
tasks, err := tb.BuildAll(ctx, repos)
288+
tasks, err := svc.BuildTasks(ctx, repos, batchSpec)
293289
if err != nil {
294290
return err
295291
}
296292
batchCompletePending(pending, fmt.Sprintf("Found %d workspaces with steps to execute", len(tasks)))
297293

298294
// EXECUTION OF TASKS
299-
300-
svc.InitCache(opts.flags.cacheDir)
301-
svc.InitExecutor(ctx, executor.NewExecutorOpts{
295+
coord := svc.NewCoordinator(executor.NewCoordinatorOpts{
296+
Creator: workspaceCreator,
302297
CacheDir: opts.flags.cacheDir,
298+
ClearCache: opts.flags.clearCache,
299+
SkipErrors: opts.flags.skipErrors,
303300
CleanArchives: opts.flags.cleanArchives,
304-
Creator: workspaceCreator,
305301
Parallelism: opts.flags.parallelism,
306302
Timeout: opts.flags.timeout,
307303
KeepLogs: opts.flags.keepLogs,
308304
TempDir: opts.flags.tempDir,
309305
})
310306

311307
pending = batchCreatePending(opts.out, "Checking cache for changeset specs")
312-
uncachedTasks, cachedSpecs, err := svc.CheckCache(ctx, tasks, opts.flags.clearCache)
308+
uncachedTasks, cachedSpecs, err := coord.CheckCache(ctx, tasks)
313309
if err != nil {
314310
return err
315311
}
@@ -329,7 +325,7 @@ func executeBatchSpec(ctx context.Context, opts executeBatchSpecOpts) error {
329325
}
330326

331327
p := newBatchProgressPrinter(opts.out, *verbose, opts.flags.parallelism)
332-
freshSpecs, logFiles, err := svc.RunExecutor(ctx, uncachedTasks, batchSpec, p.PrintStatuses, opts.flags.skipErrors)
328+
freshSpecs, logFiles, err := coord.Execute(ctx, uncachedTasks, batchSpec, p.PrintStatuses)
333329
if err != nil && !opts.flags.skipErrors {
334330
return err
335331
}

cmd/src/batch_progress_printer.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -274,10 +274,6 @@ func taskStatusBarText(ts *executor.TaskStatus) (string, error) {
274274
statusText = "No changes"
275275
}
276276
}
277-
278-
if ts.Cached {
279-
statusText += " (cached)"
280-
}
281277
} else if ts.IsRunning() {
282278
if ts.CurrentlyExecuting != "" {
283279
lines := strings.Split(ts.CurrentlyExecuting, "\n")

internal/batches/executor/changeset_specs.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import (
99
"github.com/sourcegraph/src-cli/internal/batches"
1010
)
1111

12-
func createChangesetSpecs(task *Task, result executionResult, features batches.FeatureFlags) ([]*batches.ChangesetSpec, error) {
12+
func createChangesetSpecs(task *Task, result executionResult, autoAuthorDetails bool) ([]*batches.ChangesetSpec, error) {
1313
repo := task.Repository.Name
1414

1515
tmplCtx := &ChangesetTemplateContext{
@@ -26,7 +26,7 @@ func createChangesetSpecs(task *Task, result executionResult, features batches.F
2626
var authorEmail string
2727

2828
if task.Template.Commit.Author == nil {
29-
if features.IncludeAutoAuthorDetails {
29+
if autoAuthorDetails {
3030
// user did not provide author info, so use defaults
3131
authorName = "Sourcegraph"
3232
authorEmail = "[email protected]"
Lines changed: 313 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,313 @@
1+
package executor
2+
3+
import (
4+
"encoding/json"
5+
"testing"
6+
7+
"github.com/google/go-cmp/cmp"
8+
"github.com/sourcegraph/batch-change-utils/overridable"
9+
"github.com/sourcegraph/src-cli/internal/batches"
10+
"github.com/sourcegraph/src-cli/internal/batches/git"
11+
"github.com/sourcegraph/src-cli/internal/batches/graphql"
12+
)
13+
14+
func TestCreateChangesetSpecs(t *testing.T) {
15+
srcCLI := &graphql.Repository{
16+
ID: "src-cli",
17+
Name: "github.com/sourcegraph/src-cli",
18+
DefaultBranch: &graphql.Branch{Name: "main", Target: graphql.Target{OID: "d34db33f"}},
19+
}
20+
21+
defaultChangesetSpec := &batches.ChangesetSpec{
22+
BaseRepository: srcCLI.ID,
23+
CreatedChangeset: &batches.CreatedChangeset{
24+
BaseRef: srcCLI.DefaultBranch.Name,
25+
BaseRev: srcCLI.DefaultBranch.Target.OID,
26+
HeadRepository: srcCLI.ID,
27+
HeadRef: "refs/heads/my-branch",
28+
Title: "The title",
29+
Body: "The body",
30+
Commits: []batches.GitCommitDescription{
31+
{
32+
Message: "git commit message",
33+
Diff: "cool diff",
34+
AuthorName: "Sourcegraph",
35+
AuthorEmail: "[email protected]",
36+
},
37+
},
38+
Published: false,
39+
},
40+
}
41+
42+
specWith := func(s *batches.ChangesetSpec, f func(s *batches.ChangesetSpec)) *batches.ChangesetSpec {
43+
f(s)
44+
return s
45+
}
46+
47+
defaultTask := &Task{
48+
BatchChangeAttributes: &BatchChangeAttributes{
49+
Name: "the name",
50+
Description: "The description",
51+
},
52+
Template: &batches.ChangesetTemplate{
53+
Title: "The title",
54+
Body: "The body",
55+
Branch: "my-branch",
56+
Commit: batches.ExpandedGitCommitDescription{
57+
Message: "git commit message",
58+
},
59+
Published: parsePublishedFieldString(t, "false"),
60+
},
61+
Repository: srcCLI,
62+
}
63+
64+
taskWith := func(t *Task, f func(t *Task)) *Task {
65+
f(t)
66+
return t
67+
}
68+
69+
defaultResult := executionResult{
70+
Diff: "cool diff",
71+
ChangedFiles: &git.Changes{
72+
Modified: []string{"README.md"},
73+
},
74+
Outputs: map[string]interface{}{},
75+
}
76+
77+
tests := []struct {
78+
name string
79+
task *Task
80+
result executionResult
81+
82+
want []*batches.ChangesetSpec
83+
wantErr string
84+
}{
85+
{
86+
name: "success",
87+
task: defaultTask,
88+
result: defaultResult,
89+
want: []*batches.ChangesetSpec{
90+
defaultChangesetSpec,
91+
},
92+
wantErr: "",
93+
},
94+
{
95+
name: "publish by branch",
96+
task: taskWith(defaultTask, func(task *Task) {
97+
published := `[{"github.com/sourcegraph/*@my-branch": true}]`
98+
task.Template.Published = parsePublishedFieldString(t, published)
99+
}),
100+
result: defaultResult,
101+
want: []*batches.ChangesetSpec{
102+
specWith(defaultChangesetSpec, func(s *batches.ChangesetSpec) {
103+
s.Published = true
104+
}),
105+
},
106+
wantErr: "",
107+
},
108+
{
109+
name: "publish by branch not matching",
110+
task: taskWith(defaultTask, func(task *Task) {
111+
published := `[{"github.com/sourcegraph/*@another-branch-name": true}]`
112+
task.Template.Published = parsePublishedFieldString(t, published)
113+
}),
114+
result: defaultResult,
115+
want: []*batches.ChangesetSpec{
116+
specWith(defaultChangesetSpec, func(s *batches.ChangesetSpec) {
117+
s.Published = false
118+
}),
119+
},
120+
wantErr: "",
121+
},
122+
}
123+
124+
for _, tt := range tests {
125+
t.Run(tt.name, func(t *testing.T) {
126+
have, err := createChangesetSpecs(tt.task, tt.result, true)
127+
if err != nil {
128+
if tt.wantErr != "" {
129+
if err.Error() != tt.wantErr {
130+
t.Fatalf("wrong error. want=%q, got=%q", tt.wantErr, err.Error())
131+
}
132+
return
133+
} else {
134+
t.Fatalf("unexpected error: %s", err)
135+
}
136+
}
137+
138+
if !cmp.Equal(tt.want, have) {
139+
t.Errorf("mismatch (-want +got):\n%s", cmp.Diff(tt.want, have))
140+
}
141+
})
142+
}
143+
}
144+
145+
func TestGroupFileDiffs(t *testing.T) {
146+
diff1 := `diff --git 1/1.txt 1/1.txt
147+
new file mode 100644
148+
index 0000000..19d6416
149+
--- /dev/null
150+
+++ 1/1.txt
151+
@@ -0,0 +1,1 @@
152+
+this is 1
153+
`
154+
diff2 := `diff --git 1/2/2.txt 1/2/2.txt
155+
new file mode 100644
156+
index 0000000..c825d65
157+
--- /dev/null
158+
+++ 1/2/2.txt
159+
@@ -0,0 +1,1 @@
160+
+this is 2
161+
`
162+
diff3 := `diff --git 1/2/3/3.txt 1/2/3/3.txt
163+
new file mode 100644
164+
index 0000000..1bd79fb
165+
--- /dev/null
166+
+++ 1/2/3/3.txt
167+
@@ -0,0 +1,1 @@
168+
+this is 3
169+
`
170+
171+
defaultBranch := "my-default-branch"
172+
allDiffs := diff1 + diff2 + diff3
173+
174+
tests := []struct {
175+
diff string
176+
defaultBranch string
177+
groups []batches.Group
178+
want map[string]string
179+
}{
180+
{
181+
diff: allDiffs,
182+
groups: []batches.Group{
183+
{Directory: "1/2/3", Branch: "everything-in-3"},
184+
},
185+
want: map[string]string{
186+
"my-default-branch": diff1 + diff2,
187+
"everything-in-3": diff3,
188+
},
189+
},
190+
{
191+
diff: allDiffs,
192+
groups: []batches.Group{
193+
{Directory: "1/2", Branch: "everything-in-2-and-3"},
194+
},
195+
want: map[string]string{
196+
"my-default-branch": diff1,
197+
"everything-in-2-and-3": diff2 + diff3,
198+
},
199+
},
200+
{
201+
diff: allDiffs,
202+
groups: []batches.Group{
203+
{Directory: "1", Branch: "everything-in-1-and-2-and-3"},
204+
},
205+
want: map[string]string{
206+
"my-default-branch": "",
207+
"everything-in-1-and-2-and-3": diff1 + diff2 + diff3,
208+
},
209+
},
210+
{
211+
diff: allDiffs,
212+
groups: []batches.Group{
213+
// Each diff is matched against each directory, last match wins
214+
{Directory: "1", Branch: "only-in-1"},
215+
{Directory: "1/2", Branch: "only-in-2"},
216+
{Directory: "1/2/3", Branch: "only-in-3"},
217+
},
218+
want: map[string]string{
219+
"my-default-branch": "",
220+
"only-in-3": diff3,
221+
"only-in-2": diff2,
222+
"only-in-1": diff1,
223+
},
224+
},
225+
{
226+
diff: allDiffs,
227+
groups: []batches.Group{
228+
// Last one wins here, because it matches every diff
229+
{Directory: "1/2/3", Branch: "only-in-3"},
230+
{Directory: "1/2", Branch: "only-in-2"},
231+
{Directory: "1", Branch: "only-in-1"},
232+
},
233+
want: map[string]string{
234+
"my-default-branch": "",
235+
"only-in-1": diff1 + diff2 + diff3,
236+
},
237+
},
238+
{
239+
diff: allDiffs,
240+
groups: []batches.Group{
241+
{Directory: "", Branch: "everything"},
242+
},
243+
want: map[string]string{
244+
"my-default-branch": diff1 + diff2 + diff3,
245+
},
246+
},
247+
}
248+
249+
for _, tc := range tests {
250+
have, err := groupFileDiffs(tc.diff, defaultBranch, tc.groups)
251+
if err != nil {
252+
t.Fatalf("unexpected error: %s", err)
253+
}
254+
if !cmp.Equal(tc.want, have) {
255+
t.Errorf("mismatch (-want +got):\n%s", cmp.Diff(tc.want, have))
256+
}
257+
}
258+
}
259+
260+
func TestValidateGroups(t *testing.T) {
261+
repoName := "github.com/sourcegraph/src-cli"
262+
defaultBranch := "my-batch-change"
263+
264+
tests := []struct {
265+
defaultBranch string
266+
groups []batches.Group
267+
wantErr string
268+
}{
269+
{
270+
groups: []batches.Group{
271+
{Directory: "a", Branch: "my-batch-change-a"},
272+
{Directory: "b", Branch: "my-batch-change-b"},
273+
},
274+
wantErr: "",
275+
},
276+
{
277+
groups: []batches.Group{
278+
{Directory: "a", Branch: "my-batch-change-SAME"},
279+
{Directory: "b", Branch: "my-batch-change-SAME"},
280+
},
281+
wantErr: "transformChanges would lead to multiple changesets in repository github.com/sourcegraph/src-cli to have the same branch \"my-batch-change-SAME\"",
282+
},
283+
{
284+
groups: []batches.Group{
285+
{Directory: "a", Branch: "my-batch-change-SAME"},
286+
{Directory: "b", Branch: defaultBranch},
287+
},
288+
wantErr: "transformChanges group branch for repository github.com/sourcegraph/src-cli is the same as branch \"my-batch-change\" in changesetTemplate",
289+
},
290+
}
291+
292+
for _, tc := range tests {
293+
err := validateGroups(repoName, defaultBranch, tc.groups)
294+
var haveErr string
295+
if err != nil {
296+
haveErr = err.Error()
297+
}
298+
299+
if haveErr != tc.wantErr {
300+
t.Fatalf("wrong error:\nwant=%q\nhave=%q", tc.wantErr, haveErr)
301+
}
302+
}
303+
}
304+
305+
func parsePublishedFieldString(t *testing.T, input string) overridable.BoolOrString {
306+
t.Helper()
307+
308+
var result overridable.BoolOrString
309+
if err := json.Unmarshal([]byte(input), &result); err != nil {
310+
t.Fatalf("failed to parse %q as overridable.BoolOrString: %s", input, err)
311+
}
312+
return result
313+
}

0 commit comments

Comments
 (0)