src.elv.sh@v0.21.0-dev.0.20240515223629-06979efb9a2a/pkg/prog/prog_test.go (about)

     1  package prog_test
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"os"
     7  	"testing"
     8  
     9  	. "src.elv.sh/pkg/prog"
    10  	"src.elv.sh/pkg/prog/progtest"
    11  	"src.elv.sh/pkg/testutil"
    12  )
    13  
    14  var (
    15  	Test       = progtest.Test
    16  	ThatElvish = progtest.ThatElvish
    17  )
    18  
    19  func TestFlagHandling(t *testing.T) {
    20  	Test(t, &testProgram{},
    21  		ThatElvish("-bad-flag").
    22  			ExitsWith(2).
    23  			WritesStderrContaining("flag provided but not defined: -bad-flag\nUsage:"),
    24  		// -h is treated as a bad flag
    25  		ThatElvish("-h").
    26  			ExitsWith(2).
    27  			WritesStderrContaining("flag provided but not defined: -h\nUsage:"),
    28  
    29  		ThatElvish("-help").
    30  			WritesStdoutContaining("Usage: elvish [flags] [script]"),
    31  	)
    32  }
    33  
    34  func TestLogFlag(t *testing.T) {
    35  	testutil.InTempDir(t)
    36  	Test(t, &testProgram{},
    37  		ThatElvish("-log", "log").DoesNothing(),
    38  		ThatElvish("-log", "bad/log").WritesStderrContaining("open bad/log:"),
    39  	)
    40  
    41  	_, err := os.Stat("log")
    42  	if err != nil {
    43  		t.Errorf("log file was not created: %v", err)
    44  	}
    45  }
    46  
    47  func TestCustomFlag(t *testing.T) {
    48  	Test(t, &testProgram{customFlag: true},
    49  		ThatElvish("-flag", "foo").
    50  			WritesStdout("-flag foo\n"),
    51  	)
    52  }
    53  
    54  func TestSharedFlags(t *testing.T) {
    55  	Test(t, &testProgram{sharedFlags: true},
    56  		ThatElvish("-sock", "sock", "-db", "db", "-json").
    57  			WritesStdout("-sock sock -db db -json true\n"),
    58  	)
    59  }
    60  
    61  func TestSharedFlags_MultiplePrograms(t *testing.T) {
    62  	Test(t,
    63  		Composite(
    64  			&testProgram{sharedFlags: true, returnErr: NextProgram()},
    65  			&testProgram{sharedFlags: true}),
    66  		ThatElvish("-sock", "sock", "-db", "db", "-json").
    67  			WritesStdout("-sock sock -db db -json true\n"),
    68  	)
    69  }
    70  
    71  func TestShowDeprecations(t *testing.T) {
    72  	testutil.Set(t, &DeprecationLevel, 0)
    73  
    74  	Test(t, &testProgram{},
    75  		ThatElvish("-deprecation-level", "42").DoesNothing(),
    76  	)
    77  
    78  	if DeprecationLevel != 42 {
    79  		t.Errorf("ShowDeprecations = %d, want 42", DeprecationLevel)
    80  	}
    81  }
    82  
    83  func TestComposite(t *testing.T) {
    84  	Test(t,
    85  		Composite(
    86  			&testProgram{returnErr: NextProgram()},
    87  			&testProgram{writeOut: "program 2"}),
    88  		ThatElvish().WritesStdout("program 2"),
    89  	)
    90  }
    91  
    92  func TestComposite_NoSuitableSubprogram(t *testing.T) {
    93  	Test(t,
    94  		Composite(
    95  			&testProgram{returnErr: NextProgram()},
    96  			&testProgram{returnErr: NextProgram()}),
    97  		ThatElvish().
    98  			ExitsWith(2).
    99  			WritesStderr("internal error: no suitable subprogram\n"),
   100  	)
   101  }
   102  
   103  func TestComposite_RunsCleanupsIfAnyProgramIsRun(t *testing.T) {
   104  	Test(t,
   105  		Composite(
   106  			&testProgram{returnErr: NextProgram(func(fds [3]*os.File) {
   107  				fds[1].WriteString("program 1 cleanup\n")
   108  			})},
   109  			&testProgram{returnErr: NextProgram(func(fds [3]*os.File) {
   110  				fds[1].WriteString("program 2 cleanup\n")
   111  			})},
   112  			&testProgram{writeOut: "program 3\n"}),
   113  		ThatElvish().
   114  			WritesStdout("program 3\nprogram 2 cleanup\nprogram 1 cleanup\n"),
   115  	)
   116  }
   117  
   118  func TestComposite_RunsCleanupsEvenIfProgramReturnsError(t *testing.T) {
   119  	Test(t,
   120  		Composite(
   121  			&testProgram{returnErr: NextProgram(func(fds [3]*os.File) {
   122  				fds[1].WriteString("program 1 cleanup\n")
   123  			})},
   124  			&testProgram{returnErr: errors.New("program 2 error")}),
   125  		ThatElvish().
   126  			ExitsWith(2).
   127  			WritesStderr("program 2 error\n").
   128  			WritesStdout("program 1 cleanup\n"),
   129  	)
   130  }
   131  
   132  func TestComposite_SkipsCleanupsIfAllProgramsReturnNextProgram(t *testing.T) {
   133  	Test(t,
   134  		Composite(
   135  			&testProgram{returnErr: NextProgram(func(fds [3]*os.File) {
   136  				fds[1].WriteString("program 1 cleanup\n")
   137  			})},
   138  			&testProgram{returnErr: NextProgram()}),
   139  		ThatElvish().
   140  			ExitsWith(2).
   141  			WritesStderr("internal error: no suitable subprogram\n"),
   142  	)
   143  }
   144  
   145  func TestComposite_PreferEarlierSubprogram(t *testing.T) {
   146  	Test(t,
   147  		Composite(
   148  			&testProgram{writeOut: "program 1"},
   149  			&testProgram{writeOut: "program 2"}),
   150  		ThatElvish().WritesStdout("program 1"),
   151  	)
   152  }
   153  
   154  func TestBadUsageError(t *testing.T) {
   155  	Test(t,
   156  		&testProgram{returnErr: BadUsage("lorem ipsum")},
   157  		ThatElvish().ExitsWith(2).WritesStderrContaining("lorem ipsum\n"),
   158  	)
   159  }
   160  
   161  func TestExitError(t *testing.T) {
   162  	Test(t, &testProgram{returnErr: Exit(3)},
   163  		ThatElvish().ExitsWith(3),
   164  	)
   165  }
   166  
   167  func TestExitError_0(t *testing.T) {
   168  	Test(t, &testProgram{returnErr: Exit(0)},
   169  		ThatElvish().ExitsWith(0),
   170  	)
   171  }
   172  
   173  type testProgram struct {
   174  	writeOut    string
   175  	returnErr   error
   176  	customFlag  bool
   177  	sharedFlags bool
   178  
   179  	flag        string
   180  	daemonPaths *DaemonPaths
   181  	json        *bool
   182  }
   183  
   184  func (p *testProgram) RegisterFlags(f *FlagSet) {
   185  	if p.customFlag {
   186  		f.StringVar(&p.flag, "flag", "default", "a flag")
   187  	}
   188  	if p.sharedFlags {
   189  		p.daemonPaths = f.DaemonPaths()
   190  		p.json = f.JSON()
   191  	}
   192  }
   193  
   194  func (p *testProgram) Run(fds [3]*os.File, args []string) error {
   195  	if p.returnErr != nil {
   196  		return p.returnErr
   197  	}
   198  	fds[1].WriteString(p.writeOut)
   199  	if p.customFlag {
   200  		fmt.Fprintf(fds[1], "-flag %s\n", p.flag)
   201  	}
   202  	if p.sharedFlags {
   203  		fmt.Fprintf(fds[1], "-sock %s -db %s -json %v\n",
   204  			p.daemonPaths.Sock, p.daemonPaths.DB, *p.json)
   205  	}
   206  	return nil
   207  }