/*
Copyright 2019 The Knative Authors

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 cmd

import (
	"errors"
	"reflect"
	"testing"
)

func TestRunCommand(t *testing.T) {
	testCases := []struct {
		command             string
		options             []Option
		expectedOutput      string
		expectedErrorOutput string
		expectedErrorCode   int
	}{{
		"",
		[]Option{},
		"",
		invalidInputErrorPrefix + "",
		1,
	}, {
		" ",
		[]Option{},
		"",
		invalidInputErrorPrefix + " ",
		1,
	}, {
		"echo hello, world",
		[]Option{},
		"hello, world\n",
		"",
		0,
	}, {
		"bash -c 'echo foo > /dev/stderr; exit 4'",
		[]Option{},
		"",
		"foo\n",
		4,
	}, {
		"bash -c 'echo ${HELLO} > /dev/stdout; exit 0'",
		[]Option{WithEnvs([]string{"HELLO=hello, world"})},
		"hello, world\n",
		"",
		0,
	}}
	for _, c := range testCases {
		out, err := RunCommand(c.command, c.options...)
		if c.expectedOutput != out {
			t.Fatalf("Expect %q but actual is %q", c.expectedOutput, out)
		}
		if err != nil {
			var ce *CommandLineError
			if errors.As(err, &ce) {
				if ce.ErrorCode != c.expectedErrorCode {
					t.Fatalf("Expect to get error code %d but got %d", c.expectedErrorCode, ce.ErrorCode)
				}
				if string(ce.ErrorOutput) != c.expectedErrorOutput {
					t.Fatalf("Expect to get error message %q but got %q", c.expectedErrorOutput, ce.ErrorOutput)
				}
			} else {
				t.Fatalf("Expect to get a CommandLineError but got %q", reflect.TypeOf(err))
			}
		} else {
			if c.expectedErrorCode != 0 {
				t.Fatalf("Expect to get an error code %d but got no error", c.expectedErrorCode)
			}
			if c.expectedErrorOutput != "" {
				t.Fatalf("Expect to get error message %q but got nothing", c.expectedErrorOutput)
			}
		}
	}
}

func TestRunCommands(t *testing.T) {
	testCases := []struct {
		commands            []string
		expectedOutput      string
		expectedErrorOutput string
		expectedErrorCode   int
	}{
		{
			[]string{"echo 123", "echo 234", "echo 345"},
			"123\n\n234\n\n345\n",
			"",
			0,
		},
		{
			[]string{" ", "echo 123"},
			"",
			invalidInputErrorPrefix + " ",
			1,
		},
		{
			[]string{"echo 123", "", "echo 234"},
			"123\n\n",
			invalidInputErrorPrefix + "",
			1,
		},
		{
			[]string{`bash -c "echo foo > /dev/stderr; exit 4"`},
			"",
			"foo\n",
			4,
		},
		{
			[]string{"bash -c 'exit 10'", "echo 123"},
			"",
			"",
			10,
		},
	}
	for _, c := range testCases {
		out, err := RunCommands(c.commands...)
		if c.expectedOutput != out {
			t.Fatalf("Expect %q but actual is %q", c.expectedOutput, out)
		}
		if err != nil {
			var ce *CommandLineError
			if errors.As(err, &ce) {
				if ce.ErrorCode != c.expectedErrorCode {
					t.Fatalf("Expect to get error code %d but got %d", c.expectedErrorCode, ce.ErrorCode)
				}
				if string(ce.ErrorOutput) != c.expectedErrorOutput {
					t.Fatalf("Expect to get error message %q but got %q", c.expectedErrorOutput, ce.ErrorOutput)
				}
			} else {
				t.Fatal("Expect to get a CommandLineError but got", reflect.TypeOf(err))
			}
		} else {
			if c.expectedErrorCode != 0 {
				t.Fatalf("Expect to get an error code %d but got no error", c.expectedErrorCode)
			}
			if c.expectedErrorOutput != "" {
				t.Fatalf("Expect to get error message %q but got nothing", c.expectedErrorOutput)
			}
		}
	}
}

func TestRunCommandsInParallel(t *testing.T) {
	testCases := []struct {
		commands            []string
		possibleOutput      []string
		possibleErrorOutput []string
	}{
		{
			[]string{"echo 123", "echo 234"},
			[]string{"123\n\n234\n", "234\n\n123\n"},
			nil,
		},
		{
			[]string{"", "echo 123"},
			[]string{"\n123\n", "123\n\n"},
			[]string{invalidInputErrorPrefix + "", invalidInputErrorPrefix + ""},
		},
		{
			[]string{"bash -c 'echo foo; exit 1'", "bash -c 'echo bar > /dev/stderr; exit 1'"},
			[]string{"\nfoo\n", "foo\n\n"},
			[]string{"bar\n\n", "\nbar\n"},
		},
	}
	for _, c := range testCases {
		out, err := RunCommandsInParallel(c.commands...)

		idx := -1
		for i := range c.possibleOutput {
			if c.possibleOutput[i] == out {
				idx = i
				break
			}
		}
		if idx == -1 {
			t.Fatalf("Expect output in %v but actual is %q", c.possibleOutput, out)
		}

		if len(c.possibleErrorOutput) != 0 {
			if err != nil {
				if err.Error() != c.possibleErrorOutput[idx] {
					t.Fatalf("Got an error %q but should get %q", err.Error(), c.possibleErrorOutput[idx])
				}
			} else {
				t.Fatalf("Expect to get an error %q but got nil", c.possibleErrorOutput[idx])
			}
		} else {
			if err != nil {
				t.Fatal("Expect to get no error but got", err)
			}
		}
	}
}
