mirror of https://github.com/pulumi/pulumi.git
202 lines
5.4 KiB
Go
202 lines
5.4 KiB
Go
// Copyright 2016-2022, Pulumi Corporation.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package rpcutil
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"io"
|
|
"os"
|
|
"os/exec"
|
|
"strings"
|
|
"testing"
|
|
|
|
pulumirpc "github.com/pulumi/pulumi/sdk/v3/proto/go"
|
|
"github.com/stretchr/testify/assert"
|
|
"google.golang.org/grpc"
|
|
|
|
"golang.org/x/term"
|
|
)
|
|
|
|
func makeStreamMock() *streamMock {
|
|
return &streamMock{
|
|
ctx: context.Background(),
|
|
}
|
|
}
|
|
|
|
type streamMock struct {
|
|
grpc.ServerStream
|
|
ctx context.Context
|
|
stdout bytes.Buffer
|
|
stderr bytes.Buffer
|
|
}
|
|
|
|
func (m *streamMock) Context() context.Context {
|
|
return m.ctx
|
|
}
|
|
|
|
func (m *streamMock) Send(resp *pulumirpc.InstallDependenciesResponse) error {
|
|
if _, err := m.stdout.Write(resp.Stdout); err != nil {
|
|
return err
|
|
}
|
|
if _, err := m.stderr.Write(resp.Stderr); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func TestWriter_NoTerminal(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
server := makeStreamMock()
|
|
|
|
closer, stdout, stderr, err := MakeInstallDependenciesStreams(server, false)
|
|
assert.NoError(t, err)
|
|
|
|
// stdout and stderr should just write to server
|
|
l, err := stdout.Write([]byte("hello"))
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 5, l)
|
|
|
|
l, err = stderr.Write([]byte("world"))
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 5, l)
|
|
|
|
err = closer.Close()
|
|
assert.NoError(t, err)
|
|
|
|
outBytes, err := io.ReadAll(&server.stdout)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, []byte("hello"), outBytes)
|
|
|
|
errBytes, err := io.ReadAll(&server.stderr)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, []byte("world"), errBytes)
|
|
}
|
|
|
|
func TestWriter_Terminal(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
server := makeStreamMock()
|
|
|
|
closer, stdout, stderr, err := MakeInstallDependenciesStreams(server, true)
|
|
assert.NoError(t, err)
|
|
|
|
// We _may_ have made a pty and stdout and stderr are the same and both send to the server as stdout
|
|
if stdout == stderr {
|
|
// osx behaves strangely reading and writing a pty in the same process, and we want to check that this
|
|
// is still a tty when invoking other processes so invoke the "test" program.
|
|
|
|
// We can't use the tty program because that tests stdin and we aren't setting stdin, but "test" can
|
|
// check if file descriptor 1 (i.e. stdout) is a tty with -t.
|
|
cmd := exec.Command("test", "-t", "1")
|
|
cmd.Stdin = nil
|
|
cmd.Stdout = stdout
|
|
cmd.Stderr = stdout
|
|
|
|
err := cmd.Run()
|
|
assert.NoError(t, err)
|
|
|
|
exitcode := cmd.ProcessState.ExitCode()
|
|
assert.Equal(t, 0, exitcode)
|
|
|
|
// Now check we can reuse the stream to echo some text back
|
|
text := "Lorem ipsum dolor sit amet, consectetur adipiscing elit," +
|
|
"sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n" +
|
|
"Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n" +
|
|
"Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n" +
|
|
"Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\n"
|
|
cmd = exec.Command("echo", text)
|
|
cmd.Stdin = nil
|
|
cmd.Stdout = stdout
|
|
cmd.Stderr = stdout
|
|
|
|
err = cmd.Run()
|
|
assert.NoError(t, err)
|
|
|
|
exitcode = cmd.ProcessState.ExitCode()
|
|
assert.Equal(t, 0, exitcode)
|
|
|
|
err = closer.Close()
|
|
assert.NoError(t, err)
|
|
|
|
outBytes, err := io.ReadAll(&server.stdout)
|
|
assert.NoError(t, err)
|
|
// echo adds an extra \n at the end, and line discipline will cause \n to come back as \r\n
|
|
expected := strings.ReplaceAll(text+"\n", "\n", "\r\n")
|
|
assert.Equal(t, []byte(expected), outBytes)
|
|
|
|
errBytes, err := io.ReadAll(&server.stderr)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, []byte{}, errBytes)
|
|
} else {
|
|
// else they are separate and should behave just like the NoTerminal case
|
|
l, err := stdout.Write([]byte("hello"))
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 5, l)
|
|
|
|
l, err = stderr.Write([]byte("world"))
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 5, l)
|
|
|
|
err = closer.Close()
|
|
assert.NoError(t, err)
|
|
|
|
outBytes, err := io.ReadAll(&server.stdout)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, []byte("hello"), outBytes)
|
|
|
|
errBytes, err := io.ReadAll(&server.stderr)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, []byte("world"), errBytes)
|
|
}
|
|
}
|
|
|
|
func TestWriter_IsPTY(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
server := makeStreamMock()
|
|
|
|
closer, stdout, stderr, err := MakeInstallDependenciesStreams(server, true)
|
|
assert.NoError(t, err)
|
|
|
|
// We _may_ have made a pty, check IsTerminal returns true
|
|
if stdout == stderr {
|
|
// These will be os.Files if a pty
|
|
file, ok := stdout.(*os.File)
|
|
assert.True(t, ok, "stdout was not a File")
|
|
//nolint:gosec
|
|
assert.True(t, term.IsTerminal(int(file.Fd())), "stdout was not a terminal")
|
|
}
|
|
|
|
err = closer.Close()
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func TestWriter_SafeToCloseTwice(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
server := makeStreamMock()
|
|
|
|
closer, _, _, err := MakeInstallDependenciesStreams(server, true)
|
|
assert.NoError(t, err)
|
|
|
|
err = closer.Close()
|
|
assert.NoError(t, err)
|
|
|
|
err = closer.Close()
|
|
assert.NoError(t, err)
|
|
}
|