Skip to content
This repository was archived by the owner on Feb 16, 2023. It is now read-only.

Commit d0cecb4

Browse files
committed
Fix tests using WriteClipboardAutoClear
WriteClipboardAutoClear spawns up a process to clear the clipboard, using the args of the current process as the base command. For tests, this caused the test command to be spawned, recursively spawning more test commands. We somehow got away with this for a very long while, probably because the test executes quickly enough to exit before the stack runs full with processes. However, recently an AUR installation failed because of this bug in the tests, as AUR installations run the tests on installation. This commit fixes the issue by mocking the calls to WriteClipboardAutoClear in the tests and using a fake implementation instead, that doesn't spawn new processes.
1 parent 601ad00 commit d0cecb4

8 files changed

Lines changed: 100 additions & 98 deletions

File tree

internals/secrethub/clear_clipboard.go

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,21 +62,30 @@ func (cmd *ClearClipboardCommand) Run() error {
6262
return nil
6363
}
6464

65-
// WriteClipboardAutoClear writes data to the clipboard and clears it after the timeout.
66-
func WriteClipboardAutoClear(data []byte, timeout time.Duration, clipper clip.Clipper) error {
65+
type ClipboardWriter interface {
66+
Write(data []byte) error
67+
}
68+
69+
type ClipboardWriterAutoClear struct {
70+
timeout time.Duration
71+
clipper clip.Clipper
72+
}
73+
74+
// Write writes data to the clipboard and clears it after the timeout.
75+
func (clipWriter *ClipboardWriterAutoClear) Write(data []byte) error {
6776
hash, err := bcrypt.GenerateFromPassword(data, bcrypt.DefaultCost)
6877
if err != nil {
6978
return err
7079
}
7180

72-
err = clipper.WriteAll(data)
81+
err = clipWriter.clipper.WriteAll(data)
7382
if err != nil {
7483
return err
7584
}
7685

7786
err = cloneproc.Spawn(
7887
"clipboard-clear", hex.EncodeToString(hash),
79-
"--timeout", timeout.String())
88+
"--timeout", clipWriter.timeout.String())
8089

8190
return err
8291
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package secrethub
2+
3+
import "bytes"
4+
5+
type FakeClipboardWriter struct {
6+
Buffer bytes.Buffer
7+
}
8+
9+
func (clipWriter *FakeClipboardWriter) Write(data []byte) error {
10+
_, err := clipWriter.Buffer.Write(data)
11+
return err
12+
}

internals/secrethub/generate.go

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import (
55
"os"
66
"strconv"
77
"strings"
8-
"time"
98

109
"github.com/secrethub/secrethub-cli/internals/cli"
1110
"github.com/secrethub/secrethub-cli/internals/cli/clip"
@@ -35,28 +34,29 @@ const defaultLength = 22
3534

3635
// GenerateSecretCommand generates a new secret and writes to the output path.
3736
type GenerateSecretCommand struct {
38-
symbolsFlag bool
39-
generator randchar.Generator
40-
io ui.IO
41-
lengthFlag intValue
42-
firstArg cli.StringValue
43-
secondArg cli.StringValue
44-
lengthArg intValue
45-
charsetFlag charsetValue
46-
mins minRuleValue
47-
copyToClipboard bool
48-
clearClipboardAfter time.Duration
49-
clipper clip.Clipper
50-
newClient newClientFunc
37+
symbolsFlag bool
38+
generator randchar.Generator
39+
io ui.IO
40+
lengthFlag intValue
41+
firstArg cli.StringValue
42+
secondArg cli.StringValue
43+
lengthArg intValue
44+
charsetFlag charsetValue
45+
mins minRuleValue
46+
copyToClipboard bool
47+
newClient newClientFunc
48+
clipWriter ClipboardWriter
5149
}
5250

5351
// NewGenerateSecretCommand creates a new GenerateSecretCommand.
5452
func NewGenerateSecretCommand(io ui.IO, newClient newClientFunc) *GenerateSecretCommand {
5553
return &GenerateSecretCommand{
56-
io: io,
57-
newClient: newClient,
58-
clearClipboardAfter: defaultClearClipboardAfter,
59-
clipper: clip.NewClipboard(),
54+
io: io,
55+
newClient: newClient,
56+
clipWriter: &ClipboardWriterAutoClear{
57+
clipper: clip.NewClipboard(),
58+
timeout: defaultClearClipboardAfter,
59+
},
6060
}
6161
}
6262

@@ -66,7 +66,7 @@ func (cmd *GenerateSecretCommand) Register(r cli.Registerer) {
6666
clause.Flags().VarP(&cmd.lengthFlag, "length", "l", "The length of the generated secret.")
6767
clause.Cmd.Flag("length").DefValue = strconv.Itoa(defaultLength)
6868
clause.Flags().Var(&cmd.mins, "min", "<charset>:<n> Ensure that the resulting password contains at least n characters from the given character set. Note that adding constraints reduces the strength of the secret. When possible, avoid any constraints.")
69-
clause.Flags().BoolVarP(&cmd.copyToClipboard, "clip", "c", false, "Copy the generated value to the clipboard. The clipboard is automatically cleared after "+units.HumanDuration(cmd.clearClipboardAfter)+".")
69+
clause.Flags().BoolVarP(&cmd.copyToClipboard, "clip", "c", false, "Copy the generated value to the clipboard. The clipboard is automatically cleared after "+units.HumanDuration(defaultClearClipboardAfter)+".")
7070
_ = cmd.charsetFlag.Set("alphanumeric")
7171
clause.Flags().Var(&cmd.charsetFlag, "charset", "Define the set of characters to randomly generate a password from. Options are all, alphanumeric, numeric, lowercase, uppercase, letters, symbols and human-readable. Multiple character sets can be combined by supplying them in a comma separated list.")
7272
clause.Cmd.Flag("charset").DefValue = "alphanumeric"
@@ -147,15 +147,15 @@ func (cmd *GenerateSecretCommand) run() error {
147147
fmt.Fprintf(cmd.io.Output(), "A randomly generated secret has been written to %s:%d.\n", path, version.Version)
148148

149149
if cmd.copyToClipboard {
150-
err = WriteClipboardAutoClear(data, cmd.clearClipboardAfter, cmd.clipper)
150+
err = cmd.clipWriter.Write(data)
151151
if err != nil {
152152
return err
153153
}
154154

155155
fmt.Fprintf(
156156
cmd.io.Output(),
157157
"The generated value has been copied to the clipboard. It will be cleared after %s.\n",
158-
units.HumanDuration(cmd.clearClipboardAfter),
158+
units.HumanDuration(defaultClearClipboardAfter),
159159
)
160160
}
161161

internals/secrethub/generate_test.go

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import (
55
"testing"
66

77
"github.com/secrethub/secrethub-cli/internals/cli"
8-
"github.com/secrethub/secrethub-cli/internals/cli/clip/fakeclip"
98
"github.com/secrethub/secrethub-cli/internals/cli/ui/fakeui"
109

1110
"github.com/secrethub/secrethub-go/internals/api"
@@ -42,7 +41,6 @@ func TestGenerateSecretCommand_run(t *testing.T) {
4241
Err: nil,
4342
},
4443
firstArg: cli.StringValue{Value: testPath},
45-
clipper: fakeclip.New(),
4644
},
4745
writeFunc: func(path string, data []byte) (*api.SecretVersion, error) {
4846
return &api.SecretVersion{Version: 1}, nil
@@ -60,7 +58,6 @@ func TestGenerateSecretCommand_run(t *testing.T) {
6058
},
6159
firstArg: cli.StringValue{Value: testPath},
6260
copyToClipboard: true,
63-
clipper: fakeclip.New(),
6461
},
6562
writeFunc: func(path string, data []byte) (*api.SecretVersion, error) {
6663
return &api.SecretVersion{Version: 1}, nil
@@ -69,7 +66,7 @@ func TestGenerateSecretCommand_run(t *testing.T) {
6966
data: testData,
7067
expectedClip: testData,
7168
expectedOut: "A randomly generated secret has been written to namespace/repo/secret:1.\n" +
72-
"The generated value has been copied to the clipboard. It will be cleared after Less than a second.\n",
69+
"The generated value has been copied to the clipboard. It will be cleared after 45 seconds.\n",
7370
},
7471
"length flag": {
7572
cmd: GenerateSecretCommand{
@@ -79,7 +76,6 @@ func TestGenerateSecretCommand_run(t *testing.T) {
7976
},
8077
firstArg: cli.StringValue{Value: testPath},
8178
lengthFlag: newIntValue(24),
82-
clipper: fakeclip.New(),
8379
},
8480
writeFunc: func(path string, data []byte) (*api.SecretVersion, error) {
8581
return &api.SecretVersion{Version: 1}, nil
@@ -99,7 +95,6 @@ func TestGenerateSecretCommand_run(t *testing.T) {
9995
secondArg: cli.StringValue{Value: testPath},
10096
lengthFlag: newIntValue(24),
10197
lengthArg: newIntValue(24),
102-
clipper: fakeclip.New(),
10398
},
10499
expectedErr: ErrCannotUseLengthArgAndFlag,
105100
},
@@ -112,7 +107,6 @@ func TestGenerateSecretCommand_run(t *testing.T) {
112107
firstArg: cli.StringValue{Value: "rand"},
113108
secondArg: cli.StringValue{Value: testPath},
114109
lengthArg: newIntValue(23),
115-
clipper: fakeclip.New(),
116110
},
117111
writeFunc: func(path string, data []byte) (*api.SecretVersion, error) {
118112
return &api.SecretVersion{Version: 1}, nil
@@ -127,7 +121,6 @@ func TestGenerateSecretCommand_run(t *testing.T) {
127121
firstArg: cli.StringValue{Value: "rand"},
128122
secondArg: cli.StringValue{Value: testPath},
129123
lengthArg: newIntValue(0),
130-
clipper: fakeclip.New(),
131124
},
132125
expectedErr: ErrInvalidRandLength,
133126
expectedOut: "",
@@ -137,7 +130,6 @@ func TestGenerateSecretCommand_run(t *testing.T) {
137130
firstArg: cli.StringValue{Value: "rand"},
138131
secondArg: cli.StringValue{Value: testPath},
139132
lengthArg: newIntValue(-1),
140-
clipper: fakeclip.New(),
141133
},
142134
expectedErr: ErrInvalidRandLength,
143135
expectedOut: "",
@@ -147,7 +139,6 @@ func TestGenerateSecretCommand_run(t *testing.T) {
147139
cmd: GenerateSecretCommand{
148140
firstArg: cli.StringValue{Value: testPath},
149141
lengthArg: newIntValue(24),
150-
clipper: fakeclip.New(),
151142
},
152143
expectedErr: errors.New("unexpected 24"),
153144
},
@@ -156,7 +147,6 @@ func TestGenerateSecretCommand_run(t *testing.T) {
156147
cmd: GenerateSecretCommand{
157148
firstArg: cli.StringValue{Value: testPath},
158149
secondArg: cli.StringValue{Value: "namespace/repo/secret2"},
159-
clipper: fakeclip.New(),
160150
},
161151
expectedErr: errors.New("unexpected namespace/repo/secret2"),
162152
},
@@ -167,7 +157,6 @@ func TestGenerateSecretCommand_run(t *testing.T) {
167157
Err: testErr,
168158
},
169159
firstArg: cli.StringValue{Value: testPath},
170-
clipper: fakeclip.New(),
171160
},
172161
expectedErr: testErr,
173162
},
@@ -178,7 +167,6 @@ func TestGenerateSecretCommand_run(t *testing.T) {
178167
Err: nil,
179168
},
180169
firstArg: cli.StringValue{Value: testPath},
181-
clipper: fakeclip.New(),
182170
},
183171
newClientErr: testErr,
184172
expectedErr: testErr,
@@ -190,7 +178,6 @@ func TestGenerateSecretCommand_run(t *testing.T) {
190178
Err: nil,
191179
},
192180
firstArg: cli.StringValue{Value: testPath},
193-
clipper: fakeclip.New(),
194181
},
195182
writeFunc: func(path string, data []byte) (*api.SecretVersion, error) {
196183
return nil, testErr
@@ -210,6 +197,9 @@ func TestGenerateSecretCommand_run(t *testing.T) {
210197
testIO := fakeui.NewIO(t)
211198
tc.cmd.io = testIO
212199

200+
clipWriter := &FakeClipboardWriter{}
201+
tc.cmd.clipWriter = clipWriter
202+
213203
tc.cmd.newClient = func() (secrethub.ClientInterface, error) {
214204
return fakeclient.Client{
215205
SecretService: &fakeclient.SecretService{
@@ -224,14 +214,12 @@ func TestGenerateSecretCommand_run(t *testing.T) {
224214

225215
// Act
226216
err := tc.cmd.run()
227-
resClip, clipErr := tc.cmd.clipper.ReadAll()
228217

229218
// Assert
230-
assert.OK(t, clipErr)
231219
assert.Equal(t, err, tc.expectedErr)
232220
assert.Equal(t, argPath, tc.path)
233221
assert.Equal(t, argData, tc.data)
234-
assert.Equal(t, resClip, tc.expectedClip)
222+
assert.Equal(t, clipWriter.Buffer.Bytes(), tc.expectedClip)
235223
assert.Equal(t, testIO.Out.String(), tc.expectedOut)
236224
})
237225
}

internals/secrethub/inject.go

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import (
55
"io/ioutil"
66
"os"
77
"path/filepath"
8-
"time"
98

109
"github.com/secrethub/secrethub-cli/internals/cli"
1110
"github.com/secrethub/secrethub-cli/internals/cli/clip"
@@ -31,8 +30,7 @@ type InjectCommand struct {
3130
force bool
3231
io ui.IO
3332
useClipboard bool
34-
clearClipboardAfter time.Duration
35-
clipper clip.Clipper
33+
clipWriter ClipboardWriter
3634
osEnv []string
3735
newClient newClientFunc
3836
templateVars map[string]string
@@ -43,13 +41,15 @@ type InjectCommand struct {
4341
// NewInjectCommand creates a new InjectCommand.
4442
func NewInjectCommand(io ui.IO, newClient newClientFunc) *InjectCommand {
4543
return &InjectCommand{
46-
clipper: clip.NewClipboard(),
47-
osEnv: os.Environ(),
48-
clearClipboardAfter: defaultClearClipboardAfter,
49-
io: io,
50-
newClient: newClient,
51-
templateVars: make(map[string]string),
52-
fileMode: filemode.New(0600),
44+
clipWriter: &ClipboardWriterAutoClear{
45+
clipper: clip.NewClipboard(),
46+
timeout: defaultClearClipboardAfter,
47+
},
48+
osEnv: os.Environ(),
49+
io: io,
50+
newClient: newClient,
51+
templateVars: make(map[string]string),
52+
fileMode: filemode.New(0600),
5353
}
5454
}
5555

@@ -61,7 +61,7 @@ func (cmd *InjectCommand) Register(r cli.Registerer) {
6161
"clip", "c", false,
6262
fmt.Sprintf(
6363
"Copy the injected template to the clipboard instead of stdout. The clipboard is automatically cleared after %s.",
64-
units.HumanDuration(cmd.clearClipboardAfter),
64+
units.HumanDuration(defaultClearClipboardAfter),
6565
))
6666
clause.Flags().StringVarP(&cmd.inFile, "in-file", "i", "", "The filename of a template file to inject.")
6767
clause.Flags().StringVarP(&cmd.outFile, "out-file", "o", "", "Write the injected template to a file instead of stdout.")
@@ -131,12 +131,12 @@ func (cmd *InjectCommand) Run() error {
131131

132132
out := []byte(injected)
133133
if cmd.useClipboard {
134-
err = WriteClipboardAutoClear(out, cmd.clearClipboardAfter, cmd.clipper)
134+
err = cmd.clipWriter.Write(out)
135135
if err != nil {
136136
return err
137137
}
138138

139-
_, err = fmt.Fprintf(cmd.io.Output(), "Copied injected template to clipboard. It will be cleared after %s.\n", units.HumanDuration(cmd.clearClipboardAfter))
139+
_, err = fmt.Fprintf(cmd.io.Output(), "Copied injected template to clipboard. It will be cleared after %s.\n", units.HumanDuration(defaultClearClipboardAfter))
140140
if err != nil {
141141
return err
142142
}

0 commit comments

Comments
 (0)