Help us shape the future of Go! Take the 2026 Go Developer Survey

clitest

package
v0.12.0 Latest Latest
Warning

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

Go to latest
Published: Oct 18, 2026 License: Apache-2.0 Imports: 30 Imported by: 0

Documentation

Overview

Package clitest runs the workflow command line for a test, as a user or a script would type it: each run in a world of its own — a working directory, a home and none of the developer's environment but what the test set — with a Jira, a gh or glab, and a Slack or webhook that answer as the test scripts them, the prompts a guided command reads, the repositories the commands run in, made with git itself, and the process readied for the tests by Main. It also runs one from a directory that is gone, and holds an output that refuses writes for a test to hand cli.Execute. It is the support the command tree's packages share for their black-box tests; only tests import it.

Index

Constants

View Source
const (
	WriteLink       = "link"
	WriteTransition = "transition"
)

The writes a fake Jira records, by kind.

View Source
const EmptyTokenVariable = "WORKFLOW_TEST_EMPTY_TOKEN" //nolint:gosec // G101: the name of a variable, not a credential

EmptyTokenVariable is a token_env the tests set to nothing, standing in for a variable the shell never exported.

View Source
const EraseBelow = "\x1b[J"

EraseBelow is what the live line's program sends as it ends, erasing the line it kept.

View Source
const ForgeCLIConfig = `{"forge":{"cli":true,"kind":"github","host":"github.com"},` +
	`"messaging":{"webhook_url":"https://hooks.slack.example/services/x"}}`

ForgeCLIConfig points the forge at its CLI and configures a Slack webhook, so `announce` gets past its Slack guard and reads the pull request through gh.

View Source
const GhHang = 10 * time.Second

GhHang is how long a fake gh whose api hangs takes to give up on its own: long enough that a test waiting it out cannot be mistaken for one that did not.

View Source
const GitlabMergeRequest = "https://gitlab.com/owner/repo/-/merge_requests/7"

GitlabMergeRequest is the merge request a fake glab opens.

View Source
const JiraFixture = `{"key":"JIRAUSER10100","name":"fred","displayName":"Fred F. User","active":true}`

JiraFixture is the body Jira Data Center returns for /rest/api/2/myself.

View Source
const OnPremisesRemote = "git@git.example.com:acme/thing.git"

OnPremisesRemote is an origin on a host whose name says neither GitHub nor GitLab, so only forge.kind can say which forge it is.

View Source
const SlackAuthTest = "/auth.test"

SlackAuthTest is the path of Slack's auth.test, which names the token's workspace and user.

View Source
const SlackClientID = "1234.5678"

SlackClientID is the Slack app's client ID the login is given.

View Source
const SlackPostMessage = "/chat.postMessage"

SlackPostMessage is the path of Slack's chat.postMessage, which a post is sent to.

View Source
const SlackWebhook = `"messaging": {"webhook_url": "https://hooks.slack.example/services/not-real"}`

SlackWebhook is a webhook configuration doctor accepts and never posts to.

View Source
const SlackWorkspace = "T0ACME"

SlackWorkspace is the Slack workspace the fake Slack's token is for, which a test keeps its owners' links in.

View Source
const StatusInReview = "In Review"

StatusInReview is the review status a test configures `pr` to offer.

View Source
const TaggingScopes = "chat:write,users:read,channels:read,groups:read,usergroups:read"

TaggingScopes are the scopes workflow slack login asks for, which the fake Slack's auth.test lists the token as granted.

View Source
const WorkdirUnread = "determining the working directory"

WorkdirUnread is what a command says when it cannot name the directory it was run from.

Variables

View Source
var ErrDirectoryGone = errors.New("getwd: no such file or directory")

ErrDirectoryGone is a working directory that cannot be named, as a shell is left standing in one when `git worktree remove` deletes the directory it is in.

View Source
var ErrOutputClosed = errors.New("output closed")

ErrOutputClosed is a stdout whose reader has gone, as when a pipe's far end exits early.

Functions

func ASCIIMarks

func ASCIIMarks() []string

ASCIIMarks are the frames the live line's mark turns through in ASCII.

func AnnouncedEarlier

func AnnouncedEarlier(t *testing.T, moment messaging.Moment) string

AnnouncedEarlier is a home whose store remembers the fake forge's pull request 7 announced at moment, as an earlier session — of the interface or of announce — leaves it: keyed by the repository's host and path.

func Answering

func Answering(answer string, asked *[]string) cmdkit.Prompt

Answering is a prompt that gives every question the same answer and keeps each question it was asked, for a test of what a command asks.

func AnswersThenEnds

func AnswersThenEnds(answers ...string) func(string) (string, error)

AnswersThenEnds answers each question with the next of answers, then reads the end of the input, as a pipe that runs dry does.

func AtTerminal

func AtTerminal(t *testing.T) cmdkit.Prompt

AtTerminal is UnusedPrompt with every stream flagged a terminal, as main flags a real one.

func BreakFile

func BreakFile(t *testing.T, path string) func()

BreakFile returns a function that writes what no configuration parses over the file at path, for a test to call at the moment the file should stop parsing.

func ClosedPull

func ClosedPull(title string) string

ClosedPull is a forge pull request array with one pull request closed without merging: its merged_at is null.

func Commit

func Commit(t *testing.T, dir, message string)

Commit makes an empty commit in dir with a fixed identity.

func CurrentBranch

func CurrentBranch(t *testing.T, dir string) string

CurrentBranch is the branch dir has checked out.

func DrawnWith

func DrawnWith(shown string, marks []string, want string) bool

DrawnWith reports whether shown draws want behind one of marks, the frame the live line's mark had turned to.

func EnvironmentFor

func EnvironmentFor(t *testing.T, where Place) cmdkit.Environment

EnvironmentFor is the Environment a run of test t where says sees: run from where.Dir, which a switch moves on from, with where.Home as its home and none of the developer's own environment but what t set.

func FailingStatus

func FailingStatus() string

FailingStatus is a commit status with a failed context.

func FakeGh

func FakeGh(t *testing.T, responses GhResponses)

FakeGh installs a stand-in `gh` first on the test's PATH that answers `gh api --include <url>` with canned HTTP responses, so a black-box test drives the forge through its CLI transport without a network or a real credential.

func FakeGlab

func FakeGlab(t *testing.T)

FakeGlab installs a stand-in `glab` on PATH that answers `glab api --include <path>` as a GitLab project with no merge request yet, opens one when asked, and names its user and no other, so a black-box test drives a GitLab forge through its CLI transport. Its token's scopes are not told.

func FakeGlabListing

func FakeGlabListing(t *testing.T, list string)

FakeGlabListing is FakeGlab whose merge-request listings — the branch's own and the review queue's alike — answer list.

func FakeGlabMerging

func FakeGlabMerging(t *testing.T, list string, merges ...string)

FakeGlabMerging is FakeGlab whose merge-request listings answer list and whose merge request 7, read on its own as its pipeline is, answers each of merges in turn, the last repeated.

func FakeGlabWithScopes

func FakeGlabWithScopes(t *testing.T, scopes string)

FakeGlabWithScopes is FakeGlab whose token, asked about, answers scopes — the body of GitLab's personal_access_tokens/self.

func FeatureRepo

func FeatureRepo(t *testing.T) string

FeatureRepo is PRRepo on PROJ-2's branch, fix/PROJ-2-thing: one commit on main and one on the branch.

func Git

func Git(t *testing.T, dir string, args ...string)

Git runs one git command in dir, failing the test if it does not succeed. The developer's own git configuration is kept out, as Run keeps it out of the command.

func GitInit

func GitInit(t *testing.T, dir string)

GitInit makes dir a real repository, so a command can be tested against git itself rather than against an imitation of it.

func GitOutput

func GitOutput(t *testing.T, dir string, args ...string) string

GitOutput runs one git command in dir and returns what it wrote to standard output, trimmed, keeping the developer's own git configuration out as Run does.

func GithubRepo

func GithubRepo(t *testing.T, branch string) string

GithubRepo is a repository on branch, one commit ahead of main, with a GitHub origin so the forge resolves to gh's dialect.

func GitlabRepo

func GitlabRepo(t *testing.T, branch string) string

GitlabRepo is PRRepo on branch with a GitLab origin and gitlabForgeConfig.

func IsolatedEnvironment

func IsolatedEnvironment(where Place) map[string]string

IsolatedEnvironment is the environment every run gets, so nothing of the developer's own decides a result or is touched by one: the home is the test's — a ~/.workflow.json would stand in for a missing file — and the store is kept under it, in $XDG_STATE_HOME only where the test chose one, never in %AppData%, where the developer's own store is.

func IssueFixture

func IssueFixture(key, issueType, summary string) string

IssueFixture is the Jira Data Center body for one issue: its key, type and summary, which is all the branch name is composed from.

func JiraFeatureRepo

func JiraFeatureRepo(t *testing.T, jiraURL string) string

JiraFeatureRepo is a repository on a feature branch for PROJ-7, one commit ahead of main, with a GitHub remote so the forge resolves through the fake gh, and a configuration naming the Jira at jiraURL.

func JiraServer

func JiraServer(t *testing.T, status int, body string, reached *atomic.Bool) *httptest.Server

JiraServer answers one myself request with status and body, recording whether it was reached at all. The flag is atomic because `task test` runs -race and the handler runs on its own goroutine.

func LiveMarks

func LiveMarks() []string

LiveMarks are the frames the live line's mark turns through.

func Main

func Main(m *testing.M) int

Main runs the tests m holds in a process readied for them and returns their exit code, for a TestMain to exit with once it has cleared git's repository variables (gittest.ClearLocalVariables): it trusts the local servers the tests deliver to, for as long as they run, takes every task program off PATH, and keeps every program a test or a command starts out of the developer's own home and git configuration; once they have run, it removes what it made, every test's world with it.

func MarkedLive

func MarkedLive(shown string) bool

MarkedLive reports whether shown holds the live line's mark.

func MergedPull

func MergedPull(title string) string

MergedPull is a forge pull request array with one merged pull request — GitHub tells a merge from a plain close by a non-null merged_at.

func OpenMergeRequest

func OpenMergeRequest() string

OpenMergeRequest is a listing holding merge request 7, open, as GitLab sends it to both the branch's lookup and the review queue.

func OpenPull

func OpenPull(title string) string

OpenPull is a forge pull request array with one open pull request.

func OwnedRepo

func OwnedRepo(t *testing.T) string

OwnedRepo is a GitHub repository whose main holds a CODEOWNERS giving the API to ana, a team and octo — the fake gh's own user — on a branch that changes the API and is already pushed.

func PRRepo

func PRRepo(t *testing.T, branch string) string

PRRepo is a repository on branch, one commit ahead of main — a branch a pull request can be opened for.

func PassingStatus

func PassingStatus() string

PassingStatus is a commit status that has passed.

func PretendPushed

func PretendPushed(t *testing.T, repo string)

PretendPushed gives the checked-out branch a local upstream on origin with nothing ahead, so the pull-request flow treats it as already published and skips the push — there is no real remote to push to in a black-box test.

func ProgramsOf

func ProgramsOf(t *testing.T) string

ProgramsOf is the directory test t's stand-in programs are in, found first on its runs' PATH.

func ReadLog

func ReadLog(t *testing.T, path string) string

ReadLog is what the request log at path holds, failing the test if it cannot be read.

func RecordAnnounced

func RecordAnnounced(ctx context.Context, home string, moment messaging.Moment) error

RecordAnnounced records, in the store under home, the fake forge's pull request 7 announced at moment.

func Redirecting

func Redirecting(writer http.ResponseWriter, request *http.Request)

Redirecting answers every request with a redirect to a sign-in page, as a gateway in front of a service does.

func RepoWithRemote

func RepoWithRemote(t *testing.T, remote string) string

RepoWithRemote makes a repository whose origin is remote.

func Run

func Run(t *testing.T, dir string, args ...string) (string, error)

Run executes the command tree in dir and returns everything it printed.

Each run is handed an Environment of its own, run from dir with an empty home of its own and none of the developer's environment, so the tests run side by side: see EnvironmentFor and IsolatedEnvironment.

func RunAtHome

func RunAtHome(t *testing.T, dir string, args ...string) (string, error)

RunAtHome is Run with dir as the home directory as well, so the file there is the home file, the one file that may set a program to run or a variable to read.

func RunGuided

func RunGuided(t *testing.T, dir string, prompt cmdkit.Prompt, args ...string) (string, error)

RunGuided is Run with a prompt that answers `config init`'s questions.

func RunningStatus

func RunningStatus() string

RunningStatus is a commit status still to finish.

func Scripted

func Scripted(lines, secrets []string) cmdkit.Prompt

Scripted answers the guided prompts in order: visible lines from lines, secrets from secrets. A prompt past the end of its list reads as blank.

func SearchFixture

func SearchFixture(keys ...string) string

SearchFixture is the Jira body for a search of the issues assigned to you.

func SetVariable

func SetVariable(t *testing.T, name, value string)

SetVariable sets an environment variable for test t's runs, and for its subtests'.

func SlackLoggedInConfig

func SlackLoggedInConfig() string

SlackLoggedInConfig is a GitHub forge through its CLI, announcing to Slack with a user token good for an hour, whose credentials the file keeps, so no keychain is read and no refresh is due.

func UnicodeLine

func UnicodeLine() []string

UnicodeLine is every character the live line draws by default and never in ASCII: its mark's frames, and its bar's.

func UnusedPrompt

func UnusedPrompt(t *testing.T) cmdkit.Prompt

UnusedPrompt fails the test if a command reads from it: only the guided `config init` should, and its tests drive it with a scripted prompt instead.

func WorkingJira

func WorkingJira(t *testing.T) string

WorkingJira is a local Jira that accepts the credential, so an online check never reaches a real host.

func WriteConfigFor

func WriteConfigFor(t *testing.T, dir, baseURL string)

WriteConfigFor writes a configuration pointing Jira at baseURL.

func WriteExecutable

func WriteExecutable(t *testing.T, path, contents string, mode os.FileMode)

WriteExecutable writes contents to path with mode, failing the test if it cannot. It holds syscall.ForkLock while the file is open: a program another test starts meanwhile would inherit the open file and hold it for writing until it execs, and running a script written here would then fail with "text file busy" before the script ran (golang.org/issue/22315).

func WriteFile

func WriteFile(t *testing.T, dir, contents string) string

WriteFile writes a configuration into dir, failing the test if it cannot.

func WriteRepoFile

func WriteRepoFile(t *testing.T, repo, rel, contents string)

WriteRepoFile writes contents to a path under repo, creating parent directories — for a repository file the command reads, such as a pull request template.

Types

type CapturingWebhook

type CapturingWebhook struct {
	URL string
	// contains filtered or unexported fields
}

CapturingWebhook is a local https webhook that accepts every post and keeps each body's text.

func NewCapturingWebhook

func NewCapturingWebhook(t *testing.T) *CapturingWebhook

NewCapturingWebhook serves a capturing webhook for the test's length.

func (*CapturingWebhook) Posted

func (h *CapturingWebhook) Posted() []string

Posted is every text posted so far.

type GhResponses

type GhResponses struct {
	Pulls  string // GET .../pulls?...              — a JSON array
	Status string // GET .../commits/{sha}/status
	// StatusSequence and PullsSequence, when set, answer the status reads and
	// the pull request lists in turn, the last one repeated, in place of
	// Status and Pulls.
	StatusSequence []string
	PullsSequence  []string
	// StatusGate, when set, is a directory: each status read touches its read
	// file, and answers running until its release file is there, then passed.
	StatusGate  string
	Checks      string // GET .../commits/{sha}/check-runs
	Search      string // GET .../search/issues?...      — a {"items":[...]} object
	UserError   bool   // when set, GET .../user answers unreadably, so Whoami fails
	CreateError bool   // when set, POST .../pulls answers unreadably, so the open fails
	SignedOut   bool   // when set, `gh auth token` exits 1, so no token resolves through gh
	APIHangs    bool   // when set, `gh api` answers nothing for GhHang, as behind a stalled gateway
	// InterruptsStatus, when set, has each status read press ctrl+c on the
	// process that asked, as a person would mid-wait, and then hang.
	InterruptsStatus bool
	SentLog          string // when set, each request carrying a body appends its endpoint and body here
}

GhResponses are the canned bodies a fake gh returns per route; an empty field falls back to a benign default (no pull, no CI).

type JiraWrite

type JiraWrite struct {
	Kind string
	Path string
	Body string
}

JiraWrite is one write a fake Jira was asked to make: the path it was sent to, and the body.

type JiraWrites

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

JiraWrites records, in order, the writes a fake Jira was asked to make: each link added to an issue and each transition applied. A mutex guards them because `task test` runs -race and the handler answers on its own goroutine.

func (*JiraWrites) Applied

func (w *JiraWrites) Applied() int

Applied is how many transitions were applied.

func (*JiraWrites) Kinds

func (w *JiraWrites) Kinds() []string

Kinds are the kinds of every write, in the order they were made.

func (*JiraWrites) Of

func (w *JiraWrites) Of(kind string) []JiraWrite

Of is every write of kind, in order.

func (*JiraWrites) Record

func (w *JiraWrites) Record(kind string, request *http.Request)

Record keeps request as a write of kind, with its path and body.

func (*JiraWrites) SentBody

func (w *JiraWrites) SentBody() string

SentBody is the body of the last transition applied, or empty for none.

type Place

type Place struct {
	// Dir is the working directory the command runs from.
	Dir string
	// Home is the home directory the test chose.
	Home string
	// State, when set, is $XDG_STATE_HOME.
	State string
}

Place is where a run happens: the working directory, and the home directory the test chose, for a test that puts something there first; state, when set, is $XDG_STATE_HOME, which is otherwise empty.

func HomeAndRepository

func HomeAndRepository(t *testing.T) Place

HomeAndRepository is a home directory whose file sets a Jira with its token and a webhook, and beside it a directory an empty .git marks as a repository's root, as config.Load reads one, with nothing in it yet. git sees no repository there, so a test that needs one makes it with GitInit.

type RefusingOutput

type RefusingOutput struct{}

RefusingOutput fails every write.

func (RefusingOutput) Write

func (RefusingOutput) Write([]byte) (int, error)

Write refuses whatever it is handed, writing none of it.

type SlackAnswer

type SlackAnswer struct {
	Status int
	Body   string
}

SlackAnswer is what the fake Slack answers on one path.

type SlackFake

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

SlackFake is a Slack Web API on this machine, which every Slack request of the test's commands is sent to; it keeps the text of every post, and the channel each was posted in.

func FakeSlack

func FakeSlack(t *testing.T, changed map[string]SlackAnswer) *SlackFake

FakeSlack serves Slack's answers for the token SlackLoggedInConfig keeps — ana and a bot in #dev, the api-reviewers group, workspace SlackWorkspace, granted TaggingScopes — with changed replacing any of them by path, and points the commands at it until the test ends.

func (*SlackFake) AnswerFirst

func (s *SlackFake) AnswerFirst(path string, answer SlackAnswer)

AnswerFirst makes the fake answer the first request on path with answer, and every later one as it did.

func (*SlackFake) Channel

func (s *SlackFake) Channel(t *testing.T) string

Channel is the channel of the one post the fake was sent, failing the test unless there was exactly one.

func (*SlackFake) Count

func (s *SlackFake) Count(path string) int

Count is how many requests the fake answered at path.

func (*SlackFake) Grant

func (s *SlackFake) Grant(scopes string)

Grant makes auth.test list the token as granted scopes instead.

func (*SlackFake) Post

func (s *SlackFake) Post(t *testing.T) string

Post is the one post the fake was sent, failing the test unless there was exactly one.

type Streams

type Streams struct {
	// Stdout is what the command printed on standard output.
	Stdout string
	// Stderr is what the command printed on standard error.
	Stderr string
}

Streams is what a command printed, stream by stream.

func RunFromARemovedDirectory

func RunFromARemovedDirectory(t *testing.T, home string, args ...string) (Streams, error)

RunFromARemovedDirectory runs the command tree from a working directory that cannot be named. The home is the test's, for a configuration put there first.

func RunStreams

func RunStreams(t *testing.T, dir string, prompt cmdkit.Prompt, args ...string) (Streams, error)

RunStreams is RunGuided with the two streams kept apart, for a test that says which one a line belongs on: the artifact on stdout, what is said about it on stderr.

func RunStreamsAt

func RunStreamsAt(t *testing.T, where Place, prompt cmdkit.Prompt, args ...string) (Streams, error)

RunStreamsAt is RunStreams in the home directory the test chose.

Jump to

Keyboard shortcuts

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