Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add functions for verbose and with context execution #11

Merged
merged 8 commits into from
Feb 14, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/unit-testing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,4 @@ jobs:
with:
go-version: 1.*

- run: go test ./utils -count=100
- run: go test ./utils -count=10
27 changes: 22 additions & 5 deletions utils/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package utils

import (
"bufio"
"context"
"io"
"log"
goexec "os/exec"
Expand All @@ -10,18 +11,35 @@ import (
)

func Exec(t *testing.T, command string) (stdout, stderr string, err error) {
return exec(t, command, false)
return exec(t, nil, command, false)
}

func ExecVerbose(t *testing.T, command string) (stdout, stderr string, err error) {
return exec(t, nil, command, true)
}

func ExecContext(t *testing.T, ctx context.Context, command string) (stdout, stderr string, err error) {
return exec(t, ctx, command, false)
}

func ExecContextVerbose(t *testing.T, ctx context.Context, command string) (stdout, stderr string, err error) {
return exec(t, ctx, command, true)
}

// exec executes a command
func exec(t *testing.T, command string, verbose bool) (stdout, stderr string, err error) {
func exec(t *testing.T, ctx context.Context, command string, verbose bool) (stdout, stderr string, err error) {
if t != nil {
t.Logf("[exec] %s", command)
} else {
log.Printf("[exec] %s", command)
}

cmd := goexec.Command("/bin/sh", "-c", command)
var cmd *goexec.Cmd
if ctx == nil {
cmd = goexec.Command("/bin/bash", "-c", command)
} else {
cmd = goexec.CommandContext(ctx, "/bin/bash", "-c", command)
}

var wg sync.WaitGroup

Expand Down Expand Up @@ -73,9 +91,8 @@ func exec(t *testing.T, command string, verbose bool) (stdout, stderr string, er
}
}
t.Fatal(err)
} else {
return stdout, stderr, err
}
return stdout, stderr, err
}

return
Expand Down
34 changes: 29 additions & 5 deletions utils/exec_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package utils

import (
"context"
"testing"
"time"

Expand All @@ -11,15 +12,15 @@ import (
func TestExec(t *testing.T) {

t.Run("one command", func(t *testing.T) {
stdout, stderr, err := exec(t, `echo "hi"`, true)
stdout, stderr, err := exec(t, nil, `echo "hi"`, true)
assert.NoError(t, err)
assert.Empty(t, stderr)
assert.Equal(t, "hi\n", stdout)
})

t.Run("exit after slow command", func(t *testing.T) {
start := time.Now()
stdout, _, err := exec(t, `echo "hi" && sleep 0.1 && echo "hi2"`, true)
stdout, _, err := exec(t, nil, `echo "hi" && sleep 0.1 && echo "hi2"`, true)
// must return after 100ms±50ms
require.WithinDuration(t,
start.Add(100*time.Millisecond),
Expand All @@ -30,24 +31,47 @@ func TestExec(t *testing.T) {
})

t.Run("bad command", func(t *testing.T) {
stdout, stderr, err := exec(nil, `bad_command`, true)
stdout, stderr, err := exec(nil, nil, `bad_command`, true)
assert.Error(t, err)
assert.Empty(t, stdout)
assert.Contains(t, stderr, "not found")
})

t.Run("print to stderr", func(t *testing.T) {
stdout, stderr, err := exec(t, `echo "failing" >&2`, true)
stdout, stderr, err := exec(t, nil, `echo "failing" >&2`, true)
assert.NoError(t, err)
assert.Empty(t, stdout)
assert.Equal(t, "failing\n", stderr)
})

t.Run("stderr then stdout", func(t *testing.T) {
stdout, stderr, err := exec(t, `echo "failing" >&2; echo "succeeding"`, true)
stdout, stderr, err := exec(t, nil, `echo "failing" >&2; echo "succeeding"`, true)
assert.NoError(t, err)
assert.Equal(t, "failing\n", stderr)
assert.Equal(t, "succeeding\n", stdout)
})

t.Run("context timed out", func(t *testing.T) {
timeout := 1 * time.Second

ctx, cancel := context.WithTimeout(context.Background(), timeout)
t.Cleanup(cancel)

start := time.Now()
_, _, err := exec(nil, ctx, `sleep 3`, true)

require.Error(t, err)
require.WithinDuration(t, start, time.Now(), timeout+500*time.Millisecond)
})

t.Run("context not timed out", func(t *testing.T) {
timeout := 3 * time.Second

ctx, cancel := context.WithTimeout(context.Background(), timeout)
t.Cleanup(cancel)

_, _, err := exec(nil, ctx, `sleep 1`, true)

require.NoError(t, err)
})
}
5 changes: 2 additions & 3 deletions utils/refresh.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,9 @@ func TestRefresh(t *testing.T, snapName string) {
t.Logf("Looking for files containing previous snap revision %s", originalRevision)

// The command should not return error even if nothing is grepped, hence the "|| true"
stdout, stderr, _ := exec(t,
stdout, stderr, _ := ExecVerbose(t,
fmt.Sprintf("sudo grep -RnI '%s/%s' /var/snap/%s/current || true",
snapName, originalRevision, snapName),
true)
snapName, originalRevision, snapName))
require.Empty(t, stdout,
"The following files contain revision %s instead of %s or 'current' symlink: %s",
originalRevision, refreshRevision, stdout)
Expand Down
74 changes: 37 additions & 37 deletions utils/snap.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,12 @@ func SnapInstallFromStore(t *testing.T, name, channel string) error {
option = "--revision"
}

_, stderr, err := exec(t, fmt.Sprintf(
_, stderr, err := ExecVerbose(t, fmt.Sprintf(
"sudo snap install %s %s=%s",
name,
option,
channel,
), true)
))

if err != nil {
return fmt.Errorf("%s: %s", err, stderr)
Expand All @@ -39,49 +39,49 @@ func SnapInstallFromStore(t *testing.T, name, channel string) error {
}

func SnapInstallFromFile(t *testing.T, path string) error {
_, stderr, err := exec(t, fmt.Sprintf(
_, stderr, err := ExecVerbose(t, fmt.Sprintf(
"sudo snap install --dangerous %s",
path,
), true)
))
if err != nil {
return fmt.Errorf("%s: %s", err, stderr)
}
return nil
}

func SnapInstalled(t *testing.T, name string) bool {
out, _, _ := exec(t, fmt.Sprintf(
out, _, _ := ExecVerbose(t, fmt.Sprintf(
"snap list %s || true",
name,
), true)
))
return strings.Contains(out, name)
}

func SnapRemove(t *testing.T, names ...string) {
for _, name := range names {
exec(t, fmt.Sprintf(
ExecVerbose(t, fmt.Sprintf(
"sudo snap remove --purge %s",
name,
), true)
))
}
}

func SnapBuild(t *testing.T, workDir string) error {
_, stderr, err := exec(t, fmt.Sprintf(
_, stderr, err := ExecVerbose(t, fmt.Sprintf(
"cd %s && snapcraft",
workDir,
), true)
))
if err != nil {
return fmt.Errorf("%s: %s", err, stderr)
}
return nil
}

func SnapConnect(t *testing.T, plug, slot string) error {
_, stderr, err := exec(t, fmt.Sprintf(
_, stderr, err := ExecVerbose(t, fmt.Sprintf(
"sudo snap connect %s %s",
plug, slot,
), true)
))
if err != nil {
return fmt.Errorf("%s: %s", err, stderr)
}
Expand All @@ -95,25 +95,25 @@ func SnapConnectSecretstoreToken(t *testing.T, snap string) error {
}

func SnapDisconnect(t *testing.T, plug, slot string) {
exec(t, fmt.Sprintf(
ExecVerbose(t, fmt.Sprintf(
"sudo snap disconnect %s %s",
plug, slot,
), true)
))
}

func SnapVersion(t *testing.T, name string) string {
out, _, _ := exec(t, fmt.Sprintf(
out, _, _ := ExecVerbose(t, fmt.Sprintf(
"snap info %s | grep installed | awk '{print $2}'",
name,
), true)
))
return strings.TrimSpace(out)
}

func SnapRevision(t *testing.T, name string) string {
out, _, _ := exec(t, fmt.Sprintf(
out, _, _ := ExecVerbose(t, fmt.Sprintf(
"snap list %s | awk 'NR==2 {print $3}'",
name,
), true)
))
return strings.TrimSpace(out)
}

Expand All @@ -126,87 +126,87 @@ func snapJournalCommand(start time.Time, name string) string {

func SnapDumpLogs(t *testing.T, start time.Time, name string) {
filename := name + ".log" // used in action.yml
exec(t, fmt.Sprintf("(%s) > %s",
ExecVerbose(t, fmt.Sprintf("(%s) > %s",
snapJournalCommand(start, name),
filename), true)
filename))

wd, _ := os.Getwd()
fmt.Printf("Wrote snap logs to %s/%s\n", wd, filename)
}

func SnapLogs(t *testing.T, start time.Time, name string) string {
logs, _, _ := exec(t, snapJournalCommand(start, name), false)
logs, _, _ := Exec(t, snapJournalCommand(start, name))
return logs
}

func SnapSet(t *testing.T, name, key, value string) {
exec(t, fmt.Sprintf(
ExecVerbose(t, fmt.Sprintf(
"sudo snap set %s %s='%s'",
name,
key,
value,
), true)
))
}

func SnapUnset(t *testing.T, name string, keys ...string) {
exec(t, fmt.Sprintf(
ExecVerbose(t, fmt.Sprintf(
"sudo snap unset %s %s",
name,
strings.Join(keys, " "),
), true)
))
}

func SnapStart(t *testing.T, names ...string) {
for _, name := range names {
exec(t, fmt.Sprintf(
ExecVerbose(t, fmt.Sprintf(
"sudo snap start --enable %s",
name,
), true)
))
}
}

func SnapStop(t *testing.T, names ...string) {
for _, name := range names {
exec(t, fmt.Sprintf(
ExecVerbose(t, fmt.Sprintf(
"sudo snap stop --disable %s",
name,
), true)
))
}
}

func SnapRestart(t *testing.T, names ...string) {
for _, name := range names {
exec(t, fmt.Sprintf(
ExecVerbose(t, fmt.Sprintf(
"sudo snap restart %s",
name,
), true)
))
}
// Add delay after restart to avoid reaching systemd's restart limits
// See https://www.freedesktop.org/software/systemd/man/systemd-system.conf.html#DefaultStartLimitIntervalSec=
time.Sleep(1 * time.Second)
}

func SnapRefresh(t *testing.T, name, channel string) {
exec(t, fmt.Sprintf(
ExecVerbose(t, fmt.Sprintf(
"sudo snap refresh %s --channel=%s --amend",
name,
channel,
), true)
))
}

func SnapServicesEnabled(t *testing.T, name string) bool {
out, _, _ := exec(t, fmt.Sprintf(
out, _, _ := ExecVerbose(t, fmt.Sprintf(
"snap services %s | awk 'FNR == 2 {print $2}'",
name,
), true)
))
return strings.TrimSpace(out) == "enabled"
}

func SnapServicesActive(t *testing.T, name string) bool {
out, _, _ := exec(t, fmt.Sprintf(
out, _, _ := ExecVerbose(t, fmt.Sprintf(
"snap services %s | awk 'FNR == 2 {print $3}'",
name,
), true)
))
return strings.TrimSpace(out) == "active"
}

Expand Down