gotest_labels

package module
v1.3.0 Latest Latest
Warning

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

Go to latest
Published: Apr 27, 2025 License: Apache-2.0 Imports: 11 Imported by: 0

README

gotest_labels

codecov Go Report Card

GoTestLabels enables the selection of test cases by labels from the testing function comments. The filter expression is based on the labelKey=value format, ||, &&, ! and parenthesis are supported. It is a tiny Go package with less than 1k NSCL go source code and no third party dependencies so it's easy to be equipped in any golang projects or testing frameworks.

Quick Start

Equip Your Golang Package

In the testing code, you can use this package with just one line of anonymous import code. Add one line of anonymous import code in one of the *_test.go source file:

import (
    "testing"
    _ "github.com/maxwu/gotest-labels/apply"
)

Add labels to classify test functions (including the test functions in other *_test.go files in the same package or its sub packages):

// @group=demo
// @env=dev
func TestExample(t *testing.T) {
    //...
}
Test Your Golang Package with Label Filter

Run the test with label filter, multiple label conditions can be combined using &&, ||, ! and parenthesis.

TEST_LABELS='group=demo&&!(env=prod)' go test -v -count=1 ./examples/simple/...
# Or, if only the pointed package is launched to test, the CLI flag can be used:
go test -v -count=1 ./examples/simple -labels="group=demo&&!(env=prod)"

Usage

Filter the tests with labels

Three ways are supported to use the package to filter tests, one is just to import the github.com/maxwu/gotestlabels/apply package in anonymous, which has an automatically init function to do the filtering. The other way is to explicitly import the github.com/maxwu/gotestlabels package and call the gotestlabels.MutateTestFuncsByLabels() function in your testing package's init function or TestMain function. If the parent package refers to a sub package underneath, adding the invocation of gotestlabels.MutateTestFuncsByLabels() in TestMain() function is the required safe way.

Use the simple way, only one line of anonymous import is needed:

_ "github.com/maxwu/gotest-labels/apply"

With the explicit way, one line of actual code is needed in the targeted testing package's init function:

import "github.com/maxwu/gotest-labels"

func init() {
    _ = gotest_labels.MutateTestFilterByLabels()
}

Or, if the parent package refers to a sub package underneath, here's the safe way:

// It's usually okay to import tooling package without identifier to simplify tests.
import . "github.com/maxwu/gotest-labels"

func TestMain(m *testing.M) {
    // The returned test case lists can be used to estimate the test costs or other tasks.
    _ = MutateTestFilterByLabels()
    os.Exit(m.Run())
}

Users can refer to the examples to see how to use the package with one line importing code or an explicit function call.

Add labels to your test cases

To add labels to your test cases, add a comment to the test function in @key=value format. Double slash or slash start are both supported.

If a key in function comment has no value, it's treated as a boolean true and matching condition key=true.

// @group=demo
// @env=dev
// @regression  It is treated as `@regression=true`
func TestExample(t *testing.T) {
    //...
}
Run Go Test with filter expression

The test label filter can be specified in env var or CLI args. The CLI args will overwrite env var if both are present and CLI args request all the launched packages to be equipped with gotest-labels via importing or TestMain call.

Using env variable TEST_LABELS to specify the labels to run. If sub packages are also equipped with gotest-labels, which means for each involved sub package, the gotest-labels package is imported by at least one test source file, then the CLI flag -labels can be appended to the end of go test command to replace the TEST_LABELS env var.

TEST_LABELS="group=demo" go test -v ./examples/simple/...
# Or, if sub packages are equipped with gotest-labels pkg,
go test -v ./examples/simple/... -labels "group=demo"

&&, ||, ! and parenthesis are supported in the label filter expression, e.g. TEST_LABELS='!group=demo&&env=integration'.

Compatibility

If there's no TEST_LABELS var or -labels flag passed in, the package will do nothing and go test runs normally.

If there are regex selectors like -run or -list, the labels will be applied after the regex selectors.

Due to go package loading mechanism, each involved package still needs to equip with the gotestlabels via one of the provided three ways even the wildcard your_package/... is used in CLI.

Examples

Here is an example of how to use GoTestLabels:

package yourpackage

import (
    "testing"
    _ "github.com/maxwu/gotestlabels/apply"
)

// Add labels to the test function code comment. These labels can be evaluated with conditional expression from filter.
// @group=demo
func TestExample(t *testing.T) {
    t.Log("Testing yourpackage.TestExample")
    // Your test code here
}

To run the above example, the CLI could be:

TEST_LABELS="group=demo" go test -v ./yourpackage

or,

go test -v -labels="group=demo" ./yourpackage

Given the tests in examples folder, the below CLI runs tests with label group=demo. In this sample CLI, only the TestSimpleAlpha and TestSimpleGamma cases are run and the TestSimpleBeta has no such label and will be skipped.

❯ TEST_LABELS="group=demo" go test -v ./examples/simple
=== RUN   TestSimpleAlpha
    demo_test.go:13: Testing examples.simple.TestSimpleAlpha
--- PASS: TestSimpleAlpha (0.00s)
=== RUN   TestSimpleGamma
    demo_test.go:25: Testing examples.simple.TestSimpleGamma
--- PASS: TestSimpleGamma (0.00s)
PASS
ok  	gotestlabels/examples/simple	0.267s

The filter expression supports &&, ||, ! and parenthesis. For example, the below OR condition selects 3 cases:

❯ go test -v ./examples/simple  -labels "group=demo||env=dev"
=== RUN   TestSimpleAlpha
    demo_test.go:13: Testing examples.simple.TestSimpleAlpha
--- PASS: TestSimpleAlpha (0.00s)
=== RUN   TestSimpleBeta
    demo_test.go:20: Testing examples.simple.TestSimpleBeta
--- PASS: TestSimpleBeta (0.00s)
=== RUN   TestSimpleGamma
    demo_test.go:26: Testing examples.simple.TestSimpleGamma
--- PASS: TestSimpleGamma (0.00s)
PASS
ok  	github.com/maxwu/gotest-labels/examples/simple	0.270s

Or, users can list the test cases with labels.

❯ go test -v ./examples/simple -list . -labels "group=demo"
TestSimpleAlpha
TestSimpleGamma
ok  	gotestlabels/examples/simple	0.268s
# Or, use the CLI flag
❯ TEST_LABELS="group=demo" go test -v ./examples/simple -list .
TestSimpleAlpha
TestSimpleGamma
ok  	gotestlabels/examples/simple	0.268s

To run the tests with label env=dev, the CLI could be:

❯ TEST_LABELS="env=dev" go test -v ./examples/simple -count=1
=== RUN   TestSimpleBeta
    demo_test.go:20: Testing examples.simple.TestSimpleBeta
--- PASS: TestSimpleBeta (0.00s)
PASS
ok  	gotestlabels/examples/simple	0.270s

Readers are kindly reminded to add -count=1 to the CLI since there's only env var changes so the tests shall be forced to rerun. Actually the test codes are rebuilt to new temporary binary file but the objective of this package is to offload readers from golang testing package internal details.

Limitation

The test running args are mutated by updating or adding a regex to matching test function names. Which means if two test functions share the same name in different packages, they are either both selected or both skipped. This is due to the go testing package regex filter mechanism. To mitigate it, the two packages with duplicated test name shall be launched separately. Reader could refer to below example to select package and its sub packages in CLI.

❯ go test -v .{,/pkg1,/pkg2}

The above CLI will run the tests in current path and its sub paths "./pkg1" and "./pkg2" only. The other sub packages are skipped.

Troubleshooting

  • Add -count=1 if only env vars are changed and the go test CLI still runs the same cases. It's due to go test internal mechanism that the same built binary is used since there's no code change in between.

  • When using ! as NOT operator, the expression shall be enclosed in single quotation marks to avoid being parsed as history expansion.

❯ go test -v ./examples/simple/...  -labels '!group=demo'
  • If it's expected to select test cases in the current package and all the sub packages, the sub packages shall also be equipped with the gotestlabels, e.g. by adding _ "github.com/maxwu/gotest-labels/apply" in one *_test.go file for each sub package. For one package, it's only needed in one test source file.

Background

This package attempts to offload users from the golang testing package internal implementation details and the the ASK parsing process steps and enable readers to filter test cases with labels. The go testing CLI provided the -run and -list flags to filter tests via regex against the test function names. However, sometimes it's not convenient to keep long function names as the convention or only selec tests via the linear matching mechanism. For example, if the tests are cataloged in multiple dimensions like TestAddByClickInDevForIntegration and TestAddByClickInProdForRegression. This package is designed to provide a multiple dimension test case selector with no actual coding maintenance efforts.

This small project is part of the approach to verify the generic idea that devops information could be injected without functional code change in all the devops stages. The gotest-labels inject testing control information into the go testing CLI running sessions. The author is working on other approaches to inject information to compilation steps as well.

Contributing

Contributions are welcome! Please feel free to submit a pull request or open an issue.

License

This project is licensed under the Apache License 2.0. See the LICENSE file for details.

Copyright [2025] [Max Wu] ​ Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0

Documentation

Index

Constants

View Source
const DefaultLabelValue = "true"

Variables

This section is empty.

Functions

func Evaluate added in v1.2.0

func Evaluate(node Node, labels TestLabels) bool

Evaluate traverses the AST and evaluates the expression against the provided labels. It returns true if the expression is satisfied. If the expression is nil, it always returns true.

func FindTestFuncs

func FindTestFuncs(testFiles []string, filterAST Node) (map[string]TestLabels, error)

Find all Test* functions with (t *testing.T) signature and matching the label filter in given test files It returns a map of function names to their labels

func MutateTestFilterByLabels

func MutateTestFilterByLabels() map[string]TestLabels

The actually exposed entrypoint to mutate the test functions by labels It can be called in the TestMain function of the test package. If the test command is running tests with wildcards for sub packages, either set the labels via the TEST_LABELS environment variable or set the labels in the TestMain function in every involved package The function returns the list of test functions that matched the labels as well. The result can be used to estimate the test costs or support the test operation/observability/report features.

func NewCliArgs

func NewCliArgs() *cliArgs

func ParseOSArgs added in v1.2.0

func ParseOSArgs() *cliArgs

Parse the os.Args for -run, -list, -json and the new added -labels flags in go test command.

Types

type Condition added in v1.2.0

type Condition struct {
	Key   string
	Value string
}

type LogicalOp added in v1.2.0

type LogicalOp struct {
	Operator string
	Children []Node
}

type Node added in v1.2.0

type Node any

func ParseLabelExp added in v1.2.0

func ParseLabelExp(input string) (Node, error)

The entry point for parsing label expressions It takes a string input and returns an AST representation of the expression or an error if the input is invalid.

type TestLabels

type TestLabels map[string]string

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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