// Copyright 2025 The Dawn & Tint Authors
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
//  1. Redistributions of source code must retain the above copyright notice, this
//     list of conditions and the following disclaimer.
//
//  2. Redistributions in binary form must reproduce the above copyright notice,
//     this list of conditions and the following disclaimer in the documentation
//     and/or other materials provided with the distribution.
//
//  3. Neither the name of the copyright holder nor the names of its
//     contributors may be used to endorse or promote products derived from
//     this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

package execwrapper_test

import (
	"bytes"
	"context"
	"errors"
	"testing"

	"dawn.googlesource.com/dawn/tools/src/execwrapper"
	"github.com/stretchr/testify/require"
)

var errTest = errors.New("test error")

func TestTestCmdWrapper_Run(t *testing.T) {
	for _, tc := range []struct {
		name       string
		useContext bool
		stdout     []byte
		stderr     []byte
		err        error
		wantStdout string
		wantStderr string
		wantErr    error
	}{
		{
			name:       "Command/NoOutput",
			useContext: false,
			stdout:     nil,
			stderr:     nil,
			err:        nil,
			wantStdout: "",
			wantStderr: "",
			wantErr:    nil,
		},
		{
			name:       "Command/Stderr",
			useContext: false,
			stdout:     nil,
			stderr:     []byte("stderr"),
			err:        nil,
			wantStdout: "",
			wantStderr: "stderr",
			wantErr:    nil,
		},
		{
			name:       "Command/Stdout",
			useContext: false,
			stdout:     []byte("stdout"),
			stderr:     nil,
			err:        nil,
			wantStdout: "stdout",
			wantStderr: "",
			wantErr:    nil,
		},
		{
			name:       "Command/Error",
			useContext: false,
			stdout:     nil,
			stderr:     nil,
			err:        errTest,
			wantStdout: "",
			wantStderr: "",
			wantErr:    errTest,
		},
		{
			name:       "CommandContext/NoOutput",
			useContext: true,
			stdout:     nil,
			stderr:     nil,
			err:        nil,
			wantStdout: "",
			wantStderr: "",
			wantErr:    nil,
		},
		{
			name:       "CommandContext/Stderr",
			useContext: true,
			stdout:     nil,
			stderr:     []byte("stderr"),
			err:        nil,
			wantStdout: "",
			wantStderr: "stderr",
			wantErr:    nil,
		},
		{
			name:       "CommandContext/Stdout",
			useContext: true,
			stdout:     []byte("stdout"),
			stderr:     nil,
			err:        nil,
			wantStdout: "stdout",
			wantStderr: "",
			wantErr:    nil,
		},
		{
			name:       "CommandContext/Error",
			useContext: true,
			stdout:     nil,
			stderr:     nil,
			err:        errTest,
			wantStdout: "",
			wantStderr: "",
			wantErr:    errTest,
		},
	} {
		tc := tc
		t.Run(tc.name, func(t *testing.T) {
			var ew execwrapper.ExecWrapper
			if tc.err != nil {
				ew = execwrapper.NewTestExecWrapperForFailure(tc.err)
			} else {
				ew = execwrapper.NewTestExecWrapperForSuccess(tc.stdout, tc.stderr)
			}

			var cmd execwrapper.CmdWrapper
			if tc.useContext {
				cmd = ew.CommandContext(context.Background(), "test")
			} else {
				cmd = ew.Command("test")
			}

			var stdout, stderr bytes.Buffer
			cmd.WithStdout(&stdout).WithStderr(&stderr)

			err := cmd.Run()

			if tc.wantErr != nil {
				require.ErrorIs(t, err, tc.wantErr)
			} else {
				require.NoError(t, err)
				require.Equal(t, tc.wantStdout, stdout.String())
				require.Equal(t, tc.wantStderr, stderr.String())
			}
		})
	}
}

func TestTestCmdWrapper_RunCombinedOutput(t *testing.T) {
	for _, tc := range []struct {
		name       string
		useContext bool
		stdout     []byte
		stderr     []byte
		err        error
		withStdout bool
		withStderr bool
		wantOutput string
		wantErr    error
	}{
		{
			name:       "Command/Success",
			useContext: false,
			stdout:     []byte("stdout"),
			stderr:     []byte("stderr"),
			err:        nil,
			wantOutput: "stdoutstderr",
		},
		{
			name:       "Command/NoOutput",
			useContext: false,
			wantOutput: "",
		},
		{
			name:       "Command/Error",
			useContext: false,
			stdout:     []byte("stdout"),
			stderr:     []byte("stderr"),
			err:        errTest,
			wantErr:    errTest,
		},
		{
			name:       "Command/WithStdout",
			useContext: false,
			withStdout: true,
			wantErr:    execwrapper.ErrCombinedOutputWithWriters,
		},
		{
			name:       "Command/WithStderr",
			useContext: false,
			withStderr: true,
			wantErr:    execwrapper.ErrCombinedOutputWithWriters,
		},
		{
			name:       "CommandContext/Success",
			useContext: true,
			stdout:     []byte("stdout"),
			stderr:     []byte("stderr"),
			err:        nil,
			wantOutput: "stdoutstderr",
		},
		{
			name:       "CommandContext/NoOutput",
			useContext: true,
			wantOutput: "",
		},
		{
			name:       "CommandContext/Error",
			useContext: true,
			stdout:     []byte("stdout"),
			stderr:     []byte("stderr"),
			err:        errTest,
			wantErr:    errTest,
		},
		{
			name:       "CommandContext/WithStdout",
			useContext: true,
			withStdout: true,
			wantErr:    execwrapper.ErrCombinedOutputWithWriters,
		},
		{
			name:       "CommandContext/WithStderr",
			useContext: true,
			withStderr: true,
			wantErr:    execwrapper.ErrCombinedOutputWithWriters,
		},
	} {
		tc := tc
		t.Run(tc.name, func(t *testing.T) {
			var ew execwrapper.ExecWrapper
			if tc.err != nil {
				ew = execwrapper.NewTestExecWrapperForFailure(tc.err)
			} else {
				ew = execwrapper.NewTestExecWrapperForSuccess(tc.stdout, tc.stderr)
			}

			var cmd execwrapper.CmdWrapper
			if tc.useContext {
				cmd = ew.CommandContext(context.Background(), "test")
			} else {
				cmd = ew.Command("test")
			}

			if tc.withStdout {
				cmd.WithStdout(&bytes.Buffer{})
			}
			if tc.withStderr {
				cmd.WithStderr(&bytes.Buffer{})
			}

			out, err := cmd.RunWithCombinedOutput()

			if tc.wantErr != nil {
				require.ErrorIs(t, err, tc.wantErr)
			} else {
				require.NoError(t, err)
			}

			if tc.withStdout || tc.withStderr {
				require.Nil(t, out)
			} else {
				require.Equal(t, tc.wantOutput, string(out))
			}
		})
	}
}
