-
-
Notifications
You must be signed in to change notification settings - Fork 105
/
fixture.go
309 lines (283 loc) · 11.7 KB
/
fixture.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
package fixture
import (
"log"
"os"
"path/filepath"
"github.com/cucumber/messages-go/v10"
"github.com/git-town/git-town/v13/src/git"
"github.com/git-town/git-town/v13/src/git/gitdomain"
"github.com/git-town/git-town/v13/src/gohacks/cache"
"github.com/git-town/git-town/v13/src/gohacks/slice"
"github.com/git-town/git-town/v13/test/asserts"
"github.com/git-town/git-town/v13/test/commands"
"github.com/git-town/git-town/v13/test/datatable"
"github.com/git-town/git-town/v13/test/filesystem"
testgit "github.com/git-town/git-town/v13/test/git"
"github.com/git-town/git-town/v13/test/helpers"
"github.com/git-town/git-town/v13/test/subshell"
"github.com/git-town/git-town/v13/test/testruntime"
)
// Fixture is a complete Git environment for a Cucumber scenario.
type Fixture struct {
// CoworkerRepo is the optional Git repository that is locally checked out at the coworker machine.
CoworkerRepo *testruntime.TestRuntime `exhaustruct:"optional"`
// DevRepo is the Git repository that is locally checked out at the developer machine.
DevRepo testruntime.TestRuntime `exhaustruct:"optional"`
// Dir defines the local folder in which this Fixture is stored.
// This folder also acts as the HOME directory for tests using this Fixture.
// It contains the global Git configuration to use in this test.
Dir string
// OriginRepo is the Git repository that simulates the origin repo (on GitHub).
// If this value is nil, the current test setup has no origin.
OriginRepo *testruntime.TestRuntime `exhaustruct:"optional"`
// SecondWorktree is the directory that contains an additional workspace.
// If this value is nil, the current test setup has no additional workspace.
SecondWorktree *testruntime.TestRuntime `exhaustruct:"optional"`
// SubmoduleRepo is the Git repository that simulates an external repo used as a submodule.
// If this value is nil, the current test setup uses no submodules.
SubmoduleRepo *testruntime.TestRuntime `exhaustruct:"optional"`
// UpstreamRepo is the optional Git repository that contains the upstream for this environment.
UpstreamRepo *testruntime.TestRuntime `exhaustruct:"optional"`
}
// CloneFixture provides a Fixture instance in the given directory,
// containing a copy of the given Fixture.
func CloneFixture(original Fixture, dir string) Fixture {
filesystem.CopyDirectory(original.Dir, dir)
binDir := filepath.Join(dir, "bin")
originDir := filepath.Join(dir, gitdomain.OriginRemote.String())
originRepo := testruntime.New(originDir, dir, "")
developerDir := filepath.Join(dir, "developer")
devRepo := testruntime.New(developerDir, dir, binDir)
result := Fixture{
DevRepo: devRepo,
Dir: dir,
OriginRepo: &originRepo,
}
// Since we copied the files from the memoized directory,
// we have to set the "origin" remote to the copied origin repo here.
result.DevRepo.MustRun("git", "remote", "remove", gitdomain.OriginRemote.String())
result.DevRepo.AddRemote(gitdomain.OriginRemote, result.originRepoPath())
result.DevRepo.Fetch()
// and connect the main branches again
result.DevRepo.ConnectTrackingBranch(gitdomain.NewLocalBranchName("main"))
return result
}
// NewStandardFixture provides a Fixture in the given directory,
// fully populated as a standardized setup for scenarios.
//
// The origin repo has the initial branch checked out.
// Git repos cannot receive pushes of the currently checked out branch
// because that will change files in the current workspace.
// The tests don't use the initial branch.
func NewStandardFixture(dir string) Fixture {
// create the folder
// create the fixture
gitEnv := Fixture{Dir: dir}
// create the origin repo
err := os.MkdirAll(gitEnv.originRepoPath(), 0o744)
if err != nil {
log.Fatalf("cannot create directory %q: %v", gitEnv.originRepoPath(), err)
}
// initialize the repo in the folder
originRepo := testruntime.Initialize(gitEnv.originRepoPath(), gitEnv.Dir, gitEnv.binPath())
err = originRepo.RunMany([][]string{
{"git", "commit", "--allow-empty", "-m", "initial commit"},
{"git", "branch", "main", "initial"},
})
if err != nil {
log.Fatalf("cannot initialize origin directory at %q: %v", gitEnv.originRepoPath(), err)
}
gitEnv.OriginRepo = &originRepo
// clone the "developer" repo
gitEnv.DevRepo = testruntime.Clone(originRepo.TestRunner, gitEnv.developerRepoPath())
gitEnv.initializeWorkspace(&gitEnv.DevRepo)
gitEnv.DevRepo.RemoveUnnecessaryFiles()
gitEnv.OriginRepo.RemoveUnnecessaryFiles()
return gitEnv
}
// AddCoworkerRepo adds a coworker repository.
func (self *Fixture) AddCoworkerRepo() {
coworkerRepo := testruntime.Clone(self.OriginRepo.TestRunner, self.coworkerRepoPath())
self.CoworkerRepo = &coworkerRepo
self.initializeWorkspace(self.CoworkerRepo)
self.CoworkerRepo.Verbose = self.DevRepo.Verbose
}
func (self *Fixture) AddSecondWorktree(branch gitdomain.LocalBranchName) {
workTreePath := filepath.Join(self.Dir, "development_worktree")
self.DevRepo.AddWorktree(workTreePath, branch)
runner := subshell.TestRunner{
BinDir: self.DevRepo.BinDir,
Verbose: self.DevRepo.Verbose,
HomeDir: self.DevRepo.HomeDir,
WorkingDir: workTreePath,
}
backendCommands := git.BackendCommands{
Runner: &runner,
DryRun: false,
Config: self.DevRepo.Config,
CurrentBranchCache: &cache.LocalBranchWithPrevious{},
RemotesCache: &cache.Remotes{},
}
self.SecondWorktree = &testruntime.TestRuntime{
TestCommands: commands.TestCommands{
TestRunner: &runner,
BackendCommands: &backendCommands,
},
Backend: backendCommands,
}
}
// AddSubmodule adds a submodule repository.
func (self *Fixture) AddSubmoduleRepo() {
err := os.MkdirAll(self.submoduleRepoPath(), 0o744)
if err != nil {
log.Fatalf("cannot create directory %q: %v", self.submoduleRepoPath(), err)
}
submoduleRepo := testruntime.Initialize(self.submoduleRepoPath(), self.Dir, self.binPath())
submoduleRepo.MustRunMany([][]string{
{"git", "config", "--global", "protocol.file.allow", "always"},
{"git", "commit", "--allow-empty", "-m", "initial commit"},
})
self.SubmoduleRepo = &submoduleRepo
}
// AddUpstream adds an upstream repository.
func (self *Fixture) AddUpstream() {
repo := testruntime.Clone(self.DevRepo.TestRunner, filepath.Join(self.Dir, gitdomain.UpstreamRemote.String()))
self.UpstreamRepo = &repo
self.DevRepo.AddRemote(gitdomain.UpstreamRemote, self.UpstreamRepo.WorkingDir)
}
// Branches provides a tabular list of all branches in this Fixture.
func (self *Fixture) Branches() datatable.DataTable {
result := datatable.DataTable{}
result.AddRow("REPOSITORY", "BRANCHES")
mainBranch := self.DevRepo.Config.FullConfig.MainBranch
localBranches, err := self.DevRepo.LocalBranchesMainFirst(mainBranch)
asserts.NoError(err)
localBranches = localBranches.RemoveWorkspaceMarkers().Hoist(self.DevRepo.Config.FullConfig.MainBranch)
initialBranch := gitdomain.NewLocalBranchName("initial")
localBranches = slice.Remove(localBranches, initialBranch)
localBranchesJoined := localBranches.Join(", ")
if self.OriginRepo == nil {
result.AddRow("local", localBranchesJoined)
return result
}
originBranches, err := self.OriginRepo.LocalBranchesMainFirst(mainBranch)
asserts.NoError(err)
originBranches = slice.Remove(originBranches, initialBranch)
originBranchesJoined := originBranches.Join(", ")
if localBranchesJoined == originBranchesJoined {
result.AddRow("local, origin", localBranchesJoined)
} else {
result.AddRow("local", localBranchesJoined)
result.AddRow("origin", originBranchesJoined)
}
return result
}
// CommitTable provides a table for all commits in this Git environment containing only the given fields.
func (self Fixture) CommitTable(fields []string) datatable.DataTable {
builder := datatable.NewCommitTableBuilder()
localCommits := self.DevRepo.Commits(fields, gitdomain.NewLocalBranchName("main"))
builder.AddMany(localCommits, "local")
if self.CoworkerRepo != nil {
coworkerCommits := self.CoworkerRepo.Commits(fields, gitdomain.NewLocalBranchName("main"))
builder.AddMany(coworkerCommits, "coworker")
}
if self.OriginRepo != nil {
originCommits := self.OriginRepo.Commits(fields, gitdomain.NewLocalBranchName("main"))
builder.AddMany(originCommits, gitdomain.OriginRemote.String())
}
if self.UpstreamRepo != nil {
upstreamCommits := self.UpstreamRepo.Commits(fields, gitdomain.NewLocalBranchName("main"))
builder.AddMany(upstreamCommits, "upstream")
}
if self.SecondWorktree != nil {
secondWorktreeCommits := self.SecondWorktree.Commits(fields, gitdomain.NewLocalBranchName("main"))
builder.AddMany(secondWorktreeCommits, "worktree")
}
return builder.Table(fields)
}
// CreateCommits creates the commits described by the given Gherkin table in this Git repository.
func (self *Fixture) CreateCommits(commits []testgit.Commit) {
for _, commit := range commits {
for _, location := range commit.Locations {
switch location {
case "coworker":
self.CoworkerRepo.CreateCommit(commit)
case "local":
self.DevRepo.CreateCommit(commit)
case "local, origin":
self.DevRepo.CreateCommit(commit)
self.DevRepo.PushBranch()
case "origin":
self.OriginRepo.CreateCommit(commit)
case "upstream":
self.UpstreamRepo.CreateCommit(commit)
default:
log.Fatalf("unknown commit location %q", commit.Locations)
}
}
}
// after setting up the commits, check out the "initial" branch in the origin repo so that we can git-push to it.
if self.OriginRepo != nil {
self.OriginRepo.CheckoutBranch(gitdomain.NewLocalBranchName("initial"))
}
}
// CreateTags creates tags from the given gherkin table.
func (self Fixture) CreateTags(table *messages.PickleStepArgument_PickleTable) {
columnNames := helpers.TableFields(table)
if columnNames[0] != "NAME" && columnNames[1] != "LOCATION" {
log.Fatalf("tag table must have columns NAME and LOCATION")
}
for _, row := range table.Rows[1:] {
name := row.Cells[0].Value
location := row.Cells[1].Value
switch location {
case "local":
self.DevRepo.CreateTag(name)
case "origin":
self.OriginRepo.CreateTag(name)
default:
log.Fatalf("tag table LOCATION must be 'local' or 'origin'")
}
}
}
// TagTable provides a table for all tags in this Git environment.
func (self Fixture) TagTable() datatable.DataTable {
builder := datatable.NewTagTableBuilder()
localTags := self.DevRepo.Tags()
builder.AddMany(localTags, "local")
if self.OriginRepo != nil {
originTags := self.OriginRepo.Tags()
builder.AddMany(originTags, gitdomain.OriginRemote.String())
}
return builder.Table()
}
// binPath provides the full path of the folder containing the test tools for this Fixture.
func (self *Fixture) binPath() string {
return filepath.Join(self.Dir, "bin")
}
// coworkerRepoPath provides the full path to the Git repository with the given name.
func (self Fixture) coworkerRepoPath() string {
return filepath.Join(self.Dir, "coworker")
}
// developerRepoPath provides the full path to the Git repository with the given name.
func (self Fixture) developerRepoPath() string {
return filepath.Join(self.Dir, "developer")
}
func (self Fixture) initializeWorkspace(repo *testruntime.TestRuntime) {
asserts.NoError(repo.Config.SetMainBranch(gitdomain.NewLocalBranchName("main")))
asserts.NoError(repo.Config.SetPerennialBranches(gitdomain.LocalBranchNames{}))
repo.MustRunMany([][]string{
{"git", "checkout", "main"},
// NOTE: the developer repos receives the initial branch from origin
// but we don't want it here because it isn't used in tests.
{"git", "branch", "-d", "initial"},
})
}
// originRepoPath provides the full path to the Git repository with the given name.
func (self Fixture) originRepoPath() string {
return filepath.Join(self.Dir, gitdomain.OriginRemote.String())
}
// submoduleRepoPath provides the full path to the Git repository with the given name.
func (self Fixture) submoduleRepoPath() string {
return filepath.Join(self.Dir, "submodule")
}