complete: enable complete.Complete() output capturing (#138)

* complete: test capturing Complete() output

Implement an Example test to demonstrate capturing the output
of Complete(), which is crucial for integration tests.

* complete: do not hard-code the I/O streams at the package initialization

Instead of defining the input/output streams as unexported global vars
that only get their values assigned to once, at the very initialization
of the package, use the values that `os.Stdin` and `os.Stdout` have at
the particular moment on every `complete.Complete()` call.

Fix #137

* complete: capture and discard output in TestComplete

Restore earlier behavior using proper stream redirection this time.

* complete: output capturing example: define things in the package scope

Define the `stringLookup` func type and `promptEnv` func in the package
scope instead of the `ExampleComplete_outputCapturing` test.

* complete: rename the `stringLookup` func type to `getEnvFn`
This commit is contained in:
antichris 2021-04-06 06:46:20 +03:00 committed by GitHub
parent 002575c9d5
commit 4a17a04514
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 52 additions and 6 deletions

View File

@ -51,8 +51,6 @@ func (p PredictFunc) Predict(prefix string) []string {
var ( var (
getEnv = os.Getenv getEnv = os.Getenv
exit = os.Exit exit = os.Exit
out io.Writer = os.Stdout
in io.Reader = os.Stdin
) )
// Complete the command line arguments for the given command in the case that the program // Complete the command line arguments for the given command in the case that the program
@ -66,6 +64,10 @@ func Complete(name string, cmd Completer) {
doUninstall = getEnv("COMP_UNINSTALL") == "1" doUninstall = getEnv("COMP_UNINSTALL") == "1"
yes = getEnv("COMP_YES") == "1" yes = getEnv("COMP_YES") == "1"
) )
var (
out io.Writer = os.Stdout
in io.Reader = os.Stdin
)
if doInstall || doUninstall { if doInstall || doUninstall {
install.Run(name, doUninstall, yes, out, in) install.Run(name, doUninstall, yes, out, in)
exit(0) exit(0)

View File

@ -1,8 +1,10 @@
package complete package complete
import ( import (
"io"
"io/ioutil" "io/ioutil"
"os" "os"
"strconv"
"testing" "testing"
"github.com/posener/complete/v2/internal/arg" "github.com/posener/complete/v2/internal/arg"
@ -159,9 +161,17 @@ func TestComplete(t *testing.T) {
defer func() { defer func() {
getEnv = os.Getenv getEnv = os.Getenv
exit = os.Exit exit = os.Exit
out = os.Stdout
}() }()
in, out, err := os.Pipe()
if err != nil {
t.Fatal(err)
}
defer func(o *os.File) { os.Stdout = o }(os.Stdout)
defer out.Close()
os.Stdout = out
go io.Copy(ioutil.Discard, in)
tests := []struct { tests := []struct {
line, point string line, point string
shouldExit bool shouldExit bool
@ -203,7 +213,6 @@ func TestComplete(t *testing.T) {
exit = func(int) { exit = func(int) {
isExit = true isExit = true
} }
out = ioutil.Discard
if tt.shouldPanic { if tt.shouldPanic {
assert.Panics(t, func() { testCmd.Complete("") }) assert.Panics(t, func() { testCmd.Complete("") })
} else { } else {
@ -214,6 +223,24 @@ func TestComplete(t *testing.T) {
} }
} }
// ExampleComplete_outputCapturing demonstrates the ability to capture
// the output of Complete() invocations, crucial for integration tests.
func ExampleComplete_outputCapturing() {
defer func(f func(int)) { exit = f }(exit)
defer func(f getEnvFn) { getEnv = f }(getEnv)
exit = func(int) {}
// This is where the actual example starts:
cmd := &Command{Sub: map[string]*Command{"bar": {}}}
getEnv = promptEnv("foo b")
Complete("foo", cmd)
// Output:
// bar
}
type set []string type set []string
func (s set) Predict(_ string) []string { func (s set) Predict(_ string) []string {
@ -245,3 +272,20 @@ func TestHasPrefix(t *testing.T) {
}) })
} }
} }
// getEnvFn emulates os.GetEnv by mapping one string to another.
type getEnvFn = func(string) string
// promptEnv returns getEnvFn that emulates the environment variables
// a shell would set when its prompt has the given contents.
var promptEnv = func(contents string) getEnvFn {
return func(key string) string {
switch key {
case "COMP_LINE":
return contents
case "COMP_POINT":
return strconv.Itoa(len(contents))
}
return ""
}
}