github.com/tri-adam/singularity@v3.1.1+incompatible/cmd/singularity/build_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  	"encoding/json"
    11  	"fmt"
    12  	"io/ioutil"
    13  	"log"
    14  	"os"
    15  	"os/exec"
    16  	"path"
    17  	"path/filepath"
    18  	"strings"
    19  	"testing"
    20  
    21  	"github.com/sylabs/singularity/internal/pkg/test"
    22  )
    23  
    24  var testFileContent = "Test file content\n"
    25  
    26  func imageVerify(t *testing.T, imagePath string, labels bool) {
    27  	type testSpec struct {
    28  		name          string
    29  		execArgs      []string
    30  		expectSuccess bool
    31  	}
    32  	tests := []testSpec{
    33  		{"False", []string{"false"}, false},
    34  		{"RunScript", []string{"test", "-f", "/.singularity.d/runscript"}, true},
    35  		{"OneBase", []string{"test", "-f", "/.singularity.d/env/01-base.sh"}, true},
    36  		{"ActionsShell", []string{"test", "-f", "/.singularity.d/actions/shell"}, true},
    37  		{"ActionsExec", []string{"test", "-f", "/.singularity.d/actions/exec"}, true},
    38  		{"ActionsRun", []string{"test", "-f", "/.singularity.d/actions/run"}, true},
    39  		{"Environment", []string{"test", "-L", "/environment"}, true},
    40  		{"Singularity", []string{"test", "-L", "/singularity"}, true},
    41  	}
    42  	if labels && *runDisabled { // TODO
    43  		tests = append(tests, testSpec{"Labels", []string{"test", "-f", "/.singularity.d/labels.json"}, true})
    44  	}
    45  
    46  	for _, tt := range tests {
    47  		t.Run(tt.name, test.WithoutPrivilege(func(t *testing.T) {
    48  			_, stderr, exitCode, err := imageExec(t, "exec", opts{}, imagePath, tt.execArgs)
    49  			if tt.expectSuccess && (exitCode != 0) {
    50  				t.Log(stderr)
    51  				t.Fatalf("unexpected failure running '%v': %v", strings.Join(tt.execArgs, " "), err)
    52  			} else if !tt.expectSuccess && (exitCode != 1) {
    53  				t.Log(stderr)
    54  				t.Fatalf("unexpected success running '%v'", strings.Join(tt.execArgs, " "))
    55  			}
    56  		}))
    57  	}
    58  }
    59  
    60  type buildOpts struct {
    61  	force   bool
    62  	sandbox bool
    63  	env     []string
    64  }
    65  
    66  func imageBuild(opts buildOpts, imagePath, buildSpec string) ([]byte, error) {
    67  	var argv []string
    68  	argv = append(argv, "build")
    69  	if opts.force {
    70  		argv = append(argv, "--force")
    71  	}
    72  	if opts.sandbox {
    73  		argv = append(argv, "--sandbox")
    74  	}
    75  	argv = append(argv, imagePath, buildSpec)
    76  
    77  	cmd := exec.Command(cmdPath, argv...)
    78  	cmd.Env = opts.env
    79  
    80  	return cmd.CombinedOutput()
    81  }
    82  
    83  func TestBuild(t *testing.T) {
    84  	tests := []struct {
    85  		name       string
    86  		dependency string
    87  		buildSpec  string
    88  		sandbox    bool
    89  	}{
    90  		{"BusyBox", "", "../../examples/busybox/Singularity", false},
    91  		{"BusyBoxSandbox", "", "../../examples/busybox/Singularity", true},
    92  		{"Debootstrap", "debootstrap", "../../examples/debian/Singularity", true},
    93  		{"DockerURI", "", "docker://busybox", true},
    94  		{"DockerDefFile", "", "../../examples/docker/Singularity", true},
    95  		{"SHubURI", "", "shub://GodloveD/busybox", true},
    96  		{"SHubDefFile", "", "../../examples/shub/Singularity", true},
    97  		{"LibraryDefFile", "", "../../examples/library/Singularity", true},
    98  		{"Yum", "yum", "../../examples/centos/Singularity", true},
    99  		{"Zypper", "zypper", "../../examples/opensuse/Singularity", true},
   100  	}
   101  
   102  	for _, tt := range tests {
   103  		t.Run(tt.name, test.WithPrivilege(func(t *testing.T) {
   104  			if tt.dependency != "" {
   105  				if _, err := exec.LookPath(tt.dependency); err != nil {
   106  					t.Skipf("%v not found in path", tt.dependency)
   107  				}
   108  			}
   109  
   110  			opts := buildOpts{
   111  				sandbox: tt.sandbox,
   112  			}
   113  
   114  			imagePath := path.Join(testDir, "container")
   115  			defer os.RemoveAll(imagePath)
   116  
   117  			if b, err := imageBuild(opts, imagePath, tt.buildSpec); err != nil {
   118  				t.Log(string(b))
   119  				t.Fatalf("unexpected failure: %v", err)
   120  			}
   121  			imageVerify(t, imagePath, false)
   122  		}))
   123  	}
   124  }
   125  
   126  func TestBuildMultiStage(t *testing.T) {
   127  	imagePath1 := path.Join(testDir, "container1")
   128  	imagePath2 := path.Join(testDir, "container2")
   129  	imagePath3 := path.Join(testDir, "container3")
   130  
   131  	liDefFile := prepareDefFile(DefFileDetail{
   132  		Bootstrap: "localimage",
   133  		From:      imagePath1,
   134  	})
   135  	defer os.Remove(liDefFile)
   136  
   137  	labels := make(map[string]string)
   138  	labels["FOO"] = "bar"
   139  	liLabelDefFile := prepareDefFile(DefFileDetail{
   140  		Bootstrap: "localimage",
   141  		From:      imagePath2,
   142  		Labels:    labels,
   143  	})
   144  	defer os.Remove(liLabelDefFile)
   145  
   146  	type testSpec struct {
   147  		name      string
   148  		imagePath string
   149  		buildSpec string
   150  		force     bool
   151  		sandbox   bool
   152  		labels    bool
   153  	}
   154  
   155  	tests := []struct {
   156  		name  string
   157  		steps []testSpec
   158  	}{
   159  		{"SIFToSIF", []testSpec{
   160  			{"BusyBox", imagePath1, "../../examples/busybox/Singularity", false, false, false},
   161  			{"SIF", imagePath2, imagePath1, false, false, false},
   162  		}},
   163  		{"SandboxToSIF", []testSpec{
   164  			{"BusyBoxSandbox", imagePath1, "../../examples/busybox/Singularity", false, true, false},
   165  			{"SIF", imagePath2, imagePath1, false, false, false},
   166  		}},
   167  		{"LocalImage", []testSpec{
   168  			{"BusyBox", imagePath1, "../../examples/busybox/Singularity", false, false, false},
   169  			{"LocalImage", imagePath2, liDefFile, false, false, false},
   170  			{"LocalImageLabel", imagePath3, liLabelDefFile, false, false, true},
   171  		}},
   172  		{"LocalImageSandbox", []testSpec{
   173  			{"BusyBoxSandbox", imagePath2, "../../examples/busybox/Singularity", true, true, false},
   174  			{"LocalImageLabel", imagePath3, liLabelDefFile, false, false, true},
   175  		}},
   176  	}
   177  
   178  	for _, tt := range tests {
   179  		t.Run(tt.name, test.WithPrivilege(func(t *testing.T) {
   180  			for _, ts := range tt.steps {
   181  				defer os.RemoveAll(ts.imagePath)
   182  			}
   183  
   184  			for _, ts := range tt.steps {
   185  				t.Run(ts.name, test.WithPrivilege(func(t *testing.T) {
   186  					opts := buildOpts{
   187  						force:   ts.force,
   188  						sandbox: ts.sandbox,
   189  					}
   190  
   191  					if b, err := imageBuild(opts, ts.imagePath, ts.buildSpec); err != nil {
   192  						t.Log(string(b))
   193  						t.Fatalf("unexpected failure: %v", err)
   194  					}
   195  					imageVerify(t, ts.imagePath, ts.labels)
   196  				}))
   197  			}
   198  		}))
   199  	}
   200  }
   201  
   202  func TestBadPath(t *testing.T) {
   203  	test.EnsurePrivilege(t)
   204  
   205  	imagePath := path.Join(testDir, "container")
   206  	defer os.RemoveAll(imagePath)
   207  
   208  	if b, err := imageBuild(buildOpts{}, imagePath, "/some/dumb/path"); err == nil {
   209  		t.Log(string(b))
   210  		t.Fatal("unexpected success")
   211  	}
   212  }
   213  
   214  func TestBuildDefinition(t *testing.T) {
   215  
   216  	tmpfile, err := ioutil.TempFile(testDir, "testFile-")
   217  	if err != nil {
   218  		log.Fatal(err)
   219  	}
   220  
   221  	defer os.Remove(tmpfile.Name()) // clean up
   222  
   223  	if _, err := tmpfile.Write([]byte(testFileContent)); err != nil {
   224  		log.Fatal(err)
   225  	}
   226  	if err := tmpfile.Close(); err != nil {
   227  		log.Fatal(err)
   228  	}
   229  
   230  	tests := []struct {
   231  		name    string
   232  		force   bool
   233  		sandbox bool
   234  		dfd     DefFileDetail
   235  	}{
   236  		{"Empty", false, true, DefFileDetail{
   237  			Bootstrap: "docker",
   238  			From:      "alpine:latest",
   239  		}},
   240  		{"Help", false, true, DefFileDetail{
   241  			Bootstrap: "docker",
   242  			From:      "alpine:latest",
   243  			Help: []string{
   244  				"help info line 1",
   245  				"help info line 2",
   246  				"help info line 3",
   247  			},
   248  		}},
   249  		{"Files", false, true, DefFileDetail{
   250  			Bootstrap: "docker",
   251  			From:      "alpine:latest",
   252  			Files: []FilePair{
   253  				FilePair{
   254  					Src: tmpfile.Name(),
   255  					Dst: "NewName2.txt",
   256  				},
   257  				FilePair{
   258  					Src: tmpfile.Name(),
   259  					Dst: "NewName.txt",
   260  				},
   261  			},
   262  		}},
   263  		{"Test", false, true, DefFileDetail{
   264  			Bootstrap: "docker",
   265  			From:      "alpine:latest",
   266  			Test: []string{
   267  				"echo testscript line 1",
   268  				"echo testscript line 2",
   269  				"echo testscript line 3",
   270  			},
   271  		}},
   272  		{"Startscript", false, true, DefFileDetail{
   273  			Bootstrap: "docker",
   274  			From:      "alpine:latest",
   275  			StartScript: []string{
   276  				"echo startscript line 1",
   277  				"echo startscript line 2",
   278  				"echo startscript line 3",
   279  			},
   280  		}},
   281  		{"Runscript", false, true, DefFileDetail{
   282  			Bootstrap: "docker",
   283  			From:      "alpine:latest",
   284  			RunScript: []string{
   285  				"echo runscript line 1",
   286  				"echo runscript line 2",
   287  				"echo runscript line 3",
   288  			},
   289  		}},
   290  		{"Env", false, true, DefFileDetail{
   291  			Bootstrap: "docker",
   292  			From:      "alpine:latest",
   293  			Env: []string{
   294  				"testvar1=one",
   295  				"testvar2=two",
   296  				"testvar3=three",
   297  			},
   298  		}},
   299  		{"Labels", false, true, DefFileDetail{
   300  			Bootstrap: "docker",
   301  			From:      "alpine:latest",
   302  			Labels: map[string]string{
   303  				"customLabel1": "one",
   304  				"customLabel2": "two",
   305  				"customLabel3": "three",
   306  			},
   307  		}},
   308  		{"Pre", false, true, DefFileDetail{
   309  			Bootstrap: "docker",
   310  			From:      "alpine:latest",
   311  			Pre: []string{
   312  				filepath.Join(testDir, "PreFile1"),
   313  			},
   314  		}},
   315  		{"Setup", false, true, DefFileDetail{
   316  			Bootstrap: "docker",
   317  			From:      "alpine:latest",
   318  			Setup: []string{
   319  				filepath.Join(testDir, "SetupFile1"),
   320  			},
   321  		}},
   322  		{"Post", false, true, DefFileDetail{
   323  			Bootstrap: "docker",
   324  			From:      "alpine:latest",
   325  			Post: []string{
   326  				"PostFile1",
   327  			},
   328  		}},
   329  		{"AppHelp", false, true, DefFileDetail{
   330  			Bootstrap: "docker",
   331  			From:      "alpine:latest",
   332  			Apps: []AppDetail{
   333  				AppDetail{
   334  					Name: "foo",
   335  					Help: []string{
   336  						"foo help info line 1",
   337  						"foo help info line 2",
   338  						"foo help info line 3",
   339  					},
   340  				},
   341  				AppDetail{
   342  					Name: "bar",
   343  					Help: []string{
   344  						"bar help info line 1",
   345  						"bar help info line 2",
   346  						"bar help info line 3",
   347  					},
   348  				},
   349  			},
   350  		}},
   351  		{"AppEnv", false, true, DefFileDetail{
   352  			Bootstrap: "docker",
   353  			From:      "alpine:latest",
   354  			Apps: []AppDetail{
   355  				AppDetail{
   356  					Name: "foo",
   357  					Env: []string{
   358  						"testvar1=fooOne",
   359  						"testvar2=fooTwo",
   360  						"testvar3=fooThree",
   361  					},
   362  				},
   363  				AppDetail{
   364  					Name: "bar",
   365  					Env: []string{
   366  						"testvar1=barOne",
   367  						"testvar2=barTwo",
   368  						"testvar3=barThree",
   369  					},
   370  				},
   371  			},
   372  		}},
   373  		{"AppLabels", false, true, DefFileDetail{
   374  			Bootstrap: "docker",
   375  			From:      "alpine:latest",
   376  			Apps: []AppDetail{
   377  				AppDetail{
   378  					Name: "foo",
   379  					Labels: map[string]string{
   380  						"customLabel1": "fooOne",
   381  						"customLabel2": "fooTwo",
   382  						"customLabel3": "fooThree",
   383  					},
   384  				},
   385  				AppDetail{
   386  					Name: "bar",
   387  					Labels: map[string]string{
   388  						"customLabel1": "barOne",
   389  						"customLabel2": "barTwo",
   390  						"customLabel3": "barThree",
   391  					},
   392  				},
   393  			},
   394  		}},
   395  		{"AppFiles", false, true, DefFileDetail{
   396  			Bootstrap: "docker",
   397  			From:      "alpine:latest",
   398  			Apps: []AppDetail{
   399  				AppDetail{
   400  					Name: "foo",
   401  					Files: []FilePair{
   402  						FilePair{
   403  							Src: tmpfile.Name(),
   404  							Dst: "FooFile2.txt",
   405  						},
   406  						FilePair{
   407  							Src: tmpfile.Name(),
   408  							Dst: "FooFile.txt",
   409  						},
   410  					},
   411  				},
   412  				AppDetail{
   413  					Name: "bar",
   414  					Files: []FilePair{
   415  						FilePair{
   416  							Src: tmpfile.Name(),
   417  							Dst: "BarFile2.txt",
   418  						},
   419  						FilePair{
   420  							Src: tmpfile.Name(),
   421  							Dst: "BarFile.txt",
   422  						},
   423  					},
   424  				},
   425  			},
   426  		}},
   427  		{"AppInstall", false, true, DefFileDetail{
   428  			Bootstrap: "docker",
   429  			From:      "alpine:latest",
   430  			Apps: []AppDetail{
   431  				AppDetail{
   432  					Name: "foo",
   433  					Install: []string{
   434  						"FooInstallFile1",
   435  					},
   436  				},
   437  				AppDetail{
   438  					Name: "bar",
   439  					Install: []string{
   440  						"BarInstallFile1",
   441  					},
   442  				},
   443  			},
   444  		}},
   445  		{"AppRun", false, true, DefFileDetail{
   446  			Bootstrap: "docker",
   447  			From:      "alpine:latest",
   448  			Apps: []AppDetail{
   449  				AppDetail{
   450  					Name: "foo",
   451  					Run: []string{
   452  						"echo foo runscript line 1",
   453  						"echo foo runscript line 2",
   454  						"echo foo runscript line 3",
   455  					},
   456  				},
   457  				AppDetail{
   458  					Name: "bar",
   459  					Run: []string{
   460  						"echo bar runscript line 1",
   461  						"echo bar runscript line 2",
   462  						"echo bar runscript line 3",
   463  					},
   464  				},
   465  			},
   466  		}},
   467  		{"AppTest", false, true, DefFileDetail{
   468  			Bootstrap: "docker",
   469  			From:      "alpine:latest",
   470  			Apps: []AppDetail{
   471  				AppDetail{
   472  					Name: "foo",
   473  					Test: []string{
   474  						"echo foo testscript line 1",
   475  						"echo foo testscript line 2",
   476  						"echo foo testscript line 3",
   477  					},
   478  				},
   479  				AppDetail{
   480  					Name: "bar",
   481  					Test: []string{
   482  						"echo bar testscript line 1",
   483  						"echo bar testscript line 2",
   484  						"echo bar testscript line 3",
   485  					},
   486  				},
   487  			},
   488  		}},
   489  	}
   490  
   491  	for _, tt := range tests {
   492  		t.Run(tt.name, test.WithPrivilege(func(t *testing.T) {
   493  
   494  			defFile := prepareDefFile(tt.dfd)
   495  			defer os.Remove(defFile)
   496  
   497  			opts := buildOpts{
   498  				sandbox: tt.sandbox,
   499  			}
   500  
   501  			imagePath := path.Join(testDir, "container")
   502  			defer os.RemoveAll(imagePath)
   503  
   504  			if b, err := imageBuild(opts, imagePath, defFile); err != nil {
   505  				t.Log(string(b))
   506  				t.Fatalf("unexpected failure: %v", err)
   507  			}
   508  			definitionImageVerify(t, imagePath, tt.dfd)
   509  		}))
   510  	}
   511  }
   512  
   513  func definitionImageVerify(t *testing.T, imagePath string, dfd DefFileDetail) {
   514  	if dfd.Help != nil {
   515  		helpPath := filepath.Join(imagePath, `/.singularity.d/runscript.help`)
   516  		if !fileExists(t, helpPath) {
   517  			t.Fatalf("unexpected failure: Script %v does not exist in container", helpPath)
   518  		}
   519  
   520  		if err := verifyHelp(t, helpPath, dfd.Help); err != nil {
   521  			t.Fatalf("unexpected failure: help message: %v", err)
   522  		}
   523  	}
   524  
   525  	if dfd.Env != nil {
   526  		if err := verifyEnv(t, imagePath, dfd.Env, nil); err != nil {
   527  			t.Fatalf("unexpected failure: Env in container is incorrect: %v", err)
   528  		}
   529  	}
   530  
   531  	// always run this since we should at least have default build labels
   532  	if err := verifyLabels(t, imagePath, dfd.Labels); err != nil {
   533  		t.Fatalf("unexpected failure: Labels in the container are incorrect: %v", err)
   534  	}
   535  
   536  	// verify %files section works correctly
   537  	for _, p := range dfd.Files {
   538  		var file string
   539  		if p.Src == "" {
   540  			file = p.Src
   541  		} else {
   542  			file = p.Dst
   543  		}
   544  
   545  		if !fileExists(t, filepath.Join(imagePath, file)) {
   546  			t.Fatalf("unexpected failure: File %v does not exist in container", file)
   547  		}
   548  
   549  		if err := verifyFile(t, p.Src, filepath.Join(imagePath, file)); err != nil {
   550  			t.Fatalf("unexpected failure: File %v: %v", file, err)
   551  		}
   552  	}
   553  
   554  	if dfd.RunScript != nil {
   555  		scriptPath := filepath.Join(imagePath, `/.singularity.d/runscript`)
   556  		if !fileExists(t, scriptPath) {
   557  			t.Fatalf("unexpected failure: Script %v does not exist in container", scriptPath)
   558  		}
   559  
   560  		if err := verifyScript(t, scriptPath, dfd.RunScript); err != nil {
   561  			t.Fatalf("unexpected failure: runscript: %v", err)
   562  		}
   563  	}
   564  
   565  	if dfd.StartScript != nil {
   566  		scriptPath := filepath.Join(imagePath, `/.singularity.d/startscript`)
   567  		if !fileExists(t, scriptPath) {
   568  			t.Fatalf("unexpected failure: Script %v does not exist in container", scriptPath)
   569  		}
   570  
   571  		if err := verifyScript(t, scriptPath, dfd.StartScript); err != nil {
   572  			t.Fatalf("unexpected failure: startscript: %v", err)
   573  		}
   574  	}
   575  
   576  	if dfd.Test != nil {
   577  		scriptPath := filepath.Join(imagePath, `/.singularity.d/test`)
   578  		if !fileExists(t, scriptPath) {
   579  			t.Fatalf("unexpected failure: Script %v does not exist in container", scriptPath)
   580  		}
   581  
   582  		if err := verifyScript(t, scriptPath, dfd.Test); err != nil {
   583  			t.Fatalf("unexpected failure: test script: %v", err)
   584  		}
   585  	}
   586  
   587  	for _, file := range dfd.Pre {
   588  		if !fileExists(t, file) {
   589  			t.Fatalf("unexpected failure: %%Pre generated file %v does not exist on host", file)
   590  		}
   591  	}
   592  
   593  	for _, file := range dfd.Setup {
   594  		if !fileExists(t, file) {
   595  			t.Fatalf("unexpected failure: %%Setup generated file %v does not exist on host", file)
   596  		}
   597  	}
   598  
   599  	for _, file := range dfd.Post {
   600  		if !fileExists(t, filepath.Join(imagePath, file)) {
   601  			t.Fatalf("unexpected failure: %%Post generated file %v does not exist in container", file)
   602  		}
   603  	}
   604  
   605  	// Verify any apps
   606  	for _, app := range dfd.Apps {
   607  		// %apphelp
   608  		if app.Help != nil {
   609  			helpPath := filepath.Join(imagePath, `/scif/apps/`, app.Name, `/scif/runscript.help`)
   610  			if !fileExists(t, helpPath) {
   611  				t.Fatalf("unexpected failure in app %v: Script %v does not exist in app", app.Name, helpPath)
   612  			}
   613  
   614  			if err := verifyHelp(t, helpPath, app.Help); err != nil {
   615  				t.Fatalf("unexpected failure in app %v: app help message: %v", app.Name, err)
   616  			}
   617  		}
   618  
   619  		// %appenv
   620  		if app.Env != nil {
   621  			if err := verifyEnv(t, imagePath, app.Env, []string{"--app", app.Name}); err != nil {
   622  				t.Fatalf("unexpected failure in app %v: Env in app is incorrect: %v", app.Name, err)
   623  			}
   624  		}
   625  
   626  		// %applabels
   627  		if app.Labels != nil {
   628  			if err := verifyAppLabels(t, imagePath, app.Name, app.Labels); err != nil {
   629  				t.Fatalf("unexpected failure in app %v: Labels in app are incorrect: %v", app.Name, err)
   630  			}
   631  		}
   632  
   633  		// %appfiles
   634  		for _, p := range app.Files {
   635  			var file string
   636  			if p.Src == "" {
   637  				file = p.Src
   638  			} else {
   639  				file = p.Dst
   640  			}
   641  
   642  			if !fileExists(t, filepath.Join(imagePath, "/scif/apps/", app.Name, file)) {
   643  				t.Fatalf("unexpected failure in app %v: File %v does not exist in app", app.Name, file)
   644  			}
   645  
   646  			if err := verifyFile(t, p.Src, filepath.Join(imagePath, "/scif/apps/", app.Name, file)); err != nil {
   647  				t.Fatalf("unexpected failure in app %v: File %v: %v", app.Name, file, err)
   648  			}
   649  		}
   650  
   651  		// %appInstall
   652  		for _, file := range app.Install {
   653  			if !fileExists(t, filepath.Join(imagePath, "/scif/apps/", app.Name, file)) {
   654  				t.Fatalf("unexpected failure in app %v: %%Install generated file %v does not exist in container", app.Name, file)
   655  			}
   656  		}
   657  
   658  		// %appRun
   659  		if app.Run != nil {
   660  			scriptPath := filepath.Join(imagePath, "/scif/apps/", app.Name, "scif/runscript")
   661  			if !fileExists(t, scriptPath) {
   662  				t.Fatalf("unexpected failure in app %v: Script %v does not exist in app", app.Name, scriptPath)
   663  			}
   664  
   665  			if err := verifyScript(t, scriptPath, app.Run); err != nil {
   666  				t.Fatalf("unexpected failure in app %v: runscript: %v", app.Name, err)
   667  			}
   668  		}
   669  
   670  		// %appTest
   671  		if app.Test != nil {
   672  			scriptPath := filepath.Join(imagePath, "/scif/apps/", app.Name, "scif/test")
   673  			if !fileExists(t, scriptPath) {
   674  				t.Fatalf("unexpected failure in app %v: Script %v does not exist in app", app.Name, scriptPath)
   675  			}
   676  
   677  			if err := verifyScript(t, scriptPath, app.Test); err != nil {
   678  				t.Fatalf("unexpected failure in app %v: test script: %v", app.Name, err)
   679  			}
   680  		}
   681  	}
   682  
   683  }
   684  
   685  func fileExists(t *testing.T, path string) bool {
   686  	if _, err := os.Stat(path); os.IsNotExist(err) {
   687  		return false
   688  	} else if err != nil {
   689  		t.Fatalf("While stating file: %v", err)
   690  	}
   691  
   692  	return true
   693  }
   694  
   695  func verifyFile(t *testing.T, original, copy string) error {
   696  	ofi, err := os.Stat(original)
   697  	if err != nil {
   698  		t.Fatalf("While getting file info: %v", err)
   699  	}
   700  
   701  	cfi, err := os.Stat(copy)
   702  	if err != nil {
   703  		t.Fatalf("While getting file info: %v", err)
   704  	}
   705  
   706  	if ofi.Size() != cfi.Size() {
   707  		return fmt.Errorf("Incorrect file sizes. Original: %v, Copy: %v", ofi.Size(), cfi.Size())
   708  	}
   709  
   710  	if ofi.Mode() != cfi.Mode() {
   711  		return fmt.Errorf("Incorrect file modes. Original: %v, Copy: %v", ofi.Mode(), cfi.Mode())
   712  	}
   713  
   714  	o, err := ioutil.ReadFile(original)
   715  	if err != nil {
   716  		t.Fatalf("While reading file: %v", err)
   717  	}
   718  
   719  	c, err := ioutil.ReadFile(copy)
   720  	if err != nil {
   721  		t.Fatalf("While reading file: %v", err)
   722  	}
   723  
   724  	if bytes.Compare(o, c) != 0 {
   725  		return fmt.Errorf("Incorrect file content")
   726  	}
   727  
   728  	return nil
   729  }
   730  
   731  func verifyHelp(t *testing.T, fileName string, contents []string) error {
   732  	fi, err := os.Stat(fileName)
   733  	if err != nil {
   734  		t.Fatalf("While getting file info: %v", err)
   735  	}
   736  
   737  	// do perm check
   738  	if fi.Mode().Perm() != 0644 {
   739  		return fmt.Errorf("Incorrect help script perms: %v", fi.Mode().Perm())
   740  	}
   741  
   742  	s, err := ioutil.ReadFile(fileName)
   743  	if err != nil {
   744  		t.Fatalf("While reading file: %v", err)
   745  	}
   746  
   747  	helpScript := string(s)
   748  	for _, c := range contents {
   749  		if !strings.Contains(helpScript, c) {
   750  			return fmt.Errorf("Missing help script content")
   751  		}
   752  	}
   753  
   754  	return nil
   755  }
   756  
   757  func verifyScript(t *testing.T, fileName string, contents []string) error {
   758  	fi, err := os.Stat(fileName)
   759  	if err != nil {
   760  		t.Fatalf("While getting file info: %v", err)
   761  	}
   762  
   763  	// do perm check
   764  	if fi.Mode().Perm() != 0755 {
   765  		return fmt.Errorf("Incorrect script perms: %v", fi.Mode().Perm())
   766  	}
   767  
   768  	s, err := ioutil.ReadFile(fileName)
   769  	if err != nil {
   770  		t.Fatalf("While reading file: %v", err)
   771  	}
   772  
   773  	script := string(s)
   774  	for _, c := range contents {
   775  		if !strings.Contains(script, c) {
   776  			return fmt.Errorf("Missing script content")
   777  		}
   778  	}
   779  
   780  	return nil
   781  }
   782  
   783  func verifyEnv(t *testing.T, imagePath string, env []string, flags []string) error {
   784  	args := []string{"exec"}
   785  	if flags != nil {
   786  		args = append(args, flags...)
   787  	}
   788  	args = append(args, imagePath, "env")
   789  
   790  	cmd := exec.Command(cmdPath, args...)
   791  	b, err := cmd.CombinedOutput()
   792  
   793  	out := string(b)
   794  
   795  	if err != nil {
   796  		t.Fatalf("Error running command: %v", err)
   797  	}
   798  
   799  	for _, e := range env {
   800  		if !strings.Contains(out, e) {
   801  			return fmt.Errorf("Environment is missing: %v", e)
   802  		}
   803  	}
   804  
   805  	return nil
   806  }
   807  
   808  func verifyLabels(t *testing.T, imagePath string, labels map[string]string) error {
   809  	var fileLabels map[string]string
   810  
   811  	b, err := ioutil.ReadFile(filepath.Join(imagePath, "/.singularity.d/labels.json"))
   812  	if err != nil {
   813  		t.Fatalf("While reading file: %v", err)
   814  	}
   815  
   816  	if err := json.Unmarshal(b, &fileLabels); err != nil {
   817  		t.Fatalf("While unmarshaling labels.json into map: %v", err)
   818  	}
   819  
   820  	for k, v := range labels {
   821  		if l, ok := fileLabels[k]; !ok || v != l {
   822  			return fmt.Errorf("Missing label: %v:%v", k, v)
   823  		}
   824  	}
   825  
   826  	//check default labels that are always generated
   827  	defaultLabels := []string{
   828  		"org.label-schema.schema-version",
   829  		"org.label-schema.build-date",
   830  		"org.label-schema.usage.singularity.version",
   831  	}
   832  
   833  	for _, l := range defaultLabels {
   834  		if _, ok := fileLabels[l]; !ok {
   835  			return fmt.Errorf("Missing label: %v", l)
   836  		}
   837  	}
   838  
   839  	return nil
   840  }
   841  
   842  func verifyAppLabels(t *testing.T, imagePath, appName string, labels map[string]string) error {
   843  	var fileLabels map[string]string
   844  
   845  	b, err := ioutil.ReadFile(filepath.Join(imagePath, "/scif/apps/", appName, "/scif/labels.json"))
   846  	if err != nil {
   847  		t.Fatalf("While reading file: %v", err)
   848  	}
   849  
   850  	if err := json.Unmarshal(b, &fileLabels); err != nil {
   851  		t.Fatalf("While unmarshaling labels.json into map: %v", err)
   852  	}
   853  
   854  	for k, v := range labels {
   855  		if l, ok := fileLabels[k]; !ok || v != l {
   856  			return fmt.Errorf("Missing label: %v:%v", k, v)
   857  		}
   858  	}
   859  
   860  	return nil
   861  }