opentofu/cmd/tofu/main_test.go
lettucemode e21a14fb0c
fix: cmd/tofu unit tests on windows (#1242)
Signed-off-by: Steven Hyland <2knowindeed@gmail.com>
2024-02-19 10:12:48 +00:00

371 lines
8.0 KiB
Go

// Copyright (c) The OpenTofu Authors
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) 2023 HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package main
import (
"fmt"
"os"
"path/filepath"
"reflect"
"runtime"
"testing"
"github.com/mitchellh/cli"
)
func TestMain_cliArgsFromEnv(t *testing.T) {
// Set up the state. This test really messes with the environment and
// global state so we set things up to be restored.
// Restore original CLI args
oldArgs := os.Args
defer func() { os.Args = oldArgs }()
// Set up test command and restore that
commands = make(map[string]cli.CommandFactory)
defer func() {
commands = nil
}()
testCommandName := "unit-test-cli-args"
testCommand := &testCommandCLI{}
commands[testCommandName] = func() (cli.Command, error) {
return testCommand, nil
}
cases := []struct {
Name string
Args []string
Value string
Expected []string
Err bool
}{
{
"no env",
[]string{testCommandName, "foo", "bar"},
"",
[]string{"foo", "bar"},
false,
},
{
"both env var and CLI",
[]string{testCommandName, "foo", "bar"},
"-foo baz",
[]string{"-foo", "baz", "foo", "bar"},
false,
},
{
"only env var",
[]string{testCommandName},
"-foo bar",
[]string{"-foo", "bar"},
false,
},
{
"cli string has blank values",
[]string{testCommandName, "bar", "", "baz"},
"-foo bar",
[]string{"-foo", "bar", "bar", "", "baz"},
false,
},
{
"cli string has blank values before the command",
[]string{"", testCommandName, "bar"},
"-foo bar",
[]string{"-foo", "bar", "bar"},
false,
},
{
// this should fail gracefully, this is just testing
// that we don't panic with our slice arithmetic
"no command",
[]string{},
"-foo bar",
nil,
true,
},
{
"single quoted strings",
[]string{testCommandName, "foo"},
"-foo 'bar baz'",
[]string{"-foo", "bar baz", "foo"},
false,
},
{
"double quoted strings",
[]string{testCommandName, "foo"},
`-foo "bar baz"`,
[]string{"-foo", "bar baz", "foo"},
false,
},
{
"double quoted single quoted strings",
[]string{testCommandName, "foo"},
`-foo "'bar baz'"`,
[]string{"-foo", "'bar baz'", "foo"},
false,
},
}
for i, tc := range cases {
t.Run(fmt.Sprintf("%d-%s", i, tc.Name), func(t *testing.T) {
// Set the env var value
if tc.Value != "" {
t.Setenv(EnvCLI, tc.Value)
}
// Set up the args
args := make([]string, len(tc.Args)+1)
args[0] = oldArgs[0] // process name
copy(args[1:], tc.Args)
// Run it!
os.Args = args
testCommand.Args = nil
exit := realMain()
if (exit != 0) != tc.Err {
t.Fatalf("bad: %d", exit)
}
if tc.Err {
return
}
// Verify
if !reflect.DeepEqual(testCommand.Args, tc.Expected) {
t.Fatalf("expected args %#v but got %#v", tc.Expected, testCommand.Args)
}
})
}
}
// This test just has more options than the test above. Use this for
// more control over behavior at the expense of more complex test structures.
func TestMain_cliArgsFromEnvAdvanced(t *testing.T) {
// Restore original CLI args
oldArgs := os.Args
defer func() { os.Args = oldArgs }()
// Set up test command and restore that
commands = make(map[string]cli.CommandFactory)
defer func() {
commands = nil
}()
cases := []struct {
Name string
Command string
EnvVar string
Args []string
Value string
Expected []string
Err bool
}{
{
"targeted to another command",
"command",
EnvCLI + "_foo",
[]string{"command", "foo", "bar"},
"-flag",
[]string{"foo", "bar"},
false,
},
{
"targeted to this command",
"command",
EnvCLI + "_command",
[]string{"command", "foo", "bar"},
"-flag",
[]string{"-flag", "foo", "bar"},
false,
},
{
"targeted to a command with a hyphen",
"command-name",
EnvCLI + "_command_name",
[]string{"command-name", "foo", "bar"},
"-flag",
[]string{"-flag", "foo", "bar"},
false,
},
{
"targeted to a command with a space",
"command name",
EnvCLI + "_command_name",
[]string{"command", "name", "foo", "bar"},
"-flag",
[]string{"-flag", "foo", "bar"},
false,
},
}
for i, tc := range cases {
t.Run(fmt.Sprintf("%d-%s", i, tc.Name), func(t *testing.T) {
// Set up test command and restore that
testCommandName := tc.Command
testCommand := &testCommandCLI{}
defer func() { delete(commands, testCommandName) }()
commands[testCommandName] = func() (cli.Command, error) {
return testCommand, nil
}
// Set the env var value
if tc.Value != "" {
t.Setenv(tc.EnvVar, tc.Value)
}
// Set up the args
args := make([]string, len(tc.Args)+1)
args[0] = oldArgs[0] // process name
copy(args[1:], tc.Args)
// Run it!
os.Args = args
testCommand.Args = nil
exit := realMain()
if (exit != 0) != tc.Err {
t.Fatalf("unexpected exit status %d; want 0", exit)
}
if tc.Err {
return
}
// Verify
if !reflect.DeepEqual(testCommand.Args, tc.Expected) {
t.Fatalf("bad: %#v", testCommand.Args)
}
})
}
}
// verify that we output valid autocomplete results
func TestMain_autoComplete(t *testing.T) {
// Restore original CLI args
oldArgs := os.Args
defer func() { os.Args = oldArgs }()
// Set up test command and restore that
commands = make(map[string]cli.CommandFactory)
defer func() {
commands = nil
}()
// Set up test command and restore that
commands["foo"] = func() (cli.Command, error) {
return &testCommandCLI{}, nil
}
t.Setenv("COMP_LINE", "tofu versio")
// Run it!
os.Args = []string{"tofu", "tofu", "versio"}
exit := realMain()
if exit != 0 {
t.Fatalf("unexpected exit status %d; want 0", exit)
}
}
type testCommandCLI struct {
Args []string
}
func (c *testCommandCLI) Run(args []string) int {
c.Args = args
return 0
}
func (c *testCommandCLI) Synopsis() string { return "" }
func (c *testCommandCLI) Help() string { return "" }
func TestWarnOutput(t *testing.T) {
mock := cli.NewMockUi()
wrapped := &ui{mock}
wrapped.Warn("WARNING")
stderr := mock.ErrorWriter.String()
stdout := mock.OutputWriter.String()
if stderr != "" {
t.Fatalf("unexpected stderr: %q", stderr)
}
if stdout != "WARNING\n" {
t.Fatalf("unexpected stdout: %q\n", stdout)
}
}
func TestMkConfigDir_new(t *testing.T) {
tmpConfigDir := filepath.Join(t.TempDir(), ".terraform.d")
err := mkConfigDir(tmpConfigDir)
if err != nil {
t.Fatalf("Failed to create the new config directory: %v", err)
}
info, err := os.Stat(tmpConfigDir)
if err != nil {
t.Fatalf("Directory does not exist after creation: %v", err)
}
if !info.IsDir() {
t.Fatalf("%s should be a directory but it's not", tmpConfigDir)
}
mode := int(info.Mode().Perm())
expectedMode := 0755
// Unix permissions bits are not applicable on Windows. Perm() returns
// 0777 regardless of whether readonly or hidden flags are set.
if runtime.GOOS == "windows" {
expectedMode = 0777
}
if mode != expectedMode {
t.Fatalf("Expected mode: %04o, but got: %04o", expectedMode, mode)
}
}
func TestMkConfigDir_exists(t *testing.T) {
tmpConfigDir := filepath.Join(t.TempDir(), ".terraform.d")
os.Mkdir(tmpConfigDir, os.ModePerm)
err := mkConfigDir(tmpConfigDir)
if err != nil {
t.Fatalf("Failed to create the new config directory: %v", err)
}
_, err = os.Stat(tmpConfigDir)
if err != nil {
t.Fatalf("Directory does not exist after creation: %v", err)
}
}
func TestMkConfigDir_noparent(t *testing.T) {
tmpConfigDir := filepath.Join(t.TempDir(), "nonexistenthomedir", ".terraform.d")
err := mkConfigDir(tmpConfigDir)
if err == nil {
t.Fatal("Expected an error, but got none")
}
// We wouldn't dare creating the home dir. If the parent of our config dir
// is missing, it's likely an issue with the system.
expectedError := fmt.Sprintf("mkdir %s: no such file or directory", tmpConfigDir)
if runtime.GOOS == "windows" {
expectedError = fmt.Sprintf("mkdir %s: The system cannot find the path specified.", tmpConfigDir)
}
if err.Error() != expectedError {
t.Fatalf("Expected error: %s, but got: %v", expectedError, err)
}
}