@@ -3,20 +3,11 @@ package gowsl_test
3
3
// This file contains testing functionality
4
4
5
5
import (
6
- "bufio"
7
- "bytes"
8
6
"context"
9
- "errors"
10
- "fmt"
11
- "math/rand"
12
7
"os"
13
- "os/exec"
14
- "strings"
15
8
"testing"
16
- "time"
17
9
18
10
"github.com/0xrawsec/golang-utils/log"
19
- "github.com/stretchr/testify/require"
20
11
wsl "github.com/ubuntu/gowsl"
21
12
)
22
13
@@ -27,7 +18,9 @@ const (
27
18
)
28
19
29
20
func TestMain (m * testing.M ) {
30
- restore , err := backUpDefaultDistro ()
21
+ ctx := testContext (context .Background ())
22
+
23
+ restore , err := backUpDefaultDistro (ctx )
31
24
if err != nil {
32
25
log .Errorf ("setup: %v" , err )
33
26
os .Exit (1 )
@@ -36,255 +29,11 @@ func TestMain(m *testing.M) {
36
29
37
30
exitVal := m .Run ()
38
31
39
- err = wsl .Shutdown ()
32
+ err = wsl .Shutdown (ctx )
40
33
if err != nil {
41
34
log .Warnf ("cleanup: Failed to shutdown WSL" )
42
35
}
43
- cleanUpTestWslInstances ()
36
+ cleanUpTestWslInstances (ctx )
44
37
45
38
os .Exit (exitVal )
46
39
}
47
-
48
- // sanitizeDistroName sanitizes the name of the disto as much as possible.
49
- func sanitizeDistroName (candidateName string ) string {
50
- r := strings .NewReplacer (
51
- `/` , `--` ,
52
- ` ` , `_` ,
53
- `\` , `--` ,
54
- )
55
- return r .Replace (candidateName )
56
- }
57
-
58
- // Generates a unique distro name. It does not create the distro.
59
- func uniqueDistroName (t * testing.T ) string {
60
- t .Helper ()
61
- const maxAttempts = 10
62
- for i := 0 ; i < maxAttempts ; i ++ {
63
- //nolint:gosec
64
- // No need for this random number to be cryptographically secure
65
- d := wsl .NewDistro (sanitizeDistroName (fmt .Sprintf ("%s_%s_%d" , namePrefix , t .Name (), rand .Uint32 ())))
66
-
67
- // Ensuring no name collision
68
- exists , err := d .IsRegistered ()
69
- if err != nil {
70
- t .Logf ("Setup: error in test distro name uniqueness check: %v" , err )
71
- continue
72
- }
73
- if exists {
74
- t .Logf ("Setup: name collision generating test distro: %q." , d .Name ())
75
- continue
76
- }
77
- return d .Name ()
78
- }
79
- require .Fail (t , "Setup: failed to generate a unique name for the test distro." )
80
- return ""
81
- }
82
-
83
- // newTestDistro creates and registers a new distro with a mangled name and adds it to list of distros to remove.
84
- func newTestDistro (t * testing.T , rootfs string ) wsl.Distro {
85
- t .Helper ()
86
-
87
- d := wsl .NewDistro (uniqueDistroName (t ))
88
- t .Logf ("Setup: Registering %q\n " , d .Name ())
89
-
90
- powershellInstallDistro (t , d .Name (), rootfs )
91
-
92
- t .Cleanup (func () {
93
- err := cleanUpWslInstance (d )
94
- if err != nil {
95
- t .Logf ("Cleanup: %v\n " , err )
96
- }
97
- })
98
-
99
- t .Logf ("Setup: Distro %q registered\n " , d .Name ())
100
- return d
101
- }
102
-
103
- // powershellInstallDistro installs using powershell to decouple the tests from Distro.Register
104
- // CommandContext sometimes fails to stop it, so a more aggressive approach is taken by rebooting WSL.
105
- // TODO: Consider if we want to retry.
106
- func powershellInstallDistro (t * testing.T , distroName string , rootfs string ) {
107
- t .Helper ()
108
-
109
- // Timeout to attempt a graceful failure
110
- const gracefulTimeout = 60 * time .Second
111
-
112
- // Timeout to shutdown WSL
113
- const aggressiveTimeout = gracefulTimeout + 10 * time .Second
114
-
115
- // Cannot use context.WithTimeout because I want to quit by doing wsl --shutdown
116
- expired := time .After (aggressiveTimeout )
117
-
118
- type combinedOutput struct {
119
- output string
120
- err error
121
- }
122
- cmdOut := make (chan combinedOutput )
123
-
124
- go func () {
125
- ctx , cancel := context .WithTimeout (context .Background (), gracefulTimeout )
126
- defer cancel ()
127
- cmd := fmt .Sprintf ("$env:WSL_UTF8=1 ; wsl.exe --import %s %s %s" , distroName , t .TempDir (), rootfs )
128
- o , e := exec .CommandContext (ctx , "powershell.exe" , "-Command" , cmd ).CombinedOutput () //nolint:gosec
129
-
130
- cmdOut <- combinedOutput {output : string (o ), err : e }
131
- close (cmdOut )
132
- }()
133
-
134
- var output combinedOutput
135
- select {
136
- case <- expired :
137
- t .Logf ("Setup: installation of WSL distro %s got stuck. Rebooting WSL." , distroName )
138
- e := exec .Command ("wsl.exe" , "--shutdown" ).Run ()
139
- require .NoError (t , e , "Setup: failed to shutdown WSL after distro installation got stuck" )
140
-
141
- // Almost guaranteed to error out here
142
- output = <- cmdOut
143
- require .NoError (t , output .err , output .output )
144
-
145
- require .Fail (t , "Setup: unknown state: successfully registered while WSL was shut down, stdout+stderr:" , output .output )
146
- case output = <- cmdOut :
147
- }
148
- require .NoErrorf (t , output .err , "Setup: failed to register %q: %s" , distroName , output .output )
149
- }
150
-
151
- // cleanUpTestWslInstances finds all distros with a prefixed name and unregisters them.
152
- func cleanUpTestWslInstances () {
153
- testInstances , err := registeredTestWslInstances ()
154
- if err != nil {
155
- return
156
- }
157
- if len (testInstances ) != 0 {
158
- s := ""
159
- for _ , d := range testInstances {
160
- s = s + "\n - " + d .Name ()
161
- }
162
- log .Warnf ("Cleanup: The following WSL distros were not properly cleaned up:%s" , s )
163
- }
164
-
165
- for _ , d := range testInstances {
166
- err := cleanUpWslInstance (d )
167
- if err != nil {
168
- log .Warnf ("Cleanup: %v\n " , err )
169
- }
170
- }
171
- }
172
-
173
- // cleanUpWslInstance checks if a distro exists and if it does, it unregisters it.
174
- func cleanUpWslInstance (distro wsl.Distro ) error {
175
- if r , err := distro .IsRegistered (); err == nil && ! r {
176
- return nil
177
- }
178
- cmd := fmt .Sprintf ("$env:WSL_UTF8=1 ; wsl.exe --unregister %s" , distro .Name ())
179
- _ , err := exec .Command ("powershell.exe" , "-command" , cmd ).CombinedOutput () //nolint: gosec
180
- if err != nil {
181
- return fmt .Errorf ("failed to clean up test WSL distro %q: %v" , distro .Name (), err )
182
- }
183
- return nil
184
- }
185
-
186
- // registeredTestWslInstances finds all distros with a mangled name.
187
- func registeredTestWslInstances () ([]wsl.Distro , error ) {
188
- distros := []wsl.Distro {}
189
-
190
- outp , err := exec .Command ("powershell.exe" , "-command" , "$env:WSL_UTF8=1 ; wsl.exe --list --quiet" ).CombinedOutput ()
191
- if err != nil {
192
- return distros , err
193
- }
194
-
195
- for _ , line := range strings .Fields (string (outp )) {
196
- if ! strings .HasPrefix (line , namePrefix ) {
197
- continue
198
- }
199
- distros = append (distros , wsl .NewDistro (line ))
200
- }
201
-
202
- return distros , nil
203
- }
204
-
205
- // defaultDistro gets the default distro's name via wsl.exe to bypass wsl.DefaultDistro in order to
206
- // better decouple tests.
207
- func defaultDistro () (string , error ) {
208
- out , err := exec .Command ("powershell.exe" , "-Command" , "$env:WSL_UTF8=1; wsl.exe --list --verbose" ).CombinedOutput ()
209
- if err != nil {
210
- if target := (& exec.ExitError {}); ! errors .As (err , & target ) {
211
- return "" , fmt .Errorf ("failed to find current default distro: %v" , err )
212
- }
213
- // cannot read from target.StdErr because message is printed to Stdout
214
- if ! strings .Contains (string (out ), "Wsl/WSL_E_DEFAULT_DISTRO_NOT_FOUND" ) {
215
- return "" , fmt .Errorf ("failed to find current default distro: %v. Output: %s" , err , out )
216
- }
217
- return "" , nil // No distros installed: no default
218
- }
219
-
220
- s := bufio .NewScanner (bytes .NewReader (out ))
221
- s .Scan () // Ignore first line (table header)
222
- for s .Scan () {
223
- line := s .Text ()
224
- if ! strings .HasPrefix (line , "*" ) {
225
- continue
226
- }
227
- data := strings .Fields (line )
228
- if len (data ) < 2 {
229
- return "" , fmt .Errorf ("failed to parse 'wsl.exe --list --verbose' output, line %q" , line )
230
- }
231
- return data [1 ], nil
232
- }
233
-
234
- if err := s .Err (); err != nil {
235
- return "" , err
236
- }
237
-
238
- return "" , fmt .Errorf ("failed to find default distro in 'wsl.exe --list --verbose' output:\n %s" , string (out ))
239
- }
240
-
241
- // backUpDefaultDistro returns a function to restore the default distro so the machine is restored to
242
- // its pre-testing state.
243
- func backUpDefaultDistro () (func (), error ) {
244
- distro , err := defaultDistro ()
245
- if err != nil {
246
- return nil , fmt .Errorf ("failed to back up default distro: %v" , err )
247
- }
248
- if len (distro ) == 0 {
249
- return func () {}, nil // No distros registered: no backup needed
250
- }
251
- restore := func () {
252
- //nolint: gosec // G204: Subprocess launched with a potential tainted input or cmd arguments
253
- // No threat of code injection, wsl.exe will only interpret this text as a distro name
254
- // and throw Wsl/Service/WSL_E_DISTRO_NOT_FOUND if it does not exist.
255
- out , err := exec .Command ("wsl.exe" , "--set-default" , distro ).CombinedOutput ()
256
- if err != nil {
257
- log .Warnf ("failed to set distro %q back as default: %v. Output: %s" , distro , err , out )
258
- }
259
- }
260
- return restore , nil
261
- }
262
-
263
- // keepAwake sends an endless command to the distro to keep it awake.
264
- // The distro will stay awake until the context is cancelled or the cancel
265
- // function is called.
266
- //
267
- // You must call the cancel function to release the associated resources.
268
- //
269
- //nolint:revive
270
- func keepAwake (t * testing.T , ctx context.Context , d * wsl.Distro ) context.CancelFunc {
271
- // Linter says "context-as-argument: context.Context should be the first parameter of a function"
272
- // This is an abomination that we won't stand for
273
- t .Helper ()
274
- ctx , cancel := context .WithCancel (ctx )
275
-
276
- cmd := d .Command (ctx , "sleep infinity" )
277
- err := cmd .Start ()
278
- if err != nil {
279
- cancel ()
280
- require .Failf (t , "failed to Start command to keep the distro alive:" , "%v" , err )
281
- }
282
-
283
- return func () {
284
- cancel ()
285
- //nolint: errcheck
286
- // not checking error because it is guaranteed to fail: it can only
287
- // finish by being interrupted. This is the intended behaviour.
288
- cmd .Wait ()
289
- }
290
- }
0 commit comments