github.com/pelicanplatform/pelican@v1.0.5/cmd/main_test.go (about)

     1  package main
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"os"
     7  	"os/exec"
     8  	"runtime"
     9  	"strings"
    10  
    11  	"testing"
    12  
    13  	"github.com/stretchr/testify/assert"
    14  )
    15  
    16  func TestHandleCLIVersionFlag(t *testing.T) {
    17  	version = "0.0.1"
    18  	date = "2023-10-06T15:26:50Z"
    19  	commit = "f0f94a3edf6641c2472345819a0d5453fc9e68d1"
    20  	builtBy = "goreleaser"
    21  
    22  	// Reset os.Args to ensure Windows doesn't do weird things to the test
    23  	oldArgs := os.Args
    24  	os.Args = []string{os.Args[0]}
    25  
    26  	mockVersionOutput := fmt.Sprintf(
    27  		"Version: %s\nBuild Date: %s\nBuild Commit: %s\nBuilt By: %s",
    28  		version, date, commit, builtBy,
    29  	)
    30  
    31  	testCases := []struct {
    32  		name     string
    33  		args     []string
    34  		expected string
    35  	}{
    36  		// The choice of Long and Short is based on the current pattern we have
    37  		// that only root command has Long description and Short description
    38  		// for the rest of the subcommands
    39  		{
    40  			"no-flag-on-root-command",
    41  			[]string{"pelican"},
    42  			rootCmd.Long,
    43  		},
    44  		{
    45  			"no-flag-on-subcommand",
    46  			[]string{"pelican", "origin"},
    47  			originCmd.Short,
    48  		},
    49  		{
    50  			"flag-on-root-command",
    51  			[]string{"pelican", "--version"},
    52  			mockVersionOutput,
    53  		},
    54  		{
    55  			"flag-on-subcommand",
    56  			[]string{"pelican", "origin", "--version"},
    57  			mockVersionOutput,
    58  		},
    59  		{
    60  			"flag-on-second-layer-subcommand",
    61  			[]string{"pelican", "origin", "get", "--version"},
    62  			mockVersionOutput,
    63  		},
    64  		{
    65  			"other-flag-on-root-command",
    66  			[]string{"pelican", "--help"},
    67  			rootCmd.Long,
    68  		},
    69  	}
    70  
    71  	batchTest := func(t *testing.T, arguments []string, expected string) {
    72  		// Redirect output to a pip
    73  		oldStdout := os.Stdout
    74  		r, w, _ := os.Pipe()
    75  		os.Stdout = w
    76  
    77  		err := handleCLI(arguments)
    78  
    79  		// Close the write of pip and redirect output back to stdout
    80  		w.Close()
    81  		out, _ := io.ReadAll(r)
    82  		os.Stdout = oldStdout
    83  
    84  		got := strings.TrimSpace(string(out))
    85  		assert.NoError(t, err, "Should not have error running the function")
    86  		if expected != mockVersionOutput {
    87  			// If the expected string is not the version output, use Contains to check
    88  			// This is mainly for checking against command help output
    89  			assert.Contains(t, got, expected, "Output does not match expectation")
    90  		} else {
    91  			assert.Equal(t, expected, got, "Output does not match expectation")
    92  		}
    93  	}
    94  
    95  	for _, tc := range testCases {
    96  		t.Run(tc.name, func(t *testing.T) {
    97  			batchTest(t, tc.args, tc.expected)
    98  		})
    99  	}
   100  
   101  	// Restore the args back when test finished
   102  	os.Args = oldArgs
   103  }
   104  
   105  func TestHandleCLIExecutableAlias(t *testing.T) {
   106  	// If we're in the process started by exec.Command, run the handleCLI function.
   107  	if os.Getenv("BE_CRASHER") == "1" {
   108  		err := handleCLI(os.Args[1:])
   109  		if err != nil {
   110  			t.Fatalf("Function returns error")
   111  		}
   112  		return
   113  	}
   114  
   115  	oldArgs := os.Args
   116  	os.Args = []string{}
   117  	defer func() {
   118  		os.Args = oldArgs
   119  	}()
   120  	testCases := []struct {
   121  		name     string
   122  		args     []string
   123  		expected string
   124  	}{
   125  		{
   126  			"no-alias",
   127  			[]string{"pelican"},
   128  			rootCmd.Long,
   129  		},
   130  		{
   131  			"stashcp",
   132  			[]string{"stashcp"},
   133  			"No Source or Destination", // slightly different error message, good for testing though
   134  		},
   135  		{
   136  			"stash_plugin",
   137  			[]string{"stash_plugin"},
   138  			"No source or destination specified",
   139  		},
   140  		{
   141  			"osdf_plugin",
   142  			[]string{"stash_plugin"},
   143  			"No source or destination specified",
   144  		},
   145  		{
   146  			"pelican_xfer_plugin",
   147  			[]string{"stash_plugin"},
   148  			"No source or destination specified",
   149  		},
   150  	}
   151  
   152  	batchTest := func(t *testing.T, arguments []string, expected string) {
   153  		// Compile the test binary.
   154  		cmd := exec.Command("go", "build", "-o", arguments[0], ".")
   155  		err := cmd.Run()
   156  		if err != nil {
   157  			t.Fatal(err)
   158  		}
   159  		defer os.Remove(arguments[0]) // Clean up the test binary when done.
   160  
   161  		// Run the test binary with the BE_CRASHER environment variable set.
   162  		cmd = exec.Command("./"+arguments[0], arguments[1:]...)
   163  		cmd.Env = append(os.Environ(), "BE_CRASHER=1")
   164  
   165  		// Set up pipes to capture stdout and stderr.
   166  		stdout, _ := cmd.StdoutPipe()
   167  		stderr, _ := cmd.StderrPipe()
   168  		if err := cmd.Start(); err != nil {
   169  			t.Fatal(err)
   170  		}
   171  
   172  		// Read and capture stdout and stderr.
   173  		gotBytes, _ := io.ReadAll(stdout)
   174  		errBytes, _ := io.ReadAll(stderr)
   175  
   176  		// Wait for the command to finish.
   177  		err = cmd.Wait()
   178  
   179  		got := strings.TrimSpace(string(gotBytes))
   180  		errString := strings.TrimSpace(string(errBytes))
   181  
   182  		// Now you can check the output and the error against your expectations.
   183  		// If the command exited with a non-zero status, 'err' will be non-nil.
   184  		if err != nil {
   185  			_, ok := err.(*exec.ExitError)
   186  			if !ok {
   187  				t.Fatal("Failed to cast error as *exec.ExitError")
   188  			}
   189  		}
   190  		// Apparently both stashcp and *_plug will trigger Exit(1) with error if
   191  		// the arguments are not enough/solid
   192  		if strings.ToLower(strings.TrimSuffix(arguments[0], ".exe")) != "pelican" {
   193  			assert.Contains(t, errString, expected, "Output does not match expectation")
   194  		} else {
   195  			assert.NoError(t, err, "Should not have error running the function: "+errString)
   196  			assert.Contains(t, got, expected, "Output does not match expectation")
   197  		}
   198  	}
   199  	for _, tc := range testCases {
   200  		if os := runtime.GOOS; os == "windows" {
   201  			// On Windows, you can only do *.exe
   202  			t.Run(tc.name+"-windows", func(t *testing.T) {
   203  				preserve := tc.args[0]
   204  				tc.args[0] = preserve + ".exe"
   205  				batchTest(t, tc.args, tc.expected)
   206  				tc.args[0] = preserve
   207  			})
   208  		} else {
   209  			t.Run(tc.name, func(t *testing.T) {
   210  				batchTest(t, tc.args, tc.expected)
   211  			})
   212  			t.Run(tc.name+"-windows", func(t *testing.T) {
   213  				preserve := tc.args[0]
   214  				tc.args[0] = preserve + ".exe"
   215  				batchTest(t, tc.args, tc.expected)
   216  				tc.args[0] = preserve
   217  			})
   218  			t.Run(tc.name+"-mixedCase", func(t *testing.T) {
   219  				preserve := tc.args[0]
   220  				tc.args[0] = strings.ToUpper(preserve)
   221  				batchTest(t, tc.args, tc.expected)
   222  				tc.args[0] = preserve
   223  			})
   224  		}
   225  	}
   226  }