github.com/tri-adam/singularity@v3.1.1+incompatible/cmd/singularity/actions_test.go (about)

     1  // Copyright (c) 2018, Sylabs Inc. All rights reserved.
     2  // This software is licensed under a 3-clause BSD license. Please consult the
     3  // LICENSE.md file distributed with the sources of this project regarding your
     4  // rights to use or distribute this software.
     5  
     6  package main
     7  
     8  import (
     9  	"bytes"
    10  	"fmt"
    11  	"io/ioutil"
    12  	"os"
    13  	"os/exec"
    14  	"strconv"
    15  	"strings"
    16  	"syscall"
    17  	"testing"
    18  
    19  	"github.com/sylabs/singularity/internal/pkg/test"
    20  )
    21  
    22  //build base image for tests
    23  const imagePath = "./container.sif"
    24  const appsImage = "./appsImage.sif"
    25  
    26  type opts struct {
    27  	binds     []string
    28  	security  []string
    29  	keepPrivs bool
    30  	dropCaps  string
    31  	contain   bool
    32  	noHome    bool
    33  	home      string
    34  	workdir   string
    35  	pwd       string
    36  	app       string
    37  	overlay   []string
    38  	userns    bool
    39  }
    40  
    41  // imageExec can be used to run/exec/shell a Singularity image
    42  // it return the exitCode and err of the execution
    43  func imageExec(t *testing.T, action string, opts opts, imagePath string, command []string) (stdout string, stderr string, exitCode int, err error) {
    44  	// action can be run/exec/shell
    45  	argv := []string{action}
    46  	for _, bind := range opts.binds {
    47  		argv = append(argv, "--bind", bind)
    48  	}
    49  	for _, sec := range opts.security {
    50  		argv = append(argv, "--security", sec)
    51  	}
    52  	if opts.keepPrivs {
    53  		argv = append(argv, "--keep-privs")
    54  	}
    55  	if opts.dropCaps != "" {
    56  		argv = append(argv, "--drop-caps", opts.dropCaps)
    57  	}
    58  	if opts.contain {
    59  		argv = append(argv, "--contain")
    60  	}
    61  	if opts.noHome {
    62  		argv = append(argv, "--no-home")
    63  	}
    64  	if opts.home != "" {
    65  		argv = append(argv, "--home", opts.home)
    66  	}
    67  	for _, fs := range opts.overlay {
    68  		argv = append(argv, "--overlay", fs)
    69  	}
    70  	if opts.workdir != "" {
    71  		argv = append(argv, "--workdir", opts.workdir)
    72  	}
    73  	if opts.pwd != "" {
    74  		argv = append(argv, "--pwd", opts.pwd)
    75  	}
    76  	if opts.app != "" {
    77  		argv = append(argv, "--app", opts.app)
    78  	}
    79  	if opts.userns {
    80  		argv = append(argv, "-u")
    81  	}
    82  	argv = append(argv, imagePath)
    83  	argv = append(argv, command...)
    84  
    85  	var outbuf, errbuf bytes.Buffer
    86  	cmd := exec.Command(cmdPath, argv...)
    87  
    88  	cmd.Stdout = &outbuf
    89  	cmd.Stderr = &errbuf
    90  
    91  	if err := cmd.Start(); err != nil {
    92  		t.Fatalf("cmd.Start: %v", err)
    93  	}
    94  
    95  	// retrieve exit code
    96  	if err := cmd.Wait(); err != nil {
    97  		if _, ok := err.(*exec.ExitError); ok {
    98  			// The program has exited with an exit code != 0
    99  			exitCode = 1
   100  		}
   101  	}
   102  
   103  	stdout = outbuf.String()
   104  	stderr = errbuf.String()
   105  
   106  	return
   107  }
   108  
   109  // testSingularityRun tests min fuctionality for singularity run
   110  func testSingularityRun(t *testing.T) {
   111  	tests := []struct {
   112  		name   string
   113  		image  string
   114  		action string
   115  		argv   []string
   116  		opts
   117  		exit          int
   118  		expectSuccess bool
   119  	}{
   120  		{"NoCommand", imagePath, "run", []string{}, opts{}, 0, true},
   121  		{"true", imagePath, "run", []string{"true"}, opts{}, 0, true},
   122  		{"false", imagePath, "run", []string{"false"}, opts{}, 1, false},
   123  		{"ScifTestAppGood", imagePath, "run", []string{}, opts{app: "testapp"}, 0, true},
   124  		{"ScifTestAppBad", imagePath, "run", []string{}, opts{app: "fakeapp"}, 1, false},
   125  	}
   126  
   127  	for _, tt := range tests {
   128  		t.Run(tt.name, test.WithoutPrivilege(func(t *testing.T) {
   129  			_, stderr, exitCode, err := imageExec(t, tt.action, tt.opts, tt.image, tt.argv)
   130  			if tt.expectSuccess && (exitCode != 0) {
   131  				t.Log(stderr)
   132  				t.Fatalf("unexpected failure running '%v': %v", strings.Join(tt.argv, " "), err)
   133  			} else if !tt.expectSuccess && (exitCode != 1) {
   134  				t.Log(stderr)
   135  				t.Fatalf("unexpected success running '%v'", strings.Join(tt.argv, " "))
   136  			}
   137  		}))
   138  	}
   139  }
   140  
   141  // testSingularityExec tests min fuctionality for singularity exec
   142  func testSingularityExec(t *testing.T) {
   143  	// Create a temp testfile
   144  	tmpfile, err := ioutil.TempFile("", "testSingularityExec.tmp")
   145  	if err != nil {
   146  		t.Fatal(err)
   147  	}
   148  	defer os.Remove(tmpfile.Name()) // clean up
   149  
   150  	testfile, err := tmpfile.Stat()
   151  	if err != nil {
   152  		t.Fatal(err)
   153  	}
   154  
   155  	pwd, err := os.Getwd()
   156  	if err != nil {
   157  		t.Fatal(err)
   158  	}
   159  
   160  	tests := []struct {
   161  		name   string
   162  		image  string
   163  		action string
   164  		argv   []string
   165  		opts
   166  		exit          int
   167  		expectSuccess bool
   168  	}{
   169  		{"NoCommand", imagePath, "exec", []string{}, opts{}, 1, false},
   170  		{"true", imagePath, "exec", []string{"true"}, opts{}, 0, true},
   171  		{"trueAbsPAth", imagePath, "exec", []string{"/bin/true"}, opts{}, 0, true},
   172  		{"false", imagePath, "exec", []string{"false"}, opts{}, 1, false},
   173  		{"falseAbsPath", imagePath, "exec", []string{"/bin/false"}, opts{}, 1, false},
   174  		// Scif apps tests
   175  		{"ScifTestAppGood", imagePath, "exec", []string{"testapp.sh"}, opts{app: "testapp"}, 0, true},
   176  		{"ScifTestAppBad", imagePath, "exec", []string{"testapp.sh"}, opts{app: "fakeapp"}, 1, false},
   177  		{"ScifTestfolderOrg", appsImage, "exec", []string{"test", "-d", "/scif"}, opts{}, 0, true},
   178  		{"ScifTestfolderOrg", appsImage, "exec", []string{"test", "-d", "/scif/apps"}, opts{}, 0, true},
   179  		{"ScifTestfolderOrg", appsImage, "exec", []string{"test", "-d", "/scif/data"}, opts{}, 0, true},
   180  		{"ScifTestfolderOrg", appsImage, "exec", []string{"test", "-d", "/scif/apps/foo"}, opts{}, 0, true},
   181  		{"ScifTestfolderOrg", appsImage, "exec", []string{"test", "-d", "/scif/apps/bar"}, opts{}, 0, true},
   182  		// blocked by issue [scif-apps] Files created at install step fall into an unexpected path #2404
   183  		{"ScifTestfolderOrg", appsImage, "exec", []string{"test", "-f", "/scif/apps/foo/filefoo.exec"}, opts{}, 0, true},
   184  		{"ScifTestfolderOrg", appsImage, "exec", []string{"test", "-f", "/scif/apps/bar/filebar.exec"}, opts{}, 0, true},
   185  		{"ScifTestfolderOrg", appsImage, "exec", []string{"test", "-d", "/scif/data/foo/output"}, opts{}, 0, true},
   186  		{"ScifTestfolderOrg", appsImage, "exec", []string{"test", "-d", "/scif/data/foo/input"}, opts{}, 0, true},
   187  		{"WorkdirContain", imagePath, "exec", []string{"test", "-f", tmpfile.Name()}, opts{workdir: "testdata", contain: true}, 0, false},
   188  		{"Workdir", imagePath, "exec", []string{"test", "-f", tmpfile.Name()}, opts{workdir: "testdata"}, 0, true},
   189  		{"pwdGood", imagePath, "exec", []string{"true"}, opts{pwd: "/etc"}, 0, true},
   190  		{"home", imagePath, "exec", []string{"test", "-f", tmpfile.Name()}, opts{home: pwd + "testdata"}, 0, true},
   191  		{"homePath", imagePath, "exec", []string{"test", "-f", "/home/" + testfile.Name()}, opts{home: "/tmp:/home"}, 0, true},
   192  		{"homeTmp", imagePath, "exec", []string{"true"}, opts{home: "/tmp"}, 0, true},
   193  		{"homeTmpExplicit", imagePath, "exec", []string{"true"}, opts{home: "/tmp:/home"}, 0, true},
   194  		{"ScifTestAppGood", imagePath, "exec", []string{"testapp.sh"}, opts{app: "testapp"}, 0, true},
   195  		{"ScifTestAppBad", imagePath, "exec", []string{"testapp.sh"}, opts{app: "fakeapp"}, 1, false},
   196  		//
   197  		{"userBind", imagePath, "exec", []string{"test", "-f", "/var/tmp/" + testfile.Name()}, opts{binds: []string{"/tmp:/var/tmp"}}, 0, true},
   198  	}
   199  
   200  	for _, tt := range tests {
   201  		t.Run(tt.name, test.WithoutPrivilege(func(t *testing.T) {
   202  			_, stderr, exitCode, err := imageExec(t, tt.action, tt.opts, tt.image, tt.argv)
   203  			if tt.expectSuccess && (exitCode != 0) {
   204  				t.Log(stderr)
   205  				t.Fatalf("unexpected failure running '%v': %v", strings.Join(tt.argv, " "), err)
   206  			} else if !tt.expectSuccess && (exitCode != 1) {
   207  				t.Log(stderr)
   208  				t.Fatalf("unexpected success running '%v'", strings.Join(tt.argv, " "))
   209  			}
   210  		}))
   211  	}
   212  
   213  	// test --no-home option
   214  	err = os.Chdir("/tmp")
   215  	if err != nil {
   216  		t.Fatal(err)
   217  	}
   218  	t.Run("noHome", test.WithoutPrivilege(func(t *testing.T) {
   219  		_, stderr, exitCode, err := imageExec(t, "exec", opts{noHome: true}, pwd+"/container.img", []string{"ls", "-ld", "$HOME"})
   220  		if exitCode != 1 {
   221  			t.Log(stderr, err)
   222  			t.Fatalf("unexpected success running '%v'", strings.Join([]string{"ls", "-ld", "$HOME"}, " "))
   223  		}
   224  	}))
   225  	// return to test SOURCEDIR
   226  	err = os.Chdir(pwd)
   227  	if err != nil {
   228  		t.Fatal(err)
   229  	}
   230  }
   231  
   232  // testSTDINPipe tests pipe stdin to singularity actions cmd
   233  func testSTDINPipe(t *testing.T) {
   234  	tests := []struct {
   235  		binName string
   236  		name    string
   237  		argv    []string
   238  		exit    int
   239  	}{
   240  		{"sh", "trueSTDIN", []string{"-c", fmt.Sprintf("echo hi | singularity exec %s grep hi", imagePath)}, 0},
   241  		{"sh", "falseSTDIN", []string{"-c", fmt.Sprintf("echo bye | singularity exec %s grep hi", imagePath)}, 1},
   242  		// Checking permissions
   243  		{"sh", "permissions", []string{"-c", fmt.Sprintf("singularity exec %s id -u | grep `id -u`", imagePath)}, 0},
   244  		// testing run command properly hands arguments
   245  		{"sh", "arguments", []string{"-c", fmt.Sprintf("singularity run %s foo | grep foo", imagePath)}, 0},
   246  		// Stdin to URI based image
   247  		{"sh", "library", []string{"-c", "echo true | singularity shell library://busybox"}, 0},
   248  		{"sh", "docker", []string{"-c", "echo true | singularity shell docker://busybox"}, 0},
   249  		{"sh", "shub", []string{"-c", "echo true | singularity shell shub://singularityhub/busybox"}, 0},
   250  		// Test apps
   251  		{"sh", "appsFoo", []string{"-c", fmt.Sprintf("singularity run --app foo %s | grep 'FOO'", appsImage)}, 0},
   252  		// Test target pwd
   253  		{"sh", "pwdPath", []string{"-c", fmt.Sprintf("singularity exec --pwd /etc %s pwd | egrep '^/etc'", imagePath)}, 0},
   254  	}
   255  
   256  	for _, tt := range tests {
   257  		t.Run(tt.name, test.WithoutPrivilege(func(t *testing.T) {
   258  			cmd := exec.Command(tt.binName, tt.argv...)
   259  			if err := cmd.Start(); err != nil {
   260  				t.Fatalf("cmd.Start: %v", err)
   261  			}
   262  
   263  			if err := cmd.Wait(); err != nil {
   264  				exiterr, _ := err.(*exec.ExitError)
   265  				status, _ := exiterr.Sys().(syscall.WaitStatus)
   266  				if status.ExitStatus() != tt.exit {
   267  					// The program has exited with an unexpected exit code
   268  					{
   269  						t.Fatalf("unexpected exit code '%v': for cmd %v", status.ExitStatus(), strings.Join(tt.argv, " "))
   270  					}
   271  				}
   272  			}
   273  		}))
   274  	}
   275  }
   276  
   277  // testRunFromURI tests min fuctionality for singularity run/exec URI://
   278  func testRunFromURI(t *testing.T) {
   279  	runScript := "testdata/runscript.sh"
   280  	bind := fmt.Sprintf("%s:/.singularity.d/runscript", runScript)
   281  
   282  	runOpts := opts{
   283  		binds: []string{bind},
   284  	}
   285  
   286  	fi, err := os.Stat(runScript)
   287  	if err != nil {
   288  		t.Fatalf("can't find %s", runScript)
   289  	}
   290  	size := strconv.Itoa(int(fi.Size()))
   291  
   292  	tests := []struct {
   293  		name   string
   294  		image  string
   295  		action string
   296  		argv   []string
   297  		opts
   298  		expectSuccess bool
   299  	}{
   300  		// Run from supported URI's and check the runscript call works
   301  		{"RunFromDockerOK", "docker://busybox:latest", "run", []string{size}, runOpts, true},
   302  		{"RunFromLibraryOK", "library://busybox:latest", "run", []string{size}, runOpts, true},
   303  		{"RunFromShubOK", "shub://singularityhub/busybox", "run", []string{size}, runOpts, true},
   304  		{"RunFromDockerKO", "docker://busybox:latest", "run", []string{"0"}, runOpts, false},
   305  		{"RunFromLibraryKO", "library://busybox:latest", "run", []string{"0"}, runOpts, false},
   306  		{"RunFromShubKO", "shub://singularityhub/busybox", "run", []string{"0"}, runOpts, false},
   307  		// exec from a supported URI's and check the exit code
   308  		{"trueDocker", "docker://busybox:latest", "exec", []string{"true"}, opts{}, true},
   309  		{"trueLibrary", "library://busybox:latest", "exec", []string{"true"}, opts{}, true},
   310  		{"trueShub", "shub://singularityhub/busybox", "exec", []string{"true"}, opts{}, true},
   311  		{"falseDocker", "docker://busybox:latest", "exec", []string{"false"}, opts{}, false},
   312  		{"falselibrary", "library://busybox:latest", "exec", []string{"false"}, opts{}, false},
   313  		{"falseShub", "shub://singularityhub/busybox", "exec", []string{"false"}, opts{}, false},
   314  		// exec from URI with user namespace enabled
   315  		{"trueDockerUserns", "docker://busybox:latest", "exec", []string{"true"}, opts{userns: true}, true},
   316  		{"trueLibraryUserns", "library://busybox:latest", "exec", []string{"true"}, opts{userns: true}, true},
   317  		{"trueShubUserns", "shub://singularityhub/busybox", "exec", []string{"true"}, opts{userns: true}, true},
   318  		{"falseDockerUserns", "docker://busybox:latest", "exec", []string{"false"}, opts{userns: true}, false},
   319  		{"falselibraryUserns", "library://busybox:latest", "exec", []string{"false"}, opts{userns: true}, false},
   320  		{"falseShubUserns", "shub://singularityhub/busybox", "exec", []string{"false"}, opts{userns: true}, false},
   321  	}
   322  
   323  	for _, tt := range tests {
   324  		t.Run(tt.name, test.WithoutPrivilege(func(t *testing.T) {
   325  			_, stderr, exitCode, err := imageExec(t, tt.action, tt.opts, tt.image, tt.argv)
   326  			if tt.expectSuccess && (exitCode != 0) {
   327  				t.Log(stderr)
   328  				t.Fatalf("unexpected failure running '%v': %v", strings.Join(tt.argv, " "), err)
   329  			} else if !tt.expectSuccess && (exitCode != 1) {
   330  				t.Log(stderr)
   331  				t.Fatalf("unexpected success running '%v'", strings.Join(tt.argv, " "))
   332  			}
   333  		}))
   334  	}
   335  }
   336  
   337  // testPersistentOverlay test the --overlay function
   338  func testPersistentOverlay(t *testing.T) {
   339  	const squashfsImage = "squashfs.simg"
   340  	//  Create the overlay dir
   341  	cwd, err := os.Getwd()
   342  	if err != nil {
   343  		t.Fatal(err)
   344  	}
   345  	dir, err := ioutil.TempDir(cwd, "overlay_test")
   346  	if err != nil {
   347  		t.Fatal(err)
   348  	}
   349  	defer os.RemoveAll(dir)
   350  
   351  	// Create dirfs for squashfs
   352  	squashDir, err := ioutil.TempDir(cwd, "overlay_test")
   353  	if err != nil {
   354  		t.Fatal(err)
   355  	}
   356  	defer os.RemoveAll(squashDir)
   357  
   358  	content := []byte("temporary file's content")
   359  	tmpfile, err := ioutil.TempFile(squashDir, "bogus")
   360  	if err != nil {
   361  		t.Fatal(err)
   362  	}
   363  	if _, err := tmpfile.Write(content); err != nil {
   364  		t.Fatal(err)
   365  	}
   366  	if err := tmpfile.Close(); err != nil {
   367  		t.Fatal(err)
   368  	}
   369  	defer os.Remove(tmpfile.Name())
   370  
   371  	cmd := exec.Command("mksquashfs", squashDir, squashfsImage, "-noappend", "-all-root")
   372  	var out bytes.Buffer
   373  	cmd.Stdout = &out
   374  	err = cmd.Run()
   375  	if err != nil {
   376  		t.Fatal(err)
   377  	}
   378  	defer os.RemoveAll(squashfsImage)
   379  
   380  	//  Create the overlay ext3 fs
   381  	cmd = exec.Command("dd", "if=/dev/zero", "of=ext3_fs.img", "bs=1M", "count=768", "status=none")
   382  	cmd.Stdout = &out
   383  	err = cmd.Run()
   384  	if err != nil {
   385  		t.Fatal(err)
   386  	}
   387  	cmd = exec.Command("mkfs.ext3", "-q", "-F", "ext3_fs.img")
   388  	cmd.Stdout = &out
   389  	err = cmd.Run()
   390  	if err != nil {
   391  		t.Fatal(err)
   392  	}
   393  	defer os.Remove("ext3_fs.img")
   394  
   395  	// create a file dir
   396  	t.Run("overlay_create", test.WithPrivilege(func(t *testing.T) {
   397  		_, stderr, exitCode, err := imageExec(t, "exec", opts{overlay: []string{dir}}, imagePath, []string{"touch", "/dir_overlay"})
   398  		if exitCode != 0 {
   399  			t.Log(stderr, err)
   400  			t.Fatalf("unexpected failure running '%v'", strings.Join([]string{"test", "-f", "/dir_overlay"}, " "))
   401  		}
   402  	}))
   403  	// look for the file dir
   404  	t.Run("overlay_find", test.WithPrivilege(func(t *testing.T) {
   405  		_, stderr, exitCode, err := imageExec(t, "exec", opts{overlay: []string{dir}}, imagePath, []string{"test", "-f", "/dir_overlay"})
   406  		if exitCode != 0 {
   407  			t.Log(stderr, err)
   408  			t.Fatalf("unexpected failure running '%v'", strings.Join([]string{"test", "-f", "/dir_overlay"}, " "))
   409  		}
   410  	}))
   411  	// create a file ext3
   412  	t.Run("overlay_ext3_create", test.WithPrivilege(func(t *testing.T) {
   413  		_, stderr, exitCode, err := imageExec(t, "exec", opts{overlay: []string{"ext3_fs.img"}}, imagePath, []string{"touch", "/ext3_overlay"})
   414  		if exitCode != 0 {
   415  			t.Log(stderr, err)
   416  			t.Fatalf("unexpected failure running '%v'", strings.Join([]string{"test", "-f", "/ext3_overlay"}, " "))
   417  		}
   418  	}))
   419  	// look for the file ext3
   420  	t.Run("overlay_ext3_find", test.WithPrivilege(func(t *testing.T) {
   421  		_, stderr, exitCode, err := imageExec(t, "exec", opts{overlay: []string{"ext3_fs.img"}}, imagePath, []string{"test", "-f", "/ext3_overlay"})
   422  		if exitCode != 0 {
   423  			t.Log(stderr, err)
   424  			t.Fatalf("unexpected failure running '%v'", strings.Join([]string{"test", "-f", "/ext3_overlay"}, " "))
   425  		}
   426  	}))
   427  	// look for the file squashFs
   428  	t.Run("overlay_squashFS_find", test.WithPrivilege(func(t *testing.T) {
   429  		_, stderr, exitCode, err := imageExec(t, "exec", opts{overlay: []string{squashfsImage}}, imagePath, []string{"test", "-f", fmt.Sprintf("/%s", tmpfile.Name())})
   430  		if exitCode != 0 {
   431  			t.Log(stderr, err)
   432  			t.Fatalf("unexpected failure running '%v'", strings.Join([]string{"test", "-f", fmt.Sprintf("/%s", tmpfile.Name())}, " "))
   433  		}
   434  	}))
   435  	// create a file multiple overlays
   436  	t.Run("overlay_multiple_create", test.WithPrivilege(func(t *testing.T) {
   437  		_, stderr, exitCode, err := imageExec(t, "exec", opts{overlay: []string{"ext3_fs.img", squashfsImage}}, imagePath, []string{"touch", "/multiple_overlay_fs"})
   438  		if exitCode != 0 {
   439  			t.Log(stderr, err)
   440  			t.Fatalf("unexpected failure running '%v'", strings.Join([]string{"touch", "/multiple_overlay_fs"}, " "))
   441  		}
   442  	}))
   443  	// look for the file with multiple overlays
   444  	t.Run("overlay_multiple_find_ext3", test.WithPrivilege(func(t *testing.T) {
   445  		_, stderr, exitCode, err := imageExec(t, "exec", opts{overlay: []string{"ext3_fs.img", squashfsImage}}, imagePath, []string{"test", "-f", "/multiple_overlay_fs"})
   446  		if exitCode != 0 {
   447  			t.Log(stderr, err)
   448  			t.Fatalf("unexpected failure running '%v'", strings.Join([]string{"test", "-f", "multiple_overlay_fs"}, " "))
   449  		}
   450  	}))
   451  	t.Run("overlay_multiple_find_squashfs", test.WithPrivilege(func(t *testing.T) {
   452  		_, stderr, exitCode, err := imageExec(t, "exec", opts{overlay: []string{"ext3_fs.img", squashfsImage}}, imagePath, []string{"test", "-f", fmt.Sprintf("/%s", tmpfile.Name())})
   453  		if exitCode != 0 {
   454  			t.Log(stderr, err)
   455  			t.Fatalf("unexpected failure running '%v'", strings.Join([]string{"test", "-f", fmt.Sprintf("/%s", tmpfile.Name())}, " "))
   456  		}
   457  	}))
   458  	// look for the file without root privs
   459  	t.Run("overlay_noroot", test.WithoutPrivilege(func(t *testing.T) {
   460  		_, stderr, exitCode, err := imageExec(t, "exec", opts{overlay: []string{dir}}, imagePath, []string{"test", "-f", "/foo_overlay"})
   461  		if exitCode != 1 {
   462  			t.Log(stderr, err)
   463  			t.Fatalf("unexpected success running '%v'", strings.Join([]string{"test", "-f", "/foo_overlay"}, " "))
   464  		}
   465  	}))
   466  	// look for the file without --overlay
   467  	t.Run("overlay_noflag", test.WithPrivilege(func(t *testing.T) {
   468  		_, stderr, exitCode, err := imageExec(t, "exec", opts{}, imagePath, []string{"test", "-f", "/foo_overlay"})
   469  		if exitCode != 1 {
   470  			t.Log(stderr, err)
   471  			t.Fatalf("unexpected success running '%v'", strings.Join([]string{"test", "-f", "/foo_overlay"}, " "))
   472  		}
   473  	}))
   474  }
   475  
   476  func TestSingularityActions(t *testing.T) {
   477  	test.EnsurePrivilege(t)
   478  	opts := buildOpts{
   479  		force:   true,
   480  		sandbox: false,
   481  	}
   482  	if b, err := imageBuild(opts, imagePath, "../../examples/busybox/Singularity"); err != nil {
   483  		t.Log(string(b))
   484  		t.Fatalf("unexpected failure: %v", err)
   485  	}
   486  	defer os.Remove(imagePath)
   487  	if b, err := imageBuild(opts, appsImage, "../../examples/apps/Singularity"); err != nil {
   488  		t.Log(string(b))
   489  		t.Fatalf("unexpected failure: %v", err)
   490  	}
   491  	defer os.Remove(appsImage)
   492  
   493  	// singularity run
   494  	t.Run("run", testSingularityRun)
   495  	// singularity exec
   496  	t.Run("exec", testSingularityExec)
   497  	// stdin pipe
   498  	t.Run("STDIN", testSTDINPipe)
   499  	// action_URI
   500  	t.Run("action_URI", testRunFromURI)
   501  	// Persistent Overlay
   502  	t.Run("Persistent_Overlay", testPersistentOverlay)
   503  }