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 }