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
- Variables
- func ASCIIMarks() []string
- func AnnouncedEarlier(t *testing.T, moment messaging.Moment) string
- func Answering(answer string, asked *[]string) cmdkit.Prompt
- func AnswersThenEnds(answers ...string) func(string) (string, error)
- func AtTerminal(t *testing.T) cmdkit.Prompt
- func BreakFile(t *testing.T, path string) func()
- func ClosedPull(title string) string
- func Commit(t *testing.T, dir, message string)
- func CurrentBranch(t *testing.T, dir string) string
- func DrawnWith(shown string, marks []string, want string) bool
- func EnvironmentFor(t *testing.T, where Place) cmdkit.Environment
- func FailingStatus() string
- func FakeGh(t *testing.T, responses GhResponses)
- func FakeGlab(t *testing.T)
- func FakeGlabListing(t *testing.T, list string)
- func FakeGlabMerging(t *testing.T, list string, merges ...string)
- func FakeGlabWithScopes(t *testing.T, scopes string)
- func FeatureRepo(t *testing.T) string
- func Git(t *testing.T, dir string, args ...string)
- func GitInit(t *testing.T, dir string)
- func GitOutput(t *testing.T, dir string, args ...string) string
- func GithubRepo(t *testing.T, branch string) string
- func GitlabRepo(t *testing.T, branch string) string
- func IsolatedEnvironment(where Place) map[string]string
- func IssueFixture(key, issueType, summary string) string
- func JiraFeatureRepo(t *testing.T, jiraURL string) string
- func JiraServer(t *testing.T, status int, body string, reached *atomic.Bool) *httptest.Server
- func LiveMarks() []string
- func Main(m *testing.M) int
- func MarkedLive(shown string) bool
- func MergedPull(title string) string
- func OpenMergeRequest() string
- func OpenPull(title string) string
- func OwnedRepo(t *testing.T) string
- func PRRepo(t *testing.T, branch string) string
- func PassingStatus() string
- func PretendPushed(t *testing.T, repo string)
- func ProgramsOf(t *testing.T) string
- func ReadLog(t *testing.T, path string) string
- func RecordAnnounced(ctx context.Context, home string, moment messaging.Moment) error
- func Redirecting(writer http.ResponseWriter, request *http.Request)
- func RepoWithRemote(t *testing.T, remote string) string
- func Run(t *testing.T, dir string, args ...string) (string, error)
- func RunAtHome(t *testing.T, dir string, args ...string) (string, error)
- func RunGuided(t *testing.T, dir string, prompt cmdkit.Prompt, args ...string) (string, error)
- func RunningStatus() string
- func Scripted(lines, secrets []string) cmdkit.Prompt
- func SearchFixture(keys ...string) string
- func SetVariable(t *testing.T, name, value string)
- func SlackLoggedInConfig() string
- func UnicodeLine() []string
- func UnusedPrompt(t *testing.T) cmdkit.Prompt
- func WorkingJira(t *testing.T) string
- func WriteConfigFor(t *testing.T, dir, baseURL string)
- func WriteExecutable(t *testing.T, path, contents string, mode os.FileMode)
- func WriteFile(t *testing.T, dir, contents string) string
- func WriteRepoFile(t *testing.T, repo, rel, contents string)
- type CapturingWebhook
- type GhResponses
- type JiraWrite
- type JiraWrites
- type Place
- type RefusingOutput
- type SlackAnswer
- type SlackFake
- type Streams
Constants ¶
const ( WriteLink = "link" WriteTransition = "transition" )
The writes a fake Jira records, by kind.
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.
const EraseBelow = "\x1b[J"
EraseBelow is what the live line's program sends as it ends, erasing the line it kept.
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.
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.
const GitlabMergeRequest = "https://gitlab.com/owner/repo/-/merge_requests/7"
GitlabMergeRequest is the merge request a fake glab opens.
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.
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.
const SlackAuthTest = "/auth.test"
SlackAuthTest is the path of Slack's auth.test, which names the token's workspace and user.
const SlackClientID = "1234.5678"
SlackClientID is the Slack app's client ID the login is given.
const SlackPostMessage = "/chat.postMessage"
SlackPostMessage is the path of Slack's chat.postMessage, which a post is sent to.
const SlackWebhook = `"messaging": {"webhook_url": "https://hooks.slack.example/services/not-real"}`
SlackWebhook is a webhook configuration doctor accepts and never posts to.
const SlackWorkspace = "T0ACME"
SlackWorkspace is the Slack workspace the fake Slack's token is for, which a test keeps its owners' links in.
const StatusInReview = "In Review"
StatusInReview is the review status a test configures `pr` to offer.
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.
const WorkdirUnread = "determining the working directory"
WorkdirUnread is what a command says when it cannot name the directory it was run from.
Variables ¶
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.
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 ¶
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 ¶
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 ¶
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 ¶
AtTerminal is UnusedPrompt with every stream flagged a terminal, as main flags a real one.
func BreakFile ¶
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 ¶
ClosedPull is a forge pull request array with one pull request closed without merging: its merged_at is null.
func CurrentBranch ¶
CurrentBranch is the branch dir has checked out.
func DrawnWith ¶
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 ¶
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 ¶
FakeGlabListing is FakeGlab whose merge-request listings — the branch's own and the review queue's alike — answer list.
func FakeGlabMerging ¶
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 ¶
FakeGlabWithScopes is FakeGlab whose token, asked about, answers scopes — the body of GitLab's personal_access_tokens/self.
func FeatureRepo ¶
FeatureRepo is PRRepo on PROJ-2's branch, fix/PROJ-2-thing: one commit on main and one on the branch.
func Git ¶
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 ¶
GitInit makes dir a real repository, so a command can be tested against git itself rather than against an imitation of it.
func GitOutput ¶
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 ¶
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 ¶
GitlabRepo is PRRepo on branch with a GitLab origin and gitlabForgeConfig.
func IsolatedEnvironment ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
MarkedLive reports whether shown holds the live line's mark.
func MergedPull ¶
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 OwnedRepo ¶
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 ¶
PRRepo is a repository on branch, one commit ahead of main — a branch a pull request can be opened for.
func PretendPushed ¶
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 ¶
ProgramsOf is the directory test t's stand-in programs are in, found first on its runs' PATH.
func ReadLog ¶
ReadLog is what the request log at path holds, failing the test if it cannot be read.
func RecordAnnounced ¶
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 ¶
RepoWithRemote makes a repository whose origin is remote.
func Run ¶
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 ¶
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 Scripted ¶
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 ¶
SearchFixture is the Jira body for a search of the issues assigned to you.
func SetVariable ¶
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 ¶
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 ¶
WorkingJira is a local Jira that accepts the credential, so an online check never reaches a real host.
func WriteConfigFor ¶
WriteConfigFor writes a configuration pointing Jira at baseURL.
func WriteExecutable ¶
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 WriteRepoFile ¶
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 ¶
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 ¶
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 SlackAnswer ¶
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 ¶
Channel is the channel of 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 ¶
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 ¶
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.