1- package reexec
1+ package reexec_test
22
33import (
44 "context"
55 "errors"
66 "fmt"
77 "os"
8- "os/exec"
98 "path/filepath"
109 "reflect"
10+ "runtime"
1111 "strings"
1212 "testing"
1313 "time"
14+
15+ "github.com/moby/sys/reexec"
16+ "github.com/moby/sys/reexec/internal/reexecoverride"
1417)
1518
1619const (
@@ -20,23 +23,26 @@ const (
2023)
2124
2225func init () {
23- Register (testReExec , func () {
26+ reexec . Register (testReExec , func () {
2427 panic ("Return Error" )
2528 })
26- Register (testReExec2 , func () {
29+ reexec . Register (testReExec2 , func () {
2730 var args string
2831 if len (os .Args ) > 1 {
2932 args = fmt .Sprintf ("(args: %#v)" , os .Args [1 :])
3033 }
3134 fmt .Println ("Hello" , testReExec2 , args )
3235 os .Exit (0 )
3336 })
34- Register (testReExec3 , func () {
37+ reexec . Register (testReExec3 , func () {
3538 fmt .Println ("Hello " + testReExec3 )
3639 time .Sleep (1 * time .Second )
3740 os .Exit (0 )
3841 })
39- Init ()
42+ if reexec .Init () {
43+ // Make sure we exit in case re-exec didn't os.Exit on its own.
44+ os .Exit (0 )
45+ }
4046}
4147
4248func TestRegister (t * testing.T ) {
@@ -69,7 +75,7 @@ func TestRegister(t *testing.T) {
6975 t .Errorf ("got %q, want %q" , r , tc .expectedErr )
7076 }
7177 }()
72- Register (tc .name , func () {})
78+ reexec . Register (tc .name , func () {})
7379 })
7480 }
7581}
@@ -98,7 +104,7 @@ func TestCommand(t *testing.T) {
98104 }
99105 for _ , tc := range tests {
100106 t .Run (tc .doc , func (t * testing.T ) {
101- cmd := Command (tc .cmdAndArgs ... )
107+ cmd := reexec . Command (tc .cmdAndArgs ... )
102108 if ! reflect .DeepEqual (cmd .Args , tc .cmdAndArgs ) {
103109 t .Fatalf ("got %+v, want %+v" , cmd .Args , tc .cmdAndArgs )
104110 }
@@ -165,7 +171,7 @@ func TestCommandContext(t *testing.T) {
165171 ctx , cancel := context .WithTimeout (context .Background (), 100 * time .Millisecond )
166172 defer cancel ()
167173
168- cmd := CommandContext (ctx , tc .cmdAndArgs ... )
174+ cmd := reexec . CommandContext (ctx , tc .cmdAndArgs ... )
169175 if ! reflect .DeepEqual (cmd .Args , tc .cmdAndArgs ) {
170176 t .Fatalf ("got %+v, want %+v" , cmd .Args , tc .cmdAndArgs )
171177 }
@@ -194,30 +200,88 @@ func TestCommandContext(t *testing.T) {
194200 }
195201}
196202
197- func TestNaiveSelf (t * testing.T ) {
198- if os .Getenv ("TEST_CHECK" ) == "1" {
199- os .Exit (2 )
200- }
201- cmd := exec .Command (naiveSelf (), "-test.run=TestNaiveSelf" )
202- cmd .Env = append (os .Environ (), "TEST_CHECK=1" )
203- err := cmd .Start ()
203+ // TestRunNaiveSelf verifies that reexec.Self() (and thus CommandContext)
204+ // can resolve a path that can be used to re-execute the current test binary
205+ // when it falls back to the argv[0]-based implementation.
206+ //
207+ // It forces Self() to bypass the Linux /proc/self/exe fast-path via
208+ // [reexecoverride.OverrideArgv0] so that the fallback logic is exercised
209+ // consistently across platforms.
210+ func TestRunNaiveSelf (t * testing.T ) {
211+ ctx , cancel := context .WithTimeout (context .Background (), 100 * time .Millisecond )
212+ defer cancel ()
213+
214+ // Force Self() to use naiveSelf(os.Args[0]), instead of "/proc/self/exe" on Linux.
215+ reexecoverride .OverrideArgv0 (t , os .Args [0 ])
216+
217+ cmd := reexec .CommandContext (ctx , testReExec2 )
218+ out , err := cmd .CombinedOutput ()
204219 if err != nil {
205220 t .Fatalf ("Unable to start command: %v" , err )
206221 }
207- err = cmd .Wait ()
208222
209- var expError * exec.ExitError
210- if ! errors .As (err , & expError ) {
211- t .Fatalf ("got %T, want %T" , err , expError )
212- }
213-
214- const expected = "exit status 2"
215- if err .Error () != expected {
216- t .Fatalf ("got %v, want %v" , err , expected )
223+ expOut := "Hello test-reexec2"
224+ actual := strings .TrimSpace (string (out ))
225+ if actual != expOut {
226+ t .Errorf ("got %v, want %v" , actual , expOut )
217227 }
228+ }
218229
219- os .Args [0 ] = "mkdir"
220- if naiveSelf () == os .Args [0 ] {
221- t .Fatalf ("Expected naiveSelf to resolve the location of mkdir" )
222- }
230+ func TestNaiveSelfResolve (t * testing.T ) {
231+ t .Run ("fast path on Linux" , func (t * testing.T ) {
232+ if runtime .GOOS != "linux" {
233+ t .Skip ("only supported on Linux" )
234+ }
235+ resolved := reexec .Self ()
236+ expected := "/proc/self/exe"
237+ if resolved != expected {
238+ t .Errorf ("got %v, want %v" , resolved , expected )
239+ }
240+ })
241+ t .Run ("resolve in PATH" , func (t * testing.T ) {
242+ executable := "sh"
243+ if runtime .GOOS == "windows" {
244+ executable = "cmd"
245+ }
246+ reexecoverride .OverrideArgv0 (t , executable )
247+ resolved := reexec .Self ()
248+ if resolved == executable {
249+ t .Errorf ("did not resolve via PATH; got %q" , resolved )
250+ }
251+ if ! filepath .IsAbs (resolved ) {
252+ t .Errorf ("expected absolute path; got %q" , resolved )
253+ }
254+ })
255+ t .Run ("not in PATH" , func (t * testing.T ) {
256+ const executable = "some-nonexistent-executable"
257+ want , err := filepath .Abs (executable )
258+ if err != nil {
259+ t .Fatal (err )
260+ }
261+ reexecoverride .OverrideArgv0 (t , executable )
262+ resolved := reexec .Self ()
263+ if resolved != want {
264+ t .Errorf ("expected absolute path; got %q, want %q" , resolved , want )
265+ }
266+ })
267+ t .Run ("relative path" , func (t * testing.T ) {
268+ executable := filepath .Join ("." , "some-executable" )
269+ want , err := filepath .Abs (executable )
270+ if err != nil {
271+ t .Fatal (err )
272+ }
273+ reexecoverride .OverrideArgv0 (t , executable )
274+ resolved := reexec .Self ()
275+ if resolved != want {
276+ t .Errorf ("expected absolute path; got %q, want %q" , resolved , want )
277+ }
278+ })
279+ t .Run ("absolute path unchanged" , func (t * testing.T ) {
280+ executable := filepath .Join (os .TempDir (), "some-executable" )
281+ reexecoverride .OverrideArgv0 (t , executable )
282+ resolved := reexec .Self ()
283+ if resolved != executable {
284+ t .Errorf ("should not modify absolute paths; got %q, want %q" , resolved , executable )
285+ }
286+ })
223287}
0 commit comments