Files
odo/tests/helper/helper_interactive.go
Charlie Drage 9c491b16fe Updates odo init output, fixes colorized output for tests. (#5613)
* Update vendoring for coloring to add NO_COLOR

* Updates odo init output / adds logo

<!--
Thank you for opening a PR! Here are some things you need to know before submitting:

1. Please read our developer guideline: https://github.com/redhat-developer/odo/wiki/Dev:-odo-Dev-Guidelines
2. Label this PR accordingly with the '/kind' line
3. Ensure you have written and ran the appropriate tests: https://github.com/redhat-developer/odo/wiki/Dev:-Writing-and-running-tests
4. Read how we approve and LGTM each PR: https://github.com/redhat-developer/odo/wiki/Pull-Requests:-Review-guideline

Documentation:

If you are pushing a change to documentation, please read: https://github.com/redhat-developer/odo/wiki/Documentation:-Contributing
-->

**What type of PR is this:**

<!--
Add one of the following kinds:
/kind bug
/kind cleanup
/kind tests
/kind documentation

Feel free to use other [labels](https://github.com/redhat-developer/odo/labels) as needed. However one of the above labels must be present or the PR will not be reviewed. This instruction is for reviewers as well.
-->

/kind feature

**What does this PR do / why we need it:**

We have the logo appearing in `odo dev` and `odo deploy`

Those 3 commands are the most used with `odo`, and the ones that are
most used. All three should have similar and consistent output that
contains:
  * What the component is doing (first line)
  * What's detected (in odo init, it's the files, in odo deploy and dev
    it's the component name)
  * odo version that is being used / outputted.

```sh
$ odo init
  __
 /  \__     Initializing a new component
 \__/  \    Files: No source code detected, a starter project will be created in the current directory
 /  \__/    odo version: v2.5.0
 \__/

Interactive mode enabled, please answer the following questions:
? Select language: javascript
? Select project type: Next.js
 ✓  Downloading devfile "nodejs-nextjs" from registry "DefaultDevfileRegistry" [450ms]
? Which starter project do you want to use? nodejs-nextjs-starter
? Enter component name: my-nodejs-nextjs-app
 ✓  Downloading starter project "nodejs-nextjs-starter" [516ms]

Your new component 'my-nodejs-nextjs-app' is ready in the current directory.
To start editing your component, use 'odo dev' and open this folder in your favorite IDE.
Changes will be directly reflected on the cluster.
```

**Which issue(s) this PR fixes:**
<!--
Specifying the issue will automatically close it when this PR is merged
-->

N/A

**PR acceptance criteria:**

- [X] Unit test

- [X] Integration test

- [X] Documentation

**How to test changes / Special notes to the reviewer:**

N/A

Signed-off-by: Charlie Drage <charlie@charliedrage.com>

* Update based on reviews

Signed-off-by: Charlie Drage <charlie@charliedrage.com>

* Update based on review

Signed-off-by: Charlie Drage <charlie@charliedrage.com>
2022-04-12 09:41:45 -04:00

123 lines
3.8 KiB
Go

//go:build linux || darwin || dragonfly || solaris || openbsd || netbsd || freebsd
// +build linux darwin dragonfly solaris openbsd netbsd freebsd
package helper
import (
"bytes"
"fmt"
"log"
"os"
"os/exec"
"time"
"github.com/Netflix/go-expect"
"github.com/hinshun/vt10x"
"github.com/kr/pty"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
// InteractiveContext represents the context of an interactive command to be run.
type InteractiveContext struct {
//Command represents the original command ran
Command []string
// console is the internal interface used by the interactive command
console *expect.Console
// buffer is the internal bytes buffer containing the console output.
// Its content will get updated as long as there are interactions with the console, like sending lines or
// expecting lines.
buffer *bytes.Buffer
// A function yto call to stop the process
StopCommand func()
}
// Tester represents the function that contains all steps to test the given interactive command.
// The InteractiveContext argument needs to be passed to the various helper.SendLine and helper.ExpectString methods.
type Tester func(InteractiveContext)
// RunInteractive runs the command in interactive mode and returns the output, and error.
// It takes command as array of strings, and a function `tester` that contains steps to run the test as an argument.
// The command is executed as a separate process, the environment of which is controlled via the `env` argument.
// The initial value of the sub-process environment is a copy of the environment of the current process.
// If `env` is not `nil`, it will be appended to the end of the sub-process environment.
// If there are duplicate environment keys, only the last value in the slice for each duplicate key is used.
func RunInteractive(command []string, env []string, tester Tester) (string, error) {
fmt.Fprintln(GinkgoWriter, "running command", command, "with env", env)
ptm, pts, err := pty.Open()
if err != nil {
log.Fatal(err)
}
term := vt10x.New(vt10x.WithWriter(pts))
// Set to 1 MINUTE timeout, since the command may take a while to start
c, err := expect.NewConsole(expect.WithStdin(ptm), expect.WithStdout(term), expect.WithCloser(pts, ptm), expect.WithDefaultTimeout(3*time.Minute))
if err != nil {
log.Fatal(err)
}
defer c.Close()
// execute the command
cmd := exec.Command(command[0], command[1:]...)
// setup stdin, stdout and stderr
cmd.Stdin = c.Tty()
cmd.Stdout = c.Tty()
cmd.Stderr = c.Tty()
if env != nil {
cmd.Env = append(os.Environ(), env...)
}
err = cmd.Start()
if err != nil {
log.Fatal(err)
}
buf := new(bytes.Buffer)
ctx := InteractiveContext{
Command: command,
console: c,
buffer: buf,
StopCommand: func() {
_ = cmd.Process.Kill()
},
}
tester(ctx)
err = cmd.Wait()
// Close the slave end of the pty, and read the remaining bytes from the master end.
c.Tty().Close()
return buf.String(), err
}
// expectDescriptionSupplier returns a function intended to be used as description supplier
// when checking errors do not occur in ExpectString and SendLine.
// Note that the function returned is evaluated lazily, only in case an error occurs.
func expectDescriptionSupplier(ctx InteractiveContext, line string) func() string {
return func() string {
return fmt.Sprintf("error while sending or expecting line: \"%s\"\n"+
"=== output of command '%+q' read so far ===\n%v\n======================",
line,
ctx.Command,
ctx.buffer)
}
}
func SendLine(ctx InteractiveContext, line string) {
_, err := ctx.console.SendLine(line)
Expect(err).ShouldNot(HaveOccurred(), expectDescriptionSupplier(ctx, line))
}
func ExpectString(ctx InteractiveContext, line string) {
res, err := ctx.console.ExpectString(line)
fmt.Fprint(ctx.buffer, res)
Expect(err).ShouldNot(HaveOccurred(), expectDescriptionSupplier(ctx, line))
}