...

Package packagestest

import "golang.org/x/tools/go/packages/packagestest"
Overview
Index

Overview ▾

Package packagestest creates temporary projects on disk for testing go tools on.

By changing the exporter used, you can create projects for multiple build systems from the same description, and run the same tests on them in many cases.

Example

As an example of packagestest use, consider the following test that runs the 'go list' command on the specified modules:

// TestGoList exercises the 'go list' command in module mode and in GOPATH mode.
func TestGoList(t *testing.T) { packagestest.TestAll(t, testGoList) }
func testGoList(t *testing.T, x packagestest.Exporter) {
	e := packagestest.Export(t, x, []packagestest.Module{
		{
			Name: "gopher.example/repoa",
			Files: map[string]interface{}{
				"a/a.go": "package a",
			},
		},
		{
			Name: "gopher.example/repob",
			Files: map[string]interface{}{
				"b/b.go": "package b",
			},
		},
	})
	defer e.Cleanup()

	cmd := exec.Command("go", "list", "gopher.example/...")
	cmd.Dir = e.Config.Dir
	cmd.Env = e.Config.Env
	out, err := cmd.Output()
	if err != nil {
		t.Fatal(err)
	}
	t.Logf("'go list gopher.example/...' with %s mode layout:\n%s", x.Name(), out)
}

TestGoList uses TestAll to exercise the 'go list' command with all exporters known to packagestest. Currently, packagestest includes exporters that produce module mode layouts and GOPATH mode layouts. Running the test with verbose output will print:

=== RUN   TestGoList
=== RUN   TestGoList/GOPATH
=== RUN   TestGoList/Modules
--- PASS: TestGoList (0.21s)
    --- PASS: TestGoList/GOPATH (0.03s)
        main_test.go:36: 'go list gopher.example/...' with GOPATH mode layout:
            gopher.example/repoa/a
            gopher.example/repob/b
    --- PASS: TestGoList/Modules (0.18s)
        main_test.go:36: 'go list gopher.example/...' with Modules mode layout:
            gopher.example/repoa/a
            gopher.example/repob/b

Variables

All is the list of known exporters. This is used by TestAll to run tests with all the exporters.

var All []Exporter

ErrUnsupported indicates an error due to an operation not supported on the current platform.

var ErrUnsupported = errors.New("operation is not supported")

GOPATH is the exporter that produces GOPATH layouts. Each "module" is put in it's own GOPATH entry to help test complex cases. Given the two files

golang.org/repoa#a/a.go
golang.org/repob#b/b.go

You would get the directory layout

/sometemporarydirectory
├── repoa
│   └── src
│       └── golang.org
│           └── repoa
│               └── a
│                   └── a.go
└── repob
    └── src
        └── golang.org
            └── repob
                └── b
                    └── b.go

GOPATH would be set to

/sometemporarydirectory/repoa;/sometemporarydirectory/repob

and the working directory would be

/sometemporarydirectory/repoa/src
var GOPATH = gopath{}

Modules is the exporter that produces module layouts. Each "repository" is put in it's own module, and the module file generated will have replace directives for all other modules. Given the two files

golang.org/repoa#a/a.go
golang.org/repob#b/b.go

You would get the directory layout

/sometemporarydirectory
├── repoa
│   ├── a
│   │   └── a.go
│   └── go.mod
└── repob
    ├── b
    │   └── b.go
    └── go.mod

and the working directory would be

/sometemporarydirectory/repoa
var Modules = modules{}

func BenchmarkAll

func BenchmarkAll(b *testing.B, f func(*testing.B, Exporter))

BenchmarkAll invokes the testing function once for each exporter registered in the All global. Each exporter will be run as a sub-test named after the exporter being used.

func MustCopyFileTree

func MustCopyFileTree(root string) map[string]interface{}

MustCopyFileTree returns a file set for a module based on a real directory tree. It scans the directory tree anchored at root and adds a Copy writer to the map for every file found. It skips copying files in nested modules. This is to enable the common case in tests where you have a full copy of the package in your testdata. This will panic if there is any kind of error trying to walk the file tree.

func TestAll

func TestAll(t *testing.T, f func(*testing.T, Exporter))

TestAll invokes the testing function once for each exporter registered in the All global. Each exporter will be run as a sub-test named after the exporter being used.

type Exported

Exported is returned by the Export function to report the structure that was produced on disk.

type Exported struct {
    // Config is a correctly configured packages.Config ready to be passed to packages.Load.
    // Exactly what it will contain varies depending on the Exporter being used.
    Config *packages.Config

    // Modules is the module description that was used to produce this exported data set.
    Modules []Module

    ExpectFileSet *token.FileSet // The file set used when parsing expectations

    Exporter Exporter // the exporter used
    // contains filtered or unexported fields
}

func Export

func Export(t testing.TB, exporter Exporter, modules []Module) *Exported

Export is called to write out a test directory from within a test function. It takes the exporter and the build system agnostic module descriptions, and uses them to build a temporary directory. It returns an Exported with the results of the export. The Exported.Config is prepared for loading from the exported data. You must invoke Exported.Cleanup on the returned value to clean up. The file deletion in the cleanup can be skipped by setting the skip-cleanup flag when invoking the test, allowing the temporary directory to be left for debugging tests.

If the Writer for any file within any module returns an error equivalent to ErrUnspported, Export skips the test.

func (*Exported) Cleanup

func (e *Exported) Cleanup()

Cleanup removes the temporary directory (unless the --skip-cleanup flag was set) It is safe to call cleanup multiple times.

func (*Exported) Expect

func (e *Exported) Expect(methods map[string]interface{}) error

Expect invokes the supplied methods for all expectation notes found in the exported source files.

All exported go source files are parsed to collect the expectation notes. See the documentation for expect.Parse for how the notes are collected and parsed.

The methods are supplied as a map of name to function, and those functions will be matched against the expectations by name. Notes with no matching function will be skipped, and functions with no matching notes will not be invoked. If there are no registered markers yet, a special pass will be run first which adds any markers declared with @mark(Name, pattern) or @name. These call the Mark method to add the marker to the global set. You can register the "mark" method to override these in your own call to Expect. The bound Mark function is usable directly in your method map, so

exported.Expect(map[string]interface{}{"mark": exported.Mark})

replicates the built in behavior.

Method invocation

When invoking a method the expressions in the parameter list need to be converted to values to be passed to the method. There are a very limited set of types the arguments are allowed to be.

expect.Note : passed the Note instance being evaluated.
string : can be supplied either a string literal or an identifier.
int : can only be supplied an integer literal.
*regexp.Regexp : can only be supplied a regular expression literal
token.Pos : has a file position calculated as described below.
token.Position : has a file position calculated as described below.
expect.Range: has a start and end position as described below.
interface{} : will be passed any value

Position calculation

There is some extra handling when a parameter is being coerced into a token.Pos, token.Position or Range type argument.

If the parameter is an identifier, it will be treated as the name of an marker to look up (as if markers were global variables).

If it is a string or regular expression, then it will be passed to expect.MatchBefore to look up a match in the line at which it was declared.

It is safe to call this repeatedly with different method sets, but it is not safe to call it concurrently.

func (*Exported) File

func (e *Exported) File(module, fragment string) string

File returns the full path for the given module and file fragment.

func (*Exported) FileContents

func (e *Exported) FileContents(filename string) ([]byte, error)

FileContents returns the contents of the specified file. It will use the overlay if the file is present, otherwise it will read it from disk.

func (*Exported) Mark

func (e *Exported) Mark(name string, r Range)

Mark adds a new marker to the known set.

func (*Exported) Temp

func (e *Exported) Temp() string

Temp returns the temporary directory that was generated.

type Exporter

Exporter implementations are responsible for converting from the generic description of some test data to a driver specific file layout.

type Exporter interface {
    // Name reports the name of the exporter, used in logging and sub-test generation.
    Name() string
    // Filename reports the system filename for test data source file.
    // It is given the base directory, the module the file is part of and the filename fragment to
    // work from.
    Filename(exported *Exported, module, fragment string) string
    // Finalize is called once all files have been written to write any extra data needed and modify
    // the Config to match. It is handed the full list of modules that were encountered while writing
    // files.
    Finalize(exported *Exported) error
}

type Module

Module is a representation of a go module.

type Module struct {
    // Name is the base name of the module as it would be in the go.mod file.
    Name string
    // Files is the set of source files for all packages that make up the module.
    // The keys are the file fragment that follows the module name, the value can
    // be a string or byte slice, in which case it is the contents of the
    // file, otherwise it must be a Writer function.
    Files map[string]interface{}

    // Overlay is the set of source file overlays for the module.
    // The keys are the file fragment as in the Files configuration.
    // The values are the in memory overlay content for the file.
    Overlay map[string][]byte
}

func GroupFilesByModules

func GroupFilesByModules(root string) ([]Module, error)

GroupFilesByModules attempts to map directories to the modules within each directory. This function assumes that the folder is structured in the following way:

dir/
	primarymod/
		*.go files
		packages
		go.mod (optional)
	modules/
		repoa/
			mod1/
				*.go files
				packages
				go.mod (optional)

It scans the directory tree anchored at root and adds a Copy writer to the map for every file found. This is to enable the common case in tests where you have a full copy of the package in your testdata.

type Range

A Range represents an interval within a source file in go/token notation.

type Range struct {
    TokFile    *token.File // non-nil
    Start, End token.Pos   // both valid and within range of TokFile
}

type Writer

A Writer is a function that writes out a test file. It is provided the name of the file to write, and may return an error if it cannot write the file. These are used as the content of the Files map in a Module.

type Writer func(filename string) error

func Copy

func Copy(source string) Writer

Copy returns a Writer that copies a file from the specified source to the required file. This is used to copy testdata files into the generated testing tree.

func Link(source string) Writer

Link returns a Writer that creates a hard link from the specified source to the required file. This is used to link testdata files into the generated testing tree.

If hard links to source are not supported on the destination filesystem, the returned Writer returns an error for which errors.Is(_, ErrUnsupported) returns true.

func Script

func Script(contents string) Writer

Script returns a Writer that writes out contents to the file and sets the executable bit on the created file. It is intended for source files that are shell scripts.

func Symlink(source string) Writer

Symlink returns a Writer that creates a symlink from the specified source to the required file. This is used to link testdata files into the generated testing tree.

If symlinks to source are not supported on the destination filesystem, the returned Writer returns an error for which errors.Is(_, ErrUnsupported) returns true.