qac

package module
v0.5.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 26, 2026 License: Apache-2.0 Imports: 19 Imported by: 0

README

qac

CI Linux CI Windows PkgGoDev Go Report Card

qac is a Go library to test end to end command line tools.

A test plan is written in YAML format.

preconditions:
  fs:
    - file: ../go.mod
specs:
  cat:
    command:
      working_dir: ../
      cli: cat go.mod
    expectations:
      status:
        equals_to: 0
      output:
        stdout:
          equals_to_file: ../go.mod

Quick start

Go tests (idiomatic)
import (
  "testing"
  "github.com/enr/qac"
)

func TestExecution(t *testing.T) {
  qac.NewLauncher().ExecuteFileT(t, "plan.yaml")
}

ExecuteFileT calls t.Fatalf on load errors and t.Errorf for every spec failure — no boilerplate needed.

Go tests (manual report)
func TestExecution(t *testing.T) {
  launcher := qac.NewLauncher()
  report, err := launcher.ExecuteFile("plan.yaml")
  if err != nil {
    t.Fatal(err)
  }
  reporter := qac.NewTestLogsReporter(t)
  reporter.Publish(report)
  for _, err := range report.AllErrors() {
    t.Errorf("error %v", err)
  }
}
Programmatic usage
command := qac.Command{
  Exe: "echo",
  Args: []string{"foo"},
}

stdErrEmpty := true
expectations := qac.Expectations{
  StatusAssertion: qac.StatusAssertion{
    EqualsTo: new(int), // 0
  },
  OutputAssertions: qac.OutputAssertions{
    Stdout: qac.OutputAssertion{EqualsTo: "foo"},
    Stderr: qac.OutputAssertion{IsEmpty: &stdErrEmpty},
  },
}

plan := qac.TestPlan{
  Specs: map[string]qac.Spec{
    "echo": {Command: command, Expectations: expectations},
  },
}

report := qac.NewLauncher().Execute(plan)
for _, block := range report.Blocks() {
  for _, entry := range block.Entries() {
    fmt.Printf(" - %s %s %v\n", entry.Kind().String(), entry.Description(), entry.Errors())
  }
}
Fluent builder API
plan := qac.NewPlan().
  Setup(qac.ShellCmd("mkdir -p /tmp/mytest")).
  Teardown(qac.ShellCmd("rm -rf /tmp/mytest")).
  Spec("write-file", qac.NewSpec().
    Command(qac.ShellCmd("echo hello > /tmp/mytest/out.txt")).
    ExpectStatus(0).
    Build()).
  Spec("read-file", qac.NewSpec().
    Command(qac.Cmd("cat", "/tmp/mytest/out.txt")).
    ExpectStatus(0).
    ExpectStdout(qac.Contains("hello")).
    Build()).
  Build()

qac.NewLauncher().ExecuteT(t, plan)

YAML plan reference

Top-level structure
include:           # merge specs from other plan files
vars:              # plan-level variables
preconditions:     # halt the plan if these fail
setup:             # commands to run before any spec
teardown:          # commands to run after all specs (always runs)
specs:             # the tests
Command fields

Every command block (in specs, setup, and teardown) accepts the following fields.

cli vs exe+args

cli runs the value through the system shell ($SHELL -c on Unix, cmd /C on Windows) — shell features like pipes, redirects, and globs work. exe + args starts the process directly without a shell. The two are mutually exclusive.

command:
  cli: cat /etc/hosts | grep localhost   # shell — pipes work

command:
  exe: /usr/bin/grep
  args: [localhost, /etc/hosts]          # direct — no shell
working_dir

Sets the working directory for the command. Relative paths are resolved from the directory that contains the YAML file. ~ is expanded to the current user's home directory.

command:
  working_dir: ~/projects/myapp   # ~ expansion
  cli: make test

command:
  working_dir: ../                # relative to the plan file
  cli: go build ./...

When working_dir is omitted the directory of the plan file is used.

env

Injects extra environment variables into the command process without touching the parent environment. Useful for credentials, feature flags, or locale settings.

command:
  cli: ./myapp --config config.yaml
  env:
    APP_ENV: test
    LOG_LEVEL: debug
timeout

Maximum wall-clock time to wait for the command. Accepts any duration string understood by Go's time.ParseDuration (e.g. "30s", "1m30s", "500ms"). When the timeout expires the spec is recorded as TIMEOUT and the command's output is not checked.

command:
  cli: curl -sf http://localhost:8080/health
  timeout: 10s
stdin / stdin_file

Pipe a string literal or a file's contents to the command's standard input. stdin and stdin_file are mutually exclusive.

command:
  exe: cat
  stdin: "hello world"        # inline string

command:
  cli: wc -l
  stdin_file: ./testdata/input.txt   # contents of a file
ext (platform-specific extension)

ext appends a platform-specific suffix to exe at runtime, based on runtime.GOOS. Use it to write a single plan that runs on both Unix and Windows without duplicating specs.

specs:
  run-tool:
    command:
      exe: ./bin/mytool
      ext:
        windows: .exe   # becomes ./bin/mytool.exe on Windows
        unix: ""        # no suffix on Unix (can be omitted)
      args: [--version]
    expectations:
      status:
        equals_to: 0

ext is also accepted on file and filesystem assertions to make path checks cross-platform:

expectations:
  fs:
    - file: ./bin/mytool
      ext:
        windows: .exe
Preconditions

Preconditions are checked before the plan (or spec) runs. If any check fails, the plan stops (or the spec is skipped). Preconditions do not affect teardown.

preconditions:
  fs:
    - file: /etc/resolv.conf       # assert file exists
    - directory: ./output          # assert directory exists
    - file: ./lock                 # assert file does NOT exist
      exists: false
    - file: /etc/resolv.conf
      contains_all:                # assert file content
        - nameserver

Specs can have their own preconditions block with the same syntax.

Setup and teardown

Commands listed under setup run before specs (or before a single spec); those under teardown run after. Teardown always runs, even if setup or specs fail. Plan-level setup stops the plan on first failure; spec-level setup stops that spec only.

setup:
  - cli: mkdir -p /tmp/testdata

teardown:
  - cli: rm -rf /tmp/testdata

specs:
  write-and-verify:
    setup:
      - cli: sh -c "echo test-data > /tmp/testdata/input.txt"
    teardown:
      - cli: rm -f /tmp/testdata/input.txt
    command:
      cli: cat /tmp/testdata/input.txt
    expectations:
      output:
        stdout:
          equals_to: test-data
Retries

Use retries (number of extra attempts) and retry_delay (wait between attempts) to handle flaky tests. Only the outcome of the final attempt is recorded; intermediate failures emit an info entry.

specs:
  flaky-service:
    retries: 3
    retry_delay: 2s
    command:
      cli: curl -sf http://localhost:8080/health
    expectations:
      status:
        equals_to: 0
Skip and skip_if

skip: true unconditionally skips a spec. skip_if skips conditionally based on environment variables.

specs:
  linux-only:
    skip_if:
      env_set: WINDOWS_CI        # skip when the variable is defined (any value)
    command:
      cli: ls /etc

  integration:
    skip_if:
      env_value:
        RUN_INTEGRATION: "false" # skip when the variable equals this value
    command:
      cli: ./integration-test.sh
Tags

Assign tags to specs to run or exclude subsets at call time.

specs:
  unit-check:
    tags: [fast, unit]
    command:
      cli: ./check-unit

  slow-check:
    tags: [slow, integration]
    command:
      cli: ./check-integration

Filter at call time:

// Run only specs tagged "fast"
launcher.ExecuteFileT(t, "plan.yaml", qac.WithTags("fast"))

// Skip specs tagged "slow"
launcher.ExecuteFileT(t, "plan.yaml", qac.SkipTags("slow"))
FailFast

Stop after the first failing spec (teardown still runs):

launcher.ExecuteFileT(t, "plan.yaml", qac.FailFast())
Include

Merge specs from another plan file. Included specs that share a name with the base plan are ignored (base wins). Vars from included files become defaults.

include:
  - ../shared/common.yaml

specs:
  my-extra-spec:
    command:
      cli: echo extra
    expectations:
      status:
        equals_to: 0

Merge semantics:

  • vars: included vars are defaults; base vars override.
  • preconditions: included checks run first.
  • setup: included commands run first.
  • teardown: included commands run last.
  • specs: included specs not already in base are appended.
Variables

Define plan-level variables under vars and reference them with ${name}. Environment variables are available as ${env.NAME}.

vars:
  bin: ./myapp
  output_dir: /tmp/mytest

specs:
  run:
    command:
      cli: ${bin} --output ${output_dir}
    expectations:
      status:
        equals_to: 0

  check-env:
    command:
      cli: echo ${env.HOME}
    expectations:
      status:
        equals_to: 0
Duration assertions

expectations.duration asserts on the wall-clock execution time of the command. Both min and max accept any duration string understood by Go's time.ParseDuration (e.g. "500ms", "2s", "1m30s"). Either field can be omitted.

specs:
  fast-query:
    command:
      cli: ./myapp query --cached
    expectations:
      status:
        equals_to: 0
      duration:
        max: 200ms   # must complete within 200 ms

  slow-build:
    command:
      cli: make all
    expectations:
      status:
        equals_to: 0
      duration:
        min: 1s      # sanity-check: something actually ran
        max: 5m      # upper bound for CI timeout budgets

Note: timeout (under command) stops the process when the limit is reached; duration.max (under expectations) lets the command finish and then fails the spec if it took too long.

Working with the report

Execute and ExecuteFile return a *TestExecutionReport. Three convenience methods cover the most common test-integration patterns.

Summary()

Returns a one-line human-readable result string — useful as a test log header:

report, _ := launcher.ExecuteFile("plan.yaml")
t.Log(report.Summary()) // "3/5 specs passed (1 skipped)"
FailedSpecs()

Returns the names of the specs that failed, in execution order. Use it to log or assert on specific failures:

if failed := report.FailedSpecs(); len(failed) > 0 {
  t.Errorf("failing specs: %v", failed)
}
FailWith(t)

Calls t.Errorf for every error in the report, prefixed with the spec phase so failures are easy to locate in test output. It is the manual-report equivalent of ExecuteFileT:

report, err := launcher.ExecuteFile("plan.yaml")
if err != nil {
  t.Fatal(err)
}
t.Log(report.Summary())
report.FailWith(t)

Combining all three with a reporter for full visibility:

report, err := launcher.ExecuteFile("plan.yaml", qac.WithTags("fast"))
if err != nil {
  t.Fatal(err)
}
reporter := qac.NewTestLogsReporter(t)
reporter.Publish(report)
t.Log(report.Summary())
if failed := report.FailedSpecs(); len(failed) > 0 {
  t.Logf("failed specs: %v", failed)
}
report.FailWith(t)

Reporters

A Reporter publishes a *TestExecutionReport for human consumption. The interface has a single method:

type Reporter interface {
  Publish(report *TestExecutionReport) error
}

Two built-in implementations are provided.

NewTestLogsReporter(t)

Writes to Go's test log via t.Logf. Each spec block is printed as a labelled line with its duration and status (OK / KO / SKIP); errors and skipped reasons appear as indented sub-lines. Output is only shown by go test when the test fails or -v is used.

func TestCLI(t *testing.T) {
  report, err := qac.NewLauncher().ExecuteFile("plan.yaml")
  if err != nil {
    t.Fatal(err)
  }
  reporter := qac.NewTestLogsReporter(t)
  reporter.Publish(report)
  report.FailWith(t)
}

Example output (with -v or on failure):

[1/3] cat                                    (2ms) OK
[2/3] mkdir                                  (1ms) OK
[3/3] rm                                     (0ms) KO
  | KO status: expected 0, got 1
NewConsoleReporter()

Writes the same format to stdout via fmt.Printf. Use it in standalone programs or scripts that run qac outside of go test.

report := qac.NewLauncher().Execute(plan)
qac.NewConsoleReporter().Publish(report)
Custom reporter

Implement the Reporter interface to produce any output format you need (JSON, JUnit XML, structured logs, etc.):

type jsonReporter struct{}

func (r *jsonReporter) Publish(report *qac.TestExecutionReport) error {
  return json.NewEncoder(os.Stdout).Encode(map[string]interface{}{
    "summary":      report.Summary(),
    "failed_specs": report.FailedSpecs(),
    "success":      report.Success(),
  })
}

Run options reference

Option Description
WithTags(tags...) Run only specs that have at least one of the given tags
SkipTags(tags...) Skip specs that have at least one of the given tags
FailFast() Stop after the first spec failure

All options work with Execute, ExecuteFile, ExecuteT, ExecuteFileT, DryRun, and ListSpecs.

Launcher API reference

Method Description
Execute(plan, opts...) Run a TestPlan built in Go
ExecuteFile(path, opts...) Load and run a YAML plan file
ExecuteT(t, plan, opts...) Like Execute; calls t.Errorf on failures
ExecuteFileT(t, path, opts...) Like ExecuteFile; calls t.Fatalf on load errors
DryRun(plan, opts...) Validate structure and report what would run, without executing any commands
ListSpecs(plan, opts...) Return the names of specs that would run (after tag/skip filtering)
DryRun

DryRun validates the plan and reports which specs would run or be skipped, without executing commands or touching the filesystem. Useful to verify tag filters and configuration before a real run.

report := launcher.DryRun(plan, qac.WithTags("fast"))
for _, block := range report.Blocks() {
  for _, entry := range block.Entries() {
    fmt.Println(entry.Kind(), entry.Description())
  }
}
ListSpecs

ListSpecs returns the names of specs that would actually run after applying options, in execution order.

names := launcher.ListSpecs(plan, qac.WithTags("fast"))
fmt.Println(names) // ["spec-a", "spec-b"]

Error classification

Errors returned by AllErrors() or collected via FailWith / ExecuteFileT may be *qac.Error values that carry an ErrorKind for programmatic classification. Use errors.As to inspect them:

import "errors"

for _, err := range report.AllErrors() {
  var qErr *qac.Error
  if errors.As(err, &qErr) {
    switch qErr.Kind {
    case qac.KindAssertionFailure:
      // an expectation was not met (wrong status, unexpected output, …)
    case qac.KindInfrastructure:
      // system-level failure: file I/O, path resolution, working dir missing
    case qac.KindConfiguration:
      // the plan is invalid: unknown YAML field, cli+exe both set, bad duration string
    }
  }
}
Kind When it occurs
KindAssertionFailure A spec's expectation was not met
KindInfrastructure File I/O error, unresolvable path, missing working directory
KindConfiguration Invalid plan: unknown YAML field, mutually exclusive fields, unparseable duration

Not every error is a *qac.Error; plain error values may also appear. Always guard with errors.As before accessing Kind.

License

Apache 2.0 - see LICENSE file.

Copyright 2020-TODAY qac contributors

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Bool added in v0.5.0

func Bool(v bool) *bool

Bool returns a pointer to v. Use it to set *bool fields in struct literals without a named intermediate variable:

qac.OutputAssertion{IsEmpty: qac.Bool(true)}
qac.FileAssertion{Exists: qac.Bool(false)}

func Int added in v0.5.0

func Int(v int) *int

Int returns a pointer to v. Use it to set *int fields in struct literals without a named intermediate variable:

qac.StatusAssertion{EqualsTo: qac.Int(0)}
qac.OutputAssertion{LineCount: qac.Int(5)}

Types

type AssertionResult

type AssertionResult struct {
	// contains filtered or unexported fields
}

AssertionResult is the container of verification results.

func (*AssertionResult) Description

func (r *AssertionResult) Description() string

Description is the textual representation of the assertion.

func (*AssertionResult) Errors

func (r *AssertionResult) Errors() []error

Errors returns the errors list.

func (*AssertionResult) Success

func (r *AssertionResult) Success() bool

Success returns if an assertion completed with no error.

type Command

type Command struct {
	WorkingDir string `yaml:"working_dir"`
	// Cli is a shell command line. The system shell interprets it, so pipes,
	// redirects, and globs work. Mutually exclusive with exe.
	Cli string `yaml:"cli"`
	// Exe is the path or name of the executable. The process is started
	// directly without a shell. Mutually exclusive with cli.
	Exe string            `yaml:"exe"`
	Env map[string]string `yaml:"env"`
	// Extension is appended to Exe based on the runtime OS (e.g. ".exe" on Windows).
	Extension FileExtension `yaml:"ext"`
	// Args holds arguments passed directly to Exe. Only meaningful when Exe is set.
	Args []string `yaml:"args"`
	// Timeout is the maximum wall-clock time to wait; parsed by time.ParseDuration
	// (e.g. "30s", "1m"). Zero or empty means no timeout.
	Timeout string `yaml:"timeout"`
	// Stdin is an inline string piped to the command's standard input.
	Stdin string `yaml:"stdin"`
	// StdinFile is a path to a file whose contents are piped to standard input.
	StdinFile string `yaml:"stdin_file"`
}

Command represents the command under test.

Use either cli or exe+args — setting both is a configuration error.

  • cli runs the value through the system shell ($SHELL -c on Unix, cmd /C on Windows). Shell features such as pipes (|), redirects (>), globs (*), and variable expansion are available. Convenient for simple one-liners. Avoid when any part of the command line comes from untrusted input: shell injection is possible.

  • exe + args starts the process directly without a shell. Arguments are passed verbatim to the OS; no quoting, globbing, or expansion occurs. Prefer this form when shell features are not needed, especially with values that originate from user input or external data.

func Cmd added in v0.5.0

func Cmd(exe string, args ...string) Command

Cmd creates a Command that runs the given executable directly (no shell).

func ShellCmd added in v0.5.0

func ShellCmd(cli string) Command

ShellCmd creates a Command that runs the given shell command line.

func (Command) String

func (c Command) String() string

type DirectoryAssertion

type DirectoryAssertion struct {
	Path string `yaml:"path"`
	// Exists defaults to true (nil): check that the directory exists.
	// Set to false to assert the directory must not exist.
	Exists          *bool    `yaml:"exists"`
	EqualsTo        string   `yaml:"equals_to"`
	ContainsAny     []string `yaml:"contains_any"`
	ContainsAll     []string `yaml:"contains_all"`
	ContainsExactly []string `yaml:"contains_exactly"`
}

DirectoryAssertion is an assertion on a given directory.

type DurationAssertion added in v0.5.0

type DurationAssertion struct {
	Max string `yaml:"max"`
	Min string `yaml:"min"`
}

DurationAssertion asserts that the command's wall-clock execution time is within the specified bounds. Both fields accept any string accepted by time.ParseDuration (e.g. "2s", "500ms", "1m30s").

type Error added in v0.4.0

type Error struct {
	Kind  ErrorKind
	Cause error
	// contains filtered or unexported fields
}

Error is a structured error that carries a kind for programmatic classification. It wraps the underlying cause so errors.As / errors.Is work through the chain.

func (*Error) Error added in v0.4.0

func (e *Error) Error() string

func (*Error) Unwrap added in v0.4.0

func (e *Error) Unwrap() error

type ErrorKind added in v0.4.0

type ErrorKind int

ErrorKind categorises an Error so consumers can distinguish failure modes.

const (
	// KindAssertionFailure means an assertion's condition was not met.
	KindAssertionFailure ErrorKind = iota
	// KindInfrastructure means a system-level operation failed (file I/O, path resolution).
	KindInfrastructure
	// KindConfiguration means the test plan itself is invalid (YAML, unknown fields, wrong field usage).
	KindConfiguration
)

type Expectations

type Expectations struct {
	StatusAssertion      StatusAssertion       `yaml:"status"`
	OutputAssertions     OutputAssertions      `yaml:"output"`
	FileSystemAssertions []FileSystemAssertion `yaml:"fs"`
	DurationAssertion    DurationAssertion     `yaml:"duration"`
}

Expectations is the aggregate of the final assertions on the command executed.

type FileAssertion

type FileAssertion struct {
	Path      string        `yaml:"path"`
	Extension FileExtension `yaml:"ext"`
	// Exists defaults to true (nil): check that the file exists.
	// Set to false to assert the file must not exist.
	Exists           *bool    `yaml:"exists"`
	EqualsTo         string   `yaml:"equals_to"`
	TextEqualsTo     string   `yaml:"text_equals_to"`
	ContainsAny      []string `yaml:"contains_any"`
	ContainsAll      []string `yaml:"contains_all"`
	ContainsMatching string   `yaml:"contains_matching"`
}

FileAssertion is an assertion on a given file.

type FileExtension

type FileExtension struct {
	Windows string `yaml:"windows"`
	Unix    string `yaml:"unix"`
}

FileExtension is added as suffix to file assertions' path and command's exe values based on runtime.GOOS

type FileSystemAssertion

type FileSystemAssertion struct {
	File      string        `yaml:"file"`
	Extension FileExtension `yaml:"ext"`
	Directory string        `yaml:"directory"`
	// Exists defaults to true when omitted (nil): the assertion checks the path exists.
	// Set explicitly to false to assert the path must not exist.
	Exists   *bool  `yaml:"exists"`
	EqualsTo string `yaml:"equals_to"`
	// Only for files
	TextEqualsTo     string   `yaml:"text_equals_to"`
	ContainsAny      []string `yaml:"contains_any"`
	ContainsAll      []string `yaml:"contains_all"`
	ContainsExactly  []string `yaml:"contains_exactly"`
	ContainsMatching string   `yaml:"contains_matching"`
}

FileSystemAssertion is an assertion on files and directories.

type Launcher

type Launcher struct {
	// contains filtered or unexported fields
}

Launcher checks the results respect expectations.

func NewLauncher

func NewLauncher() *Launcher

NewLauncher creates a default implementation for Launcher.

func (*Launcher) DryRun added in v0.5.0

func (l *Launcher) DryRun(plan TestPlan, opts ...RunOption) *TestExecutionReport

DryRun validates the plan structure and reports which specs would run or be skipped, without executing any commands or touching the filesystem. The returned report uses InfoType entries for specs that would run and SkippedType entries for those that would not. Config errors produce ErrorType entries just as they do in Execute.

func (*Launcher) Execute

func (l *Launcher) Execute(plan TestPlan, opts ...RunOption) *TestExecutionReport

Execute runs all specs in plan and returns a report of the results. The report is never nil; spec-level failures are recorded inside it and do not cause an error return. Pass RunOption values (WithTags, SkipTags, FailFast) to control which specs run. For use in Go tests prefer ExecuteT, which forwards spec failures to t.Errorf.

func (*Launcher) ExecuteFile

func (l *Launcher) ExecuteFile(path string, opts ...RunOption) (*TestExecutionReport, error)

ExecuteFile loads a test plan from path and runs it. Returns a non-nil error when the file cannot be read or parsed; spec-level failures are recorded in the report and do not cause an error return.

func (*Launcher) ExecuteFileT added in v0.5.0

func (l *Launcher) ExecuteFileT(t testing.TB, path string, opts ...RunOption) *TestExecutionReport

ExecuteFileT loads the YAML plan at path, runs all specs, and integrates with Go's testing.TB: load and parse errors call t.Fatalf (stopping the test immediately); each spec failure calls t.Errorf. The returned report is never nil. Pass RunOption values (WithTags, SkipTags, FailFast) to filter specs. It is the idiomatic single-line form for Go tests:

qac.NewLauncher().ExecuteFileT(t, "plan.yaml")

func (*Launcher) ExecuteT added in v0.5.0

func (l *Launcher) ExecuteT(t testing.TB, plan TestPlan, opts ...RunOption) *TestExecutionReport

ExecuteT runs plan and integrates with Go's testing.TB: each spec failure calls t.Errorf. The returned report is never nil. Pass RunOption values (WithTags, SkipTags, FailFast) to filter specs. It is the idiomatic single-line form when constructing plans in Go:

qac.NewLauncher().ExecuteT(t, plan)

func (*Launcher) ListSpecs added in v0.5.0

func (l *Launcher) ListSpecs(plan TestPlan, opts ...RunOption) []string

ListSpecs returns the names of specs in execution order after applying the given RunOptions (tag filters, static skip conditions). Only specs that would actually run are included; already-skipped specs are omitted.

type OutputAssertion

type OutputAssertion struct {
	EqualsTo     string `yaml:"equals_to"`
	EqualsToFile string `yaml:"equals_to_file"`
	// output is trimmed
	StartsWith string `yaml:"starts_with"`
	// output is trimmed
	EndsWith     string   `yaml:"ends_with"`
	IsEmpty      *bool    `yaml:"is_empty"`
	ContainsAny  []string `yaml:"contains_any"`
	ContainsAll  []string `yaml:"contains_all"`
	ContainsNone []string `yaml:"contains_none"`
	// ContainsLine requires at least one line to equal the given string exactly (after trimming).
	ContainsLine string `yaml:"contains_line"`
	// LineCount requires the output to have exactly N non-empty lines.
	LineCount *int `yaml:"line_count"`
	// LineCountGte requires the output to have at least N non-empty lines.
	LineCountGte *int `yaml:"line_count_gte"`
	// Matches requires the entire trimmed output to match the regular expression.
	Matches string `yaml:"matches"`
	// NotMatches requires the entire trimmed output NOT to match the regular expression.
	NotMatches string `yaml:"not_matches"`
	// contains filtered or unexported fields
}

OutputAssertion is an assertion on the output of a command: namely standard output and standard error.

type OutputAssertions

type OutputAssertions struct {
	Stdout OutputAssertion `yaml:"stdout"`
	Stderr OutputAssertion `yaml:"stderr"`
}

OutputAssertions is the aggregate of stdout and stderr assertions.

type OutputMatcher added in v0.5.0

type OutputMatcher func(*OutputAssertion)

OutputMatcher is a function that configures an OutputAssertion. Use the constructor functions (Contains, Equals, Empty, etc.) to create matchers.

func Contains added in v0.5.0

func Contains(substrings ...string) OutputMatcher

Contains requires the output to contain all of the given substrings.

func ContainsAnyOf added in v0.5.0

func ContainsAnyOf(substrings ...string) OutputMatcher

ContainsAnyOf requires the output to contain at least one of the given substrings.

func ContainsExactLine added in v0.5.0

func ContainsExactLine(line string) OutputMatcher

ContainsExactLine requires at least one line to equal the given string exactly (after trimming).

func Empty added in v0.5.0

func Empty() OutputMatcher

Empty requires the output to be empty.

func EndsWith added in v0.5.0

func EndsWith(suffix string) OutputMatcher

EndsWith requires the output to end with the given suffix (after trimming).

func Equals added in v0.5.0

func Equals(s string) OutputMatcher

Equals requires the output to equal the given string exactly (after trimming).

func HasLineCount added in v0.5.0

func HasLineCount(n int) OutputMatcher

HasLineCount requires the output to have exactly n non-empty lines.

func HasLineCountAtLeast added in v0.5.0

func HasLineCountAtLeast(n int) OutputMatcher

HasLineCountAtLeast requires the output to have at least n non-empty lines.

func Matches added in v0.5.0

func Matches(re string) OutputMatcher

Matches requires the entire trimmed output to match the given regular expression.

func NotContains added in v0.5.0

func NotContains(substrings ...string) OutputMatcher

NotContains requires the output to contain none of the given substrings.

func NotMatches added in v0.5.0

func NotMatches(re string) OutputMatcher

NotMatches requires the entire trimmed output NOT to match the given regular expression.

func StartsWith added in v0.5.0

func StartsWith(prefix string) OutputMatcher

StartsWith requires the output to start with the given prefix (after trimming).

type PlanBuilder added in v0.5.0

type PlanBuilder struct {
	// contains filtered or unexported fields
}

PlanBuilder is a fluent builder for TestPlan.

func NewPlan added in v0.5.0

func NewPlan() *PlanBuilder

NewPlan creates a new PlanBuilder.

func (*PlanBuilder) Build added in v0.5.0

func (b *PlanBuilder) Build() TestPlan

Build returns the constructed TestPlan value.

func (*PlanBuilder) Setup added in v0.5.0

func (b *PlanBuilder) Setup(cmds ...Command) *PlanBuilder

Setup appends plan-level setup commands.

func (*PlanBuilder) Spec added in v0.5.0

func (b *PlanBuilder) Spec(name string, spec interface{}) *PlanBuilder

Spec adds a spec to the plan under the given name. The spec argument may be a *SpecBuilder (Build() is called automatically) or a Spec value directly.

func (*PlanBuilder) Teardown added in v0.5.0

func (b *PlanBuilder) Teardown(cmds ...Command) *PlanBuilder

Teardown appends plan-level teardown commands.

func (*PlanBuilder) Var added in v0.5.0

func (b *PlanBuilder) Var(key, value string) *PlanBuilder

Var sets a plan-level variable.

type Preconditions

type Preconditions struct {
	FileSystemAssertions []FileSystemAssertion `yaml:"fs"`
}

Preconditions represents the minimal requirements for a plan or a single spec to start.

type ReportBlock

type ReportBlock struct {
	// contains filtered or unexported fields
}

ReportBlock is an aggregate of report entries classified on the phase.

func (*ReportBlock) Duration added in v0.4.0

func (r *ReportBlock) Duration() time.Duration

Duration returns how long this block took to execute.

func (*ReportBlock) Entries

func (r *ReportBlock) Entries() []ReportEntry

Entries returns the entries of a block.

func (*ReportBlock) Failed added in v0.5.0

func (r *ReportBlock) Failed() bool

Failed returns true if this block contains at least one error or timeout entry.

func (*ReportBlock) Index added in v0.4.0

func (r *ReportBlock) Index() int

Index returns the 1-based ordinal of this spec in the execution order (0 for non-spec blocks).

func (*ReportBlock) Phase

func (r *ReportBlock) Phase() string

Phase returns the phase of a block.

func (*ReportBlock) Skipped added in v0.5.0

func (r *ReportBlock) Skipped() bool

Skipped returns true if this spec block was skipped (by skip field, skip_if, or tag filter).

func (*ReportBlock) StartedAt added in v0.4.0

func (r *ReportBlock) StartedAt() time.Time

StartedAt returns when execution of this block started.

func (*ReportBlock) TimedOut added in v0.5.0

func (r *ReportBlock) TimedOut() bool

TimedOut returns true if the command in this block exceeded its configured timeout.

func (*ReportBlock) Total added in v0.4.0

func (r *ReportBlock) Total() int

Total returns the total number of specs in the plan (0 for non-spec blocks).

type ReportEntry

type ReportEntry struct {
	// contains filtered or unexported fields
}

ReportEntry is a single unit of information in a report.

func (*ReportEntry) Description

func (r *ReportEntry) Description() string

Description is the textual representation of a report entry.

func (*ReportEntry) Errors

func (r *ReportEntry) Errors() []error

Errors returns the errors list in a report entry.

func (*ReportEntry) Kind

func (r *ReportEntry) Kind() ReportEntryType

Kind returns the type of a report entry: error, info, success, skipped...

type ReportEntryType

type ReportEntryType int8

ReportEntryType represents the type of a report entry.

const (

	// ErrorType is report entry error
	ErrorType ReportEntryType
	// InfoType is report entry info
	InfoType
	// SuccessType is report entry success
	SuccessType
	// SkippedType means the spec was not executed because preconditions were not met
	SkippedType
	// TimedOutType means the command exceeded its configured timeout
	TimedOutType
)

func (ReportEntryType) String added in v0.5.0

func (t ReportEntryType) String() string

String returns a human-readable name for the entry type.

type Reporter

type Reporter interface {
	Publish(report *TestExecutionReport) error
}

Reporter is the interface for components publishing the report.

func NewConsoleReporter

func NewConsoleReporter() Reporter

NewConsoleReporter returns a Reporter implementation writing to the stdout.

func NewTestLogsReporter

func NewTestLogsReporter(t *testing.T) Reporter

NewTestLogsReporter returns a Reporter implementation using the testing log.

type RunOption added in v0.5.0

type RunOption func(*runConfig)

RunOption configures a single Execute or ExecuteFile call.

func FailFast added in v0.5.0

func FailFast() RunOption

FailFast stops execution after the first spec failure. Subsequent specs are not run; plan-level teardown still executes.

func SkipTags added in v0.5.0

func SkipTags(tags ...string) RunOption

SkipTags excludes specs that have at least one of the given tags. Excluded specs are reported as skipped.

func WithTags added in v0.5.0

func WithTags(tags ...string) RunOption

WithTags restricts execution to specs that have at least one of the given tags. Specs with no matching tag are reported as skipped.

type SkipCondition added in v0.5.0

type SkipCondition struct {
	// EnvSet skips the spec when the named environment variable is defined (regardless of its value).
	EnvSet string `yaml:"env_set"`
	// EnvValue skips the spec when any named variable equals its specified value.
	EnvValue map[string]string `yaml:"env_value"`
}

SkipCondition holds the conditions under which a spec is skipped.

type Spec

type Spec struct {
	Description string        `yaml:"description"`
	Tags        []string      `yaml:"tags"`
	Skip        bool          `yaml:"skip"`
	SkipIf      SkipCondition `yaml:"skip_if"`
	// Retries is the number of additional attempts after the first failure.
	// Zero (default) means no retry.
	Retries       int           `yaml:"retries"`
	RetryDelay    string        `yaml:"retry_delay"`
	Setup         []Command     `yaml:"setup"`
	Teardown      []Command     `yaml:"teardown"`
	Preconditions Preconditions `yaml:"preconditions"`
	Command       Command       `yaml:"command"`
	Expectations  Expectations  `yaml:"expectations"`
	// contains filtered or unexported fields
}

Spec is the single test.

func (Spec) ID

func (s Spec) ID() string

ID returns the dynamically created identifier for a spec.

type SpecBuilder added in v0.5.0

type SpecBuilder struct {
	// contains filtered or unexported fields
}

SpecBuilder is a fluent builder for Spec.

func NewSpec added in v0.5.0

func NewSpec() *SpecBuilder

NewSpec creates a new SpecBuilder.

func (*SpecBuilder) Build added in v0.5.0

func (b *SpecBuilder) Build() Spec

Build returns the constructed Spec value.

func (*SpecBuilder) Command added in v0.5.0

func (b *SpecBuilder) Command(cmd Command) *SpecBuilder

Command sets the command under test.

func (*SpecBuilder) Description added in v0.5.0

func (b *SpecBuilder) Description(d string) *SpecBuilder

Description sets the human-readable description for the spec.

func (*SpecBuilder) ExpectStatus added in v0.5.0

func (b *SpecBuilder) ExpectStatus(code int) *SpecBuilder

ExpectStatus asserts the command exits with the given status code.

func (*SpecBuilder) ExpectStatusGT added in v0.5.0

func (b *SpecBuilder) ExpectStatusGT(n int) *SpecBuilder

ExpectStatusGT asserts the exit status is greater than n.

func (*SpecBuilder) ExpectStatusLT added in v0.5.0

func (b *SpecBuilder) ExpectStatusLT(n int) *SpecBuilder

ExpectStatusLT asserts the exit status is less than n.

func (*SpecBuilder) ExpectStderr added in v0.5.0

func (b *SpecBuilder) ExpectStderr(matchers ...OutputMatcher) *SpecBuilder

ExpectStderr adds one or more matchers on the command's standard error.

func (*SpecBuilder) ExpectStdout added in v0.5.0

func (b *SpecBuilder) ExpectStdout(matchers ...OutputMatcher) *SpecBuilder

ExpectStdout adds one or more matchers on the command's standard output.

func (*SpecBuilder) Retries added in v0.5.0

func (b *SpecBuilder) Retries(n int) *SpecBuilder

Retries sets the number of additional attempts after the first failure.

func (*SpecBuilder) RetryDelay added in v0.5.0

func (b *SpecBuilder) RetryDelay(d string) *SpecBuilder

RetryDelay sets the delay between retries (e.g. "1s", "500ms").

func (*SpecBuilder) Setup added in v0.5.0

func (b *SpecBuilder) Setup(cmds ...Command) *SpecBuilder

Setup appends setup commands that run before this spec.

func (*SpecBuilder) Skip added in v0.5.0

func (b *SpecBuilder) Skip() *SpecBuilder

Skip marks this spec so it is always skipped.

func (*SpecBuilder) Tags added in v0.5.0

func (b *SpecBuilder) Tags(tags ...string) *SpecBuilder

Tags sets the tags for the spec.

func (*SpecBuilder) Teardown added in v0.5.0

func (b *SpecBuilder) Teardown(cmds ...Command) *SpecBuilder

Teardown appends teardown commands that run after this spec.

type StatusAssertion

type StatusAssertion struct {
	EqualsTo    *int `yaml:"equals_to"`
	GreaterThan *int `yaml:"greater_than"`
	LessThan    *int `yaml:"less_than"`
}

StatusAssertion represents an assertion on the status code returned from a command.

type TestExecutionReport

type TestExecutionReport struct {
	// contains filtered or unexported fields
}

TestExecutionReport is the full report on a test execution

func (*TestExecutionReport) AllErrors

func (r *TestExecutionReport) AllErrors() []error

AllErrors returns all errors in a report, without considering blocks or phases.

func (*TestExecutionReport) Blocks

func (r *TestExecutionReport) Blocks() []*ReportBlock

Blocks returns the blocks list in a full report.

func (*TestExecutionReport) FailWith added in v0.5.0

func (r *TestExecutionReport) FailWith(t *testing.T)

FailWith calls t.Errorf for every error in the report, prefixed with the block phase so failures are easy to locate. It is the idiomatic one-liner to fail a Go test when a qac plan has errors:

report.FailWith(t)

func (*TestExecutionReport) FailedSpecs added in v0.5.0

func (r *TestExecutionReport) FailedSpecs() []string

FailedSpecs returns the phase names of spec blocks that failed.

func (*TestExecutionReport) Success added in v0.5.0

func (r *TestExecutionReport) Success() bool

Success returns true when no block recorded an error or timeout.

func (*TestExecutionReport) Summary added in v0.5.0

func (r *TestExecutionReport) Summary() string

Summary returns a one-line human-readable description of the execution result, e.g. "3/5 specs passed" or "5/5 specs passed (2 skipped)".

type TestPlan

type TestPlan struct {
	Include       []string          `yaml:"include"`
	Vars          map[string]string `yaml:"vars"`
	Setup         []Command         `yaml:"setup"`
	Teardown      []Command         `yaml:"teardown"`
	Preconditions Preconditions     `yaml:"preconditions"`
	Specs         map[string]Spec   `yaml:"specs"`
	// contains filtered or unexported fields
}

TestPlan represents the full set of tests on a program.

func (*TestPlan) UnmarshalYAML added in v0.4.0

func (tp *TestPlan) UnmarshalYAML(value *yaml.Node) error

UnmarshalYAML preserves the declaration order of specs from the YAML source and rejects unknown fields.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL