Skip to content

Commit

Permalink
skip: introduce a skip package with utilities for skipping test cases
Browse files Browse the repository at this point in the history
  • Loading branch information
shoenig committed Apr 1, 2024
1 parent 2ceae04 commit 79592fd
Show file tree
Hide file tree
Showing 3 changed files with 362 additions and 1 deletion.
22 changes: 21 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,20 @@

`test` is a modern and generics oriented testing assertions library for Go.

There are four key packages,
There are five key packages,

- `must` - assertions causing test failure and halt the test case immediately
- `test` - assertions causing test failure and allow the test case to continue
- `wait` - utilities for waiting on conditionals in tests
- `skip` - utilities for skipping test cases in some situations
- `portal` - utilities for allocating free ports for network listeners in tests

### Changes

:ballot_box_with_check: v1.8.0 introduces the `skip` package for skipping tests!

- New helper functions for skipping out tests based on environment detection

:ballot_box_with_check: v1.7.0 marks the first stable release!

- Going forward no breaking changes will be made without a v2 major version
Expand Down Expand Up @@ -155,6 +160,21 @@ must.Eq(t, exp, result, must.Func(func() string {
})
```

### Skip

Sometimes it makes sense to just skip running a certain test case. Maybe the
operating system is all wrong or a certain necessary command is not currently
installed. The `skip` package provides utilities for skipping tests under some
given circumstances.

```go
skip.OperatingSystem(t, "windows")
```

```go
skip.CommandNotFound(t, "java")
```

### Wait

Sometimes a test needs to wait on a condition for a non-deterministic amount of time.
Expand Down
242 changes: 242 additions & 0 deletions skip/skip.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,242 @@
// Copyright (c) The Test Authors
// SPDX-License-Identifier: MPL-2.0

// Package skip provides helper functions for skipping test cases in
// environments that meet certain conditions.
package skip

import (
"context"
"errors"
"os"
"os/exec"
"regexp"
"runtime"
"strings"
"testing"
"time"
)

// OperatingSystem will skip the test if the Go runtime detects the operating system
// matches one of the given names.
func OperatingSystem(t *testing.T, names ...string) {
os := runtime.GOOS
for _, name := range names {
if os == strings.ToLower(name) {
t.Skipf("operating system excluded from tests %q", os)
}
}
}

// NotOperatingSystem will skip the test if the Go runtime detects the operating
// system does not match one of the given names.
func NotOperatingSystem(t *testing.T, names ...string) {
os := runtime.GOOS
for _, name := range names {
if os == strings.ToLower(name) {
return
}
}
t.Skipf("operating excluded from tests %q", os)
}

// UserRoot will skip the test if the test is being run as the root user.
//
// Uses the effective UID value to determine user.
func UserRoot(t *testing.T) {
euid := os.Geteuid()
if euid == 0 {
t.Skip("test must not run as root")
}
}

// NotUserRoot will skip the test if the test is not being run as the root user.
//
// Uses the effective UID value to determine user.
func NotUserRoot(t *testing.T) {
euid := os.Geteuid()
if euid != 0 {
t.Skip("test must run as root")
}
}

// Architecture will skip the test if the Go runtime detects the system
// architecture matches one of the given names.
func Architecture(t *testing.T, names ...string) {
arch := runtime.GOARCH
for _, name := range names {
if arch == strings.ToLower(name) {
t.Skipf("arch excluded from tests %q", arch)
}
}
}

// NotArchitecture will skip the test if the Go runtime the system architecture
// does not match one of the given names.
func NotArchitecture(t *testing.T, names ...string) {
arch := runtime.GOARCH
for _, name := range names {
if arch == strings.ToLower(name) {
return
}
}
t.Skipf("arch excluded from tests %q", arch)
}

func cmdAvailable(name string) bool {
_, err := exec.LookPath(name)
return !errors.Is(err, exec.ErrNotFound)
}

// CommandNotFound will skip the test if the given command cannot be found on
// the system.
func CommandNotFound(t *testing.T, command string) {
if !cmdAvailable(command) {
t.Skipf("command %q not detected on system", command)
}
}

// DockerNotFound will skip the test if the docker command cannot be found on
// the system path.
func DockerNotFound(t *testing.T) {
if !cmdAvailable("docker") {
t.Skip("docker not detected on system")
}
}

// PodmanNotFound will skip the test if the podman command cannot be found on
// the system path.
func PodmanNotFound(t *testing.T) {
if !cmdAvailable("podman") {
t.Skip("podman not detected on system")
}
}

// MinimumCores will skip the test if the system does not meet the minimum
// number of CPU cores.
func MinimumCores(t *testing.T, num int) {
cpus := runtime.NumCPU()
if cpus < num {
t.Skip("system does not meet minimum cpu cores")
}
}

// MaximumCores will skip the test if the system does not meet the maximum
// number of cpu cores.
func MaximumCores(t *testing.T, num int) {
cpus := runtime.NumCPU()
if cpus > num {
t.Skip("system exceeds maximum cpu cores")
}
}

// CgroupsVersion will skip the test if the system does not match the given
// cgroups version.
func CgroupsVersion(t *testing.T, version int) {
if runtime.GOOS != "linux" {
t.Skip("cgroups requires linux")
}

mType := mountType(t, "/sys/fs/cgroup")

switch mType {
case "tmpfs":
// this is a cgroups v1 system
if version == 2 {
t.Skip("system does not match cgroups version 2")
}
case "cgroup2":
// this is a cgroups v2 system
if version == 1 {
t.Skip("system does not match cgroups version 1")
}
default:
t.Fatalf("unknown cgroups mount type %q", mType)
}
}

func mountType(t *testing.T, path string) string {
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()

cmd := exec.CommandContext(ctx, "df", "-T", path)
b, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("unable to run df command: %v", err)
}

// need the first token of the second line
output := string(b)
tokenRe := regexp.MustCompile(`on\s+([\w]+)\s+`)
results := tokenRe.FindStringSubmatch(output)
if len(results) != 2 {
t.Fatal("no mount type for path")
}
return results[1]
}

// EnvironmentVariableSet will skip the test if the given environment variable
// is set to any value.
func EnvironmentVariableSet(t *testing.T, name string) {
if name == "" {
t.Fatal("environment variable name must be set")
}

_, exists := os.LookupEnv(name)
if exists {
t.Skipf("environment variable %q is set", name)
}
}

// EnvironmentVariableNotSet will skip the test if the given environment
// variable is not set in the system environment.
func EnvironmentVariableNotSet(t *testing.T, name string) {
if name == "" {
t.Fatal("environment variable name must be set")
}
_, exists := os.LookupEnv(name)
if !exists {
t.Skipf("environment variable %q is not set", name)
}
}

// EnvironmentVariableMatches will skip the test if the system environment
// matches one of the given environment variable values.
func EnvironmentVariableMatches(t *testing.T, name string, values ...string) {
if len(values) == 0 {
t.Fatal("no possible environment variable values given")
}

// if not set in the system, then it must not match
actual, exists := os.LookupEnv(name)
if !exists {
return
}

for _, value := range values {
if value == actual {
t.Skipf("environment variable %q matches %q", name, value)
}
}
}

// EnvironmentVariableNotMatches will skip the test if the system environment
// does not match one of the given environment variable values.
func EnvironmentVariableNotMatches(t *testing.T, name string, values ...string) {
if len(values) == 0 {
t.Fatal("no possible environment variable values given")
}

actual, exists := os.LookupEnv(name)
if !exists {
t.Skipf("environment variable %q not set", name)
}

for _, value := range values {
if actual == value {
return
}
}

t.Skipf("environment variable %q does not match values (is %q)", name, actual)
}
99 changes: 99 additions & 0 deletions skip/skip_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
// Copyright (c) The Test Authors
// SPDX-License-Identifier: MPL-2.0

//go:build linux || darwin

package skip

import "testing"

func TestSkip_OperatingSystem(t *testing.T) {
OperatingSystem(t, "darwin", "linux", "windows")
t.Fatal("expected to skip test")
}

func TestSkip_NotOperatingSystem(t *testing.T) {
NotOperatingSystem(t, "windows")
t.Fatal("expected to skip test")
}

func TestSkip_UserRoot(t *testing.T) {
t.Skip("requires root")
UserRoot(t)
t.Fatal("expected to skip test")
}

func TestSkip_NotUserRoot(t *testing.T) {
NotUserRoot(t)
t.Fatal("expected to skip test")
}

func TestSkip_Architecture(t *testing.T) {
Architecture(t, "arm64", "amd64")
t.Fatal("expected to skip test")
}

func TestSkip_NotArchitecture(t *testing.T) {
NotArchitecture(t, "itanium", "mips")
t.Fatal("expected to skip test")
}

func TestSkip_DockerNotFound(t *testing.T) {
t.Skip("skip docker test") // gha runner

DockerNotFound(t)
t.Fatal("expected to skip test")
}

func TestSkip_PodmanNotFound(t *testing.T) {
t.Skip("skip podman test") // gha runner

PodmanNotFound(t)
t.Fatal("expected to skip test")
}

func TestSkip_CommandNotFound(t *testing.T) {
CommandNotFound(t, "doesnotexist")
t.Fatal("expected to skip test")
}

func TestSkip_MinimumCores(t *testing.T) {
MinimumCores(t, 200)
t.Fatal("expected to skip test")
}

func TestSkip_MaximumCores(t *testing.T) {
MaximumCores(t, 2)
t.Fatal("expected to skip test")
}

func TestSkip_CgroupsVersion(t *testing.T) {
CgroupsVersion(t, 1)
t.Fatal("expected to skip test")
}

func TestSkip_EnvironmentVariableSet(t *testing.T) {
t.Setenv("EXAMPLE", "value")

EnvironmentVariableSet(t, "EXAMPLE")
t.Fatal("expected to skip test")
}

func TestSkip_EnvironmentVariableNotSet(t *testing.T) {
EnvironmentVariableNotSet(t, "DOESNOTEXIST")
t.Fatal("expected to skip test")
}

func TestSkip_EnvironmentVariableMatches(t *testing.T) {
t.Setenv("EXAMPLE", "foo")

EnvironmentVariableMatches(t, "EXAMPLE", "bar", "foo", "baz")
t.Fatal("expected to skip test")
}

func TestSkip_EnvironmentVariableNotMatches(t *testing.T) {
t.Setenv("EXAMPLE", "other")

EnvironmentVariableNotMatches(t, "EXAMPLE", "bar", "foo", "baz")
t.Fatal("expected to skip test")
}

0 comments on commit 79592fd

Please sign in to comment.