Skip to content

Commit f4a277a

Browse files
Adapted test utilities to mocking mechanism
1 parent 6af4599 commit f4a277a

File tree

4 files changed

+357
-256
lines changed

4 files changed

+357
-256
lines changed

main_test.go

+5-256
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,11 @@ package gowsl_test
33
// This file contains testing functionality
44

55
import (
6-
"bufio"
7-
"bytes"
86
"context"
9-
"errors"
10-
"fmt"
11-
"math/rand"
127
"os"
13-
"os/exec"
14-
"strings"
158
"testing"
16-
"time"
179

1810
"github.com/0xrawsec/golang-utils/log"
19-
"github.com/stretchr/testify/require"
2011
wsl "github.com/ubuntu/gowsl"
2112
)
2213

@@ -27,7 +18,9 @@ const (
2718
)
2819

2920
func TestMain(m *testing.M) {
30-
restore, err := backUpDefaultDistro()
21+
ctx := testContext(context.Background())
22+
23+
restore, err := backUpDefaultDistro(ctx)
3124
if err != nil {
3225
log.Errorf("setup: %v", err)
3326
os.Exit(1)
@@ -36,255 +29,11 @@ func TestMain(m *testing.M) {
3629

3730
exitVal := m.Run()
3831

39-
err = wsl.Shutdown()
32+
err = wsl.Shutdown(ctx)
4033
if err != nil {
4134
log.Warnf("cleanup: Failed to shutdown WSL")
4235
}
43-
cleanUpTestWslInstances()
36+
cleanUpTestWslInstances(ctx)
4437

4538
os.Exit(exitVal)
4639
}
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-
}

utils_mock_test.go

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
//go:build gowslmock
2+
3+
// This file contains the implementation of testutils geared towards the mock back-end.
4+
5+
package gowsl_test
6+
7+
import (
8+
"context"
9+
"errors"
10+
"testing"
11+
12+
"github.com/stretchr/testify/require"
13+
wsl "github.com/ubuntu/gowsl"
14+
wslmock "github.com/ubuntu/gowsl/mock"
15+
)
16+
17+
// TestContext creates a context that will instruct GoWSL to use the right back-end
18+
// based on whether it was build with mocking enabled.
19+
func testContext(ctx context.Context) context.Context {
20+
return wsl.WithMock(ctx, wslmock.Backend{})
21+
}
22+
23+
// installDistro installs using powershell to decouple the tests from Distro.Register
24+
// CommandContext sometimes fails to stop it, so a more aggressive approach is taken by rebooting WSL.
25+
//
26+
// TODO: Implement mock.
27+
//
28+
//nolint:revive // No, I wont' put the context before the *testing.T.
29+
func installDistro(t *testing.T, ctx context.Context, distroName string, rootfs string) {
30+
t.Helper()
31+
32+
require.Fail(t, "Mock not implemented")
33+
}
34+
35+
// uninstallDistro checks if a distro exists and if it does, it unregisters it.
36+
//
37+
// TODO: Implement mock.
38+
func uninstallDistro(distro wsl.Distro) error {
39+
return errors.New("uninstallDistro not implemented for mock back-end")
40+
}
41+
42+
// testDistros finds all distros with a mangled name.
43+
//
44+
// TODO: Implement mock.
45+
func registeredDistros(ctx context.Context) (distros []wsl.Distro, err error) {
46+
return nil, errors.New("registeredDistros not implemented for mock back-end")
47+
}
48+
49+
// defaultDistro gets the default distro's name via wsl.exe to bypass wsl.DefaultDistro in order to
50+
// better decouple tests.
51+
//
52+
// TODO: Implement mock.
53+
func defaultDistro(ctx context.Context) (string, error) {
54+
return "", errors.New("defaultDistro not implemented for mock back-end")
55+
}
56+
57+
// setDefaultDistro sets a distro as default using Powershell.
58+
//
59+
// TODO: Implement mock.
60+
func setDefaultDistro(ctx context.Context, distroName string) error {
61+
return errors.New("setDefaultDistro not implemented for mock back-end")
62+
}

0 commit comments

Comments
 (0)