Skip to content

Commit e951576

Browse files
committed
commontest: extract test helpers from daemon
The shared_test namespace as added in 92237dd (systemd: implement 'Create()' function, 2023-01-10) and a4d43a6 (launchd: implement 'Create()' function, 2023-01-10) are helpful for unit testing logic. However, there are multiple barriers from using this mocking infrastructure in other tests, such as in internal/common: 1. The 'shared_test' namespace does not allow sharing anything beyond the internal/daemon/ directory. Moving it into just internal/ does not seem to make this visible. The "_test" portion of the package tells Go to treat it with special properties. 2. After moving the definitions into internal/commontest/, the definitions are not visible to anyone else because all the names start with a lowercase letter. Convert these to uppercase so they will be visible. 3. By extracting to an external package, we suddenly would need to add "commontest." in front of all of the imported terms. Use a trick in the import statement ('. "package"') to allow skipping this prefix. Signed-off-by: Derrick Stolee <[email protected]>
1 parent 0391ae4 commit e951576

File tree

5 files changed

+149
-146
lines changed

5 files changed

+149
-146
lines changed

internal/daemon/launchd_test.go

Lines changed: 42 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"testing"
1111

1212
"github.com/github/git-bundle-server/internal/daemon"
13+
. "github.com/github/git-bundle-server/internal/testhelpers"
1314
"github.com/stretchr/testify/assert"
1415
"github.com/stretchr/testify/mock"
1516
)
@@ -19,14 +20,14 @@ var launchdCreateBehaviorTests = []struct {
1920

2021
// Inputs
2122
config *daemon.DaemonConfig
22-
force boolArg
23+
force BoolArg
2324

2425
// Mocked responses
25-
fileExists []pair[bool, error]
26+
fileExists []Pair[bool, error]
2627
writeFile []error
27-
launchctlPrint []pair[int, error]
28-
launchctlBootstrap []pair[int, error]
29-
launchctlBootout []pair[int, error]
28+
launchctlPrint []Pair[int, error]
29+
launchctlBootstrap []Pair[int, error]
30+
launchctlBootout []Pair[int, error]
3031

3132
// Expected values
3233
expectErr bool
@@ -35,66 +36,66 @@ var launchdCreateBehaviorTests = []struct {
3536
"Fresh config created if none exists",
3637
&basicDaemonConfig,
3738
Any,
38-
[]pair[bool, error]{newPair[bool, error](false, nil)}, // file exists
39+
[]Pair[bool, error]{NewPair[bool, error](false, nil)}, // file exists
3940
[]error{nil}, // write file
40-
[]pair[int, error]{newPair[int, error](daemon.LaunchdServiceNotFoundErrorCode, nil)}, // launchctl print (isBootstrapped)
41-
[]pair[int, error]{newPair[int, error](0, nil)}, // launchctl bootstrap
42-
[]pair[int, error]{}, // launchctl bootout
41+
[]Pair[int, error]{NewPair[int, error](daemon.LaunchdServiceNotFoundErrorCode, nil)}, // launchctl print (isBootstrapped)
42+
[]Pair[int, error]{NewPair[int, error](0, nil)}, // launchctl bootstrap
43+
[]Pair[int, error]{}, // launchctl bootout
4344
false,
4445
},
4546
{
4647
"Config exists & is not bootstrapped doesn't write file, bootstraps",
4748
&basicDaemonConfig,
4849
False,
49-
[]pair[bool, error]{newPair[bool, error](true, nil)}, // file exists
50+
[]Pair[bool, error]{NewPair[bool, error](true, nil)}, // file exists
5051
[]error{}, // write file
51-
[]pair[int, error]{newPair[int, error](daemon.LaunchdServiceNotFoundErrorCode, nil)}, // launchctl print (isBootstrapped)
52-
[]pair[int, error]{newPair[int, error](0, nil)}, // launchctl bootstrap
53-
[]pair[int, error]{}, // launchctl bootout
52+
[]Pair[int, error]{NewPair[int, error](daemon.LaunchdServiceNotFoundErrorCode, nil)}, // launchctl print (isBootstrapped)
53+
[]Pair[int, error]{NewPair[int, error](0, nil)}, // launchctl bootstrap
54+
[]Pair[int, error]{}, // launchctl bootout
5455
false,
5556
},
5657
{
5758
"'force' option overwrites file and bootstraps when not already bootstrapped",
5859
&basicDaemonConfig,
5960
True,
60-
[]pair[bool, error]{newPair[bool, error](true, nil)}, // file exists
61+
[]Pair[bool, error]{NewPair[bool, error](true, nil)}, // file exists
6162
[]error{nil}, // write file
62-
[]pair[int, error]{newPair[int, error](daemon.LaunchdServiceNotFoundErrorCode, nil)}, // launchctl print (isBootstrapped)
63-
[]pair[int, error]{newPair[int, error](0, nil)}, // launchctl bootstrap
64-
[]pair[int, error]{}, // launchctl bootout
63+
[]Pair[int, error]{NewPair[int, error](daemon.LaunchdServiceNotFoundErrorCode, nil)}, // launchctl print (isBootstrapped)
64+
[]Pair[int, error]{NewPair[int, error](0, nil)}, // launchctl bootstrap
65+
[]Pair[int, error]{}, // launchctl bootout
6566
false,
6667
},
6768
{
6869
"Config exists & already bootstrapped does nothing",
6970
&basicDaemonConfig,
7071
False,
71-
[]pair[bool, error]{newPair[bool, error](true, nil)}, // file exists
72+
[]Pair[bool, error]{NewPair[bool, error](true, nil)}, // file exists
7273
[]error{}, // write file
73-
[]pair[int, error]{newPair[int, error](0, nil)}, // launchctl print (isBootstrapped)
74-
[]pair[int, error]{}, // launchctl bootstrap
75-
[]pair[int, error]{}, // launchctl bootout
74+
[]Pair[int, error]{NewPair[int, error](0, nil)}, // launchctl print (isBootstrapped)
75+
[]Pair[int, error]{}, // launchctl bootstrap
76+
[]Pair[int, error]{}, // launchctl bootout
7677
false,
7778
},
7879
{
7980
"'force' option unloads config, overwrites file, and bootstraps",
8081
&basicDaemonConfig,
8182
True,
82-
[]pair[bool, error]{newPair[bool, error](true, nil)}, // file exists
83+
[]Pair[bool, error]{NewPair[bool, error](true, nil)}, // file exists
8384
[]error{nil}, // write file
84-
[]pair[int, error]{newPair[int, error](0, nil)}, // launchctl print (isBootstrapped)
85-
[]pair[int, error]{newPair[int, error](0, nil)}, // launchctl bootstrap
86-
[]pair[int, error]{newPair[int, error](0, nil)}, // launchctl bootout
85+
[]Pair[int, error]{NewPair[int, error](0, nil)}, // launchctl print (isBootstrapped)
86+
[]Pair[int, error]{NewPair[int, error](0, nil)}, // launchctl bootstrap
87+
[]Pair[int, error]{NewPair[int, error](0, nil)}, // launchctl bootout
8788
false,
8889
},
8990
{
9091
"Config missing & already bootstrapped throws error",
9192
&basicDaemonConfig,
9293
Any,
93-
[]pair[bool, error]{newPair[bool, error](false, nil)}, // file exists
94+
[]Pair[bool, error]{NewPair[bool, error](false, nil)}, // file exists
9495
[]error{}, // write file
95-
[]pair[int, error]{newPair[int, error](0, nil)}, // launchctl print (isBootstrapped)
96-
[]pair[int, error]{}, // launchctl bootstrap
97-
[]pair[int, error]{}, // launchctl bootout
96+
[]Pair[int, error]{NewPair[int, error](0, nil)}, // launchctl print (isBootstrapped)
97+
[]Pair[int, error]{}, // launchctl bootstrap
98+
[]Pair[int, error]{}, // launchctl bootout
9899
true,
99100
},
100101
}
@@ -220,43 +221,43 @@ func TestLaunchd_Create(t *testing.T) {
220221
Username: "testuser",
221222
HomeDir: "/my/test/dir",
222223
}
223-
testUserProvider := &mockUserProvider{}
224+
testUserProvider := &MockUserProvider{}
224225
testUserProvider.On("CurrentUser").Return(testUser, nil)
225226

226-
testCommandExecutor := &mockCommandExecutor{}
227+
testCommandExecutor := &MockCommandExecutor{}
227228

228-
testFileSystem := &mockFileSystem{}
229+
testFileSystem := &MockFileSystem{}
229230

230231
launchd := daemon.NewLaunchdProvider(testUserProvider, testCommandExecutor, testFileSystem)
231232

232233
// Verify launchd commands called
233234
for _, tt := range launchdCreateBehaviorTests {
234-
forceArg := tt.force.toBoolList()
235+
forceArg := tt.force.ToBoolList()
235236
for _, force := range forceArg {
236237
t.Run(fmt.Sprintf("%s (force='%t')", tt.title, force), func(t *testing.T) {
237238
// Mock responses
238239
for _, retVal := range tt.launchctlPrint {
239240
testCommandExecutor.On("Run",
240241
"launchctl",
241242
mock.MatchedBy(func(args []string) bool { return args[0] == "print" }),
242-
).Return(retVal.first, retVal.second).Once()
243+
).Return(retVal.First, retVal.Second).Once()
243244
}
244245
for _, retVal := range tt.launchctlBootstrap {
245246
testCommandExecutor.On("Run",
246247
"launchctl",
247248
mock.MatchedBy(func(args []string) bool { return args[0] == "bootstrap" }),
248-
).Return(retVal.first, retVal.second).Once()
249+
).Return(retVal.First, retVal.Second).Once()
249250
}
250251
for _, retVal := range tt.launchctlBootout {
251252
testCommandExecutor.On("Run",
252253
"launchctl",
253254
mock.MatchedBy(func(args []string) bool { return args[0] == "bootout" }),
254-
).Return(retVal.first, retVal.second).Once()
255+
).Return(retVal.First, retVal.Second).Once()
255256
}
256257
for _, retVal := range tt.fileExists {
257258
testFileSystem.On("FileExists",
258259
mock.AnythingOfType("string"),
259-
).Return(retVal.first, retVal.second).Once()
260+
).Return(retVal.First, retVal.Second).Once()
260261
}
261262
for _, retVal := range tt.writeFile {
262263
testFileSystem.On("WriteFile",
@@ -347,10 +348,10 @@ func TestLaunchd_Start(t *testing.T) {
347348
Uid: "123",
348349
Username: "testuser",
349350
}
350-
testUserProvider := &mockUserProvider{}
351+
testUserProvider := &MockUserProvider{}
351352
testUserProvider.On("CurrentUser").Return(testUser, nil)
352353

353-
testCommandExecutor := &mockCommandExecutor{}
354+
testCommandExecutor := &MockCommandExecutor{}
354355

355356
launchd := daemon.NewLaunchdProvider(testUserProvider, testCommandExecutor, nil)
356357

@@ -388,10 +389,10 @@ func TestLaunchd_Stop(t *testing.T) {
388389
Uid: "123",
389390
Username: "testuser",
390391
}
391-
testUserProvider := &mockUserProvider{}
392+
testUserProvider := &MockUserProvider{}
392393
testUserProvider.On("CurrentUser").Return(testUser, nil)
393394

394-
testCommandExecutor := &mockCommandExecutor{}
395+
testCommandExecutor := &MockCommandExecutor{}
395396

396397
launchd := daemon.NewLaunchdProvider(testUserProvider, testCommandExecutor, nil)
397398

internal/daemon/shared_test.go

Lines changed: 0 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
11
package daemon_test
22

33
import (
4-
"os/user"
5-
64
"github.com/github/git-bundle-server/internal/daemon"
7-
"github.com/stretchr/testify/mock"
85
)
96

107
/*********************************************/
@@ -16,86 +13,3 @@ var basicDaemonConfig = daemon.DaemonConfig{
1613
Description: "Test service",
1714
Program: "/usr/local/bin/test/git-bundle-web-server",
1815
}
19-
20-
/*********************************************/
21-
/************* Types & Functions *************/
22-
/*********************************************/
23-
24-
type pair[T any, R any] struct {
25-
first T
26-
second R
27-
}
28-
29-
func newPair[T any, R any](first T, second R) pair[T, R] {
30-
return pair[T, R]{
31-
first: first,
32-
second: second,
33-
}
34-
}
35-
36-
type boolArg int
37-
38-
const (
39-
False boolArg = iota
40-
True
41-
Any
42-
)
43-
44-
func (b boolArg) toBoolList() []bool {
45-
switch b {
46-
case False:
47-
return []bool{false}
48-
case True:
49-
return []bool{true}
50-
case Any:
51-
return []bool{false, true}
52-
default:
53-
panic("invalid bool arg value")
54-
}
55-
}
56-
57-
/*********************************************/
58-
/******************* Mocks *******************/
59-
/*********************************************/
60-
61-
type mockUserProvider struct {
62-
mock.Mock
63-
}
64-
65-
func (m *mockUserProvider) CurrentUser() (*user.User, error) {
66-
fnArgs := m.Called()
67-
return fnArgs.Get(0).(*user.User), fnArgs.Error(1)
68-
}
69-
70-
type mockCommandExecutor struct {
71-
mock.Mock
72-
}
73-
74-
func (m *mockCommandExecutor) Run(command string, args ...string) (int, error) {
75-
fnArgs := m.Called(command, args)
76-
return fnArgs.Int(0), fnArgs.Error(1)
77-
}
78-
79-
type mockFileSystem struct {
80-
mock.Mock
81-
}
82-
83-
func (m *mockFileSystem) FileExists(filename string) (bool, error) {
84-
fnArgs := m.Called(filename)
85-
return fnArgs.Bool(0), fnArgs.Error(1)
86-
}
87-
88-
func (m *mockFileSystem) WriteFile(filename string, content []byte) error {
89-
fnArgs := m.Called(filename, content)
90-
return fnArgs.Error(0)
91-
}
92-
93-
func (m *mockFileSystem) ReadFileLines(filename string) ([]string, error) {
94-
fnArgs := m.Called(filename)
95-
return fnArgs.Get(0).([]string), fnArgs.Error(1)
96-
}
97-
98-
func (m *mockFileSystem) UserHomeDir() (string, error) {
99-
fnArgs := m.Called()
100-
return fnArgs.String(0), fnArgs.Error(1)
101-
}

0 commit comments

Comments
 (0)