github.com/kubiko/snapd@v0.0.0-20201013125620-d4f3094d9ddf/cmd/snap-exec/main_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2016 Canonical Ltd
     5   *
     6   * This program is free software: you can redistribute it and/or modify
     7   * it under the terms of the GNU General Public License version 3 as
     8   * published by the Free Software Foundation.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18   */
    19  
    20  package main_test
    21  
    22  import (
    23  	"fmt"
    24  	"io/ioutil"
    25  	"os"
    26  	"os/exec"
    27  	"path/filepath"
    28  	"testing"
    29  
    30  	. "gopkg.in/check.v1"
    31  
    32  	"github.com/snapcore/snapd/dirs"
    33  	"github.com/snapcore/snapd/osutil"
    34  	"github.com/snapcore/snapd/release"
    35  	"github.com/snapcore/snapd/snap"
    36  	"github.com/snapcore/snapd/snap/snaptest"
    37  	"github.com/snapcore/snapd/testutil"
    38  
    39  	snapExec "github.com/snapcore/snapd/cmd/snap-exec"
    40  )
    41  
    42  // Hook up check.v1 into the "go test" runner
    43  func Test(t *testing.T) { TestingT(t) }
    44  
    45  type snapExecSuite struct{}
    46  
    47  var _ = Suite(&snapExecSuite{})
    48  
    49  func (s *snapExecSuite) SetUpTest(c *C) {
    50  	// clean previous parse runs
    51  	snapExec.SetOptsCommand("")
    52  	snapExec.SetOptsHook("")
    53  }
    54  
    55  func (s *snapExecSuite) TearDown(c *C) {
    56  	dirs.SetRootDir("/")
    57  }
    58  
    59  var mockYaml = []byte(`name: snapname
    60  version: 1.0
    61  apps:
    62   app:
    63    command: run-app cmd-arg1 $SNAP_DATA
    64    stop-command: stop-app
    65    post-stop-command: post-stop-app
    66    completer: you/complete/me
    67    environment:
    68     BASE_PATH: /some/path
    69     LD_LIBRARY_PATH: ${BASE_PATH}/lib
    70     MY_PATH: $PATH
    71     TEST_PATH: /custom
    72   app2:
    73    command: run-app2
    74    stop-command: stop-app2
    75    post-stop-command: post-stop-app2
    76    command-chain: [chain1, chain2]
    77   nostop:
    78    command: nostop
    79  `)
    80  
    81  var mockClassicYaml = append([]byte("confinement: classic\n"), mockYaml...)
    82  
    83  var mockHookYaml = []byte(`name: snapname
    84  version: 1.0
    85  hooks:
    86   configure:
    87  `)
    88  
    89  var mockHookCommandChainYaml = []byte(`name: snapname
    90  version: 1.0
    91  hooks:
    92   configure:
    93    command-chain: [chain1, chain2]
    94  `)
    95  
    96  var binaryTemplate = `#!/bin/sh
    97  echo "$(basename $0)" >> %[1]q
    98  for arg in "$@"; do
    99  echo "$arg" >> %[1]q
   100  done
   101  printf "\n" >> %[1]q`
   102  
   103  func (s *snapExecSuite) TestInvalidCombinedParameters(c *C) {
   104  	invalidParameters := []string{"--hook=hook-name", "--command=command-name", "snap-name"}
   105  	_, _, err := snapExec.ParseArgs(invalidParameters)
   106  	c.Check(err, ErrorMatches, ".*cannot use --hook and --command together.*")
   107  }
   108  
   109  func (s *snapExecSuite) TestInvalidExtraParameters(c *C) {
   110  	invalidParameters := []string{"--hook=hook-name", "snap-name", "foo", "bar"}
   111  	_, _, err := snapExec.ParseArgs(invalidParameters)
   112  	c.Check(err, ErrorMatches, ".*too many arguments for hook \"hook-name\": snap-name foo bar.*")
   113  }
   114  
   115  func (s *snapExecSuite) TestFindCommand(c *C) {
   116  	info, err := snap.InfoFromSnapYaml(mockYaml)
   117  	c.Assert(err, IsNil)
   118  
   119  	for _, t := range []struct {
   120  		cmd      string
   121  		expected string
   122  	}{
   123  		{cmd: "", expected: `run-app cmd-arg1 $SNAP_DATA`},
   124  		{cmd: "stop", expected: "stop-app"},
   125  		{cmd: "post-stop", expected: "post-stop-app"},
   126  	} {
   127  		cmd, err := snapExec.FindCommand(info.Apps["app"], t.cmd)
   128  		c.Check(err, IsNil)
   129  		c.Check(cmd, Equals, t.expected)
   130  	}
   131  }
   132  
   133  func (s *snapExecSuite) TestFindCommandInvalidCommand(c *C) {
   134  	info, err := snap.InfoFromSnapYaml(mockYaml)
   135  	c.Assert(err, IsNil)
   136  
   137  	_, err = snapExec.FindCommand(info.Apps["app"], "xxx")
   138  	c.Check(err, ErrorMatches, `cannot use "xxx" command`)
   139  }
   140  
   141  func (s *snapExecSuite) TestFindCommandNoCommand(c *C) {
   142  	info, err := snap.InfoFromSnapYaml(mockYaml)
   143  	c.Assert(err, IsNil)
   144  
   145  	_, err = snapExec.FindCommand(info.Apps["nostop"], "stop")
   146  	c.Check(err, ErrorMatches, `no "stop" command found for "nostop"`)
   147  }
   148  
   149  func (s *snapExecSuite) TestSnapExecAppIntegration(c *C) {
   150  	dirs.SetRootDir(c.MkDir())
   151  	snaptest.MockSnap(c, string(mockYaml), &snap.SideInfo{
   152  		Revision: snap.R("42"),
   153  	})
   154  
   155  	execArgv0 := ""
   156  	execArgs := []string{}
   157  	execEnv := []string{}
   158  	restore := snapExec.MockSyscallExec(func(argv0 string, argv []string, env []string) error {
   159  		execArgv0 = argv0
   160  		execArgs = argv
   161  		execEnv = env
   162  		return nil
   163  	})
   164  	defer restore()
   165  
   166  	// FIXME: TEST_PATH was meant to be just PATH but this uncovers another
   167  	// bug in the test suite where mocking binaries misbehaves.
   168  	oldPath := os.Getenv("TEST_PATH")
   169  	os.Setenv("TEST_PATH", "/vanilla")
   170  	defer os.Setenv("TEST_PATH", oldPath)
   171  
   172  	// launch and verify its run the right way
   173  	err := snapExec.ExecApp("snapname.app", "42", "stop", []string{"arg1", "arg2"})
   174  	c.Assert(err, IsNil)
   175  	c.Check(execArgv0, Equals, fmt.Sprintf("%s/snapname/42/stop-app", dirs.SnapMountDir))
   176  	c.Check(execArgs, DeepEquals, []string{execArgv0, "arg1", "arg2"})
   177  	c.Check(execEnv, testutil.Contains, "BASE_PATH=/some/path")
   178  	c.Check(execEnv, testutil.Contains, "LD_LIBRARY_PATH=/some/path/lib")
   179  	c.Check(execEnv, testutil.Contains, fmt.Sprintf("MY_PATH=%s", os.Getenv("PATH")))
   180  	// TEST_PATH is properly handled and we only see one value, /custom, defined
   181  	// as an app-specific override.
   182  	// See also https://bugs.launchpad.net/snapd/+bug/1860369
   183  	c.Check(execEnv, Not(testutil.Contains), "TEST_PATH=/vanilla")
   184  	c.Check(execEnv, testutil.Contains, "TEST_PATH=/custom")
   185  }
   186  
   187  func (s *snapExecSuite) TestSnapExecAppCommandChainIntegration(c *C) {
   188  	dirs.SetRootDir(c.MkDir())
   189  	snaptest.MockSnap(c, string(mockYaml), &snap.SideInfo{
   190  		Revision: snap.R("42"),
   191  	})
   192  
   193  	execArgv0 := ""
   194  	execArgs := []string{}
   195  	restore := snapExec.MockSyscallExec(func(argv0 string, argv []string, env []string) error {
   196  		execArgv0 = argv0
   197  		execArgs = argv
   198  		return nil
   199  	})
   200  	defer restore()
   201  
   202  	chain1_path := fmt.Sprintf("%s/snapname/42/chain1", dirs.SnapMountDir)
   203  	chain2_path := fmt.Sprintf("%s/snapname/42/chain2", dirs.SnapMountDir)
   204  	app_path := fmt.Sprintf("%s/snapname/42/run-app2", dirs.SnapMountDir)
   205  	stop_path := fmt.Sprintf("%s/snapname/42/stop-app2", dirs.SnapMountDir)
   206  	post_stop_path := fmt.Sprintf("%s/snapname/42/post-stop-app2", dirs.SnapMountDir)
   207  
   208  	for _, t := range []struct {
   209  		cmd      string
   210  		args     []string
   211  		expected []string
   212  	}{
   213  		// Normal command
   214  		{expected: []string{chain1_path, chain2_path, app_path}},
   215  		{args: []string{"arg1", "arg2"}, expected: []string{chain1_path, chain2_path, app_path, "arg1", "arg2"}},
   216  
   217  		// Stop command
   218  		{cmd: "stop", expected: []string{chain1_path, chain2_path, stop_path}},
   219  		{cmd: "stop", args: []string{"arg1", "arg2"}, expected: []string{chain1_path, chain2_path, stop_path, "arg1", "arg2"}},
   220  
   221  		// Post-stop command
   222  		{cmd: "post-stop", expected: []string{chain1_path, chain2_path, post_stop_path}},
   223  		{cmd: "post-stop", args: []string{"arg1", "arg2"}, expected: []string{chain1_path, chain2_path, post_stop_path, "arg1", "arg2"}},
   224  	} {
   225  		err := snapExec.ExecApp("snapname.app2", "42", t.cmd, t.args)
   226  		c.Assert(err, IsNil)
   227  		c.Check(execArgv0, Equals, t.expected[0])
   228  		c.Check(execArgs, DeepEquals, t.expected)
   229  	}
   230  }
   231  
   232  func (s *snapExecSuite) TestSnapExecHookIntegration(c *C) {
   233  	dirs.SetRootDir(c.MkDir())
   234  	snaptest.MockSnap(c, string(mockHookYaml), &snap.SideInfo{
   235  		Revision: snap.R("42"),
   236  	})
   237  
   238  	execArgv0 := ""
   239  	execArgs := []string{}
   240  	restore := snapExec.MockSyscallExec(func(argv0 string, argv []string, env []string) error {
   241  		execArgv0 = argv0
   242  		execArgs = argv
   243  		return nil
   244  	})
   245  	defer restore()
   246  
   247  	// launch and verify it ran correctly
   248  	err := snapExec.ExecHook("snapname", "42", "configure")
   249  	c.Assert(err, IsNil)
   250  	c.Check(execArgv0, Equals, fmt.Sprintf("%s/snapname/42/meta/hooks/configure", dirs.SnapMountDir))
   251  	c.Check(execArgs, DeepEquals, []string{execArgv0})
   252  }
   253  
   254  func (s *snapExecSuite) TestSnapExecHookCommandChainIntegration(c *C) {
   255  	dirs.SetRootDir(c.MkDir())
   256  	snaptest.MockSnap(c, string(mockHookCommandChainYaml), &snap.SideInfo{
   257  		Revision: snap.R("42"),
   258  	})
   259  
   260  	execArgv0 := ""
   261  	execArgs := []string{}
   262  	restore := snapExec.MockSyscallExec(func(argv0 string, argv []string, env []string) error {
   263  		execArgv0 = argv0
   264  		execArgs = argv
   265  		return nil
   266  	})
   267  	defer restore()
   268  
   269  	chain1_path := fmt.Sprintf("%s/snapname/42/chain1", dirs.SnapMountDir)
   270  	chain2_path := fmt.Sprintf("%s/snapname/42/chain2", dirs.SnapMountDir)
   271  	hook_path := fmt.Sprintf("%s/snapname/42/meta/hooks/configure", dirs.SnapMountDir)
   272  
   273  	err := snapExec.ExecHook("snapname", "42", "configure")
   274  	c.Assert(err, IsNil)
   275  	c.Check(execArgv0, Equals, chain1_path)
   276  	c.Check(execArgs, DeepEquals, []string{chain1_path, chain2_path, hook_path})
   277  }
   278  
   279  func (s *snapExecSuite) TestSnapExecHookMissingHookIntegration(c *C) {
   280  	dirs.SetRootDir(c.MkDir())
   281  	snaptest.MockSnap(c, string(mockHookYaml), &snap.SideInfo{
   282  		Revision: snap.R("42"),
   283  	})
   284  
   285  	err := snapExec.ExecHook("snapname", "42", "missing-hook")
   286  	c.Assert(err, NotNil)
   287  	c.Assert(err, ErrorMatches, "cannot find hook \"missing-hook\" in \"snapname\"")
   288  }
   289  
   290  func (s *snapExecSuite) TestSnapExecIgnoresUnknownArgs(c *C) {
   291  	snapApp, rest, err := snapExec.ParseArgs([]string{"--command=shell", "snapname.app", "--arg1", "arg2"})
   292  	c.Assert(err, IsNil)
   293  	c.Assert(snapExec.GetOptsCommand(), Equals, "shell")
   294  	c.Assert(snapApp, DeepEquals, "snapname.app")
   295  	c.Assert(rest, DeepEquals, []string{"--arg1", "arg2"})
   296  }
   297  
   298  func (s *snapExecSuite) TestSnapExecErrorsOnUnknown(c *C) {
   299  	_, _, err := snapExec.ParseArgs([]string{"--command=shell", "--unknown", "snapname.app", "--arg1", "arg2"})
   300  	c.Check(err, ErrorMatches, "unknown flag `unknown'")
   301  }
   302  
   303  func (s *snapExecSuite) TestSnapExecErrorsOnMissingSnapApp(c *C) {
   304  	_, _, err := snapExec.ParseArgs([]string{"--command=shell"})
   305  	c.Check(err, ErrorMatches, "need the application to run as argument")
   306  }
   307  
   308  func (s *snapExecSuite) TestSnapExecAppRealIntegration(c *C) {
   309  	// we need a lot of mocks
   310  	dirs.SetRootDir(c.MkDir())
   311  
   312  	oldOsArgs := os.Args
   313  	defer func() { os.Args = oldOsArgs }()
   314  
   315  	os.Setenv("SNAP_REVISION", "42")
   316  	defer os.Unsetenv("SNAP_REVISION")
   317  
   318  	snaptest.MockSnap(c, string(mockYaml), &snap.SideInfo{
   319  		Revision: snap.R("42"),
   320  	})
   321  
   322  	canaryFile := filepath.Join(c.MkDir(), "canary.txt")
   323  	script := fmt.Sprintf("%s/snapname/42/run-app", dirs.SnapMountDir)
   324  	err := ioutil.WriteFile(script, []byte(fmt.Sprintf(binaryTemplate, canaryFile)), 0755)
   325  	c.Assert(err, IsNil)
   326  
   327  	// we can not use the real syscall.execv here because it would
   328  	// replace the entire test :)
   329  	restore := snapExec.MockSyscallExec(actuallyExec)
   330  	defer restore()
   331  
   332  	// run it
   333  	os.Args = []string{"snap-exec", "snapname.app", "foo", "--bar=baz", "foobar"}
   334  	err = snapExec.Run()
   335  	c.Assert(err, IsNil)
   336  
   337  	c.Assert(canaryFile, testutil.FileEquals, `run-app
   338  cmd-arg1
   339  foo
   340  --bar=baz
   341  foobar
   342  
   343  `)
   344  }
   345  
   346  func (s *snapExecSuite) TestSnapExecHookRealIntegration(c *C) {
   347  	// we need a lot of mocks
   348  	dirs.SetRootDir(c.MkDir())
   349  
   350  	oldOsArgs := os.Args
   351  	defer func() { os.Args = oldOsArgs }()
   352  
   353  	os.Setenv("SNAP_REVISION", "42")
   354  	defer os.Unsetenv("SNAP_REVISION")
   355  
   356  	canaryFile := filepath.Join(c.MkDir(), "canary.txt")
   357  
   358  	testSnap := snaptest.MockSnap(c, string(mockHookYaml), &snap.SideInfo{
   359  		Revision: snap.R("42"),
   360  	})
   361  	hookPath := filepath.Join("meta", "hooks", "configure")
   362  	hookPathAndContents := []string{hookPath, fmt.Sprintf(binaryTemplate, canaryFile)}
   363  	snaptest.PopulateDir(testSnap.MountDir(), [][]string{hookPathAndContents})
   364  	hookPath = filepath.Join(testSnap.MountDir(), hookPath)
   365  	c.Assert(os.Chmod(hookPath, 0755), IsNil)
   366  
   367  	// we can not use the real syscall.execv here because it would
   368  	// replace the entire test :)
   369  	restore := snapExec.MockSyscallExec(actuallyExec)
   370  	defer restore()
   371  
   372  	// run it
   373  	os.Args = []string{"snap-exec", "--hook=configure", "snapname"}
   374  	err := snapExec.Run()
   375  	c.Assert(err, IsNil)
   376  
   377  	c.Assert(canaryFile, testutil.FileEquals, "configure\n\n")
   378  }
   379  
   380  func actuallyExec(argv0 string, argv []string, env []string) error {
   381  	cmd := exec.Command(argv[0], argv[1:]...)
   382  	cmd.Env = env
   383  	output, err := cmd.CombinedOutput()
   384  	if len(output) > 0 {
   385  		return fmt.Errorf("Expected output length to be 0, it was %d", len(output))
   386  	}
   387  	return err
   388  }
   389  
   390  func (s *snapExecSuite) TestSnapExecShellIntegration(c *C) {
   391  	dirs.SetRootDir(c.MkDir())
   392  	snaptest.MockSnap(c, string(mockYaml), &snap.SideInfo{
   393  		Revision: snap.R("42"),
   394  	})
   395  
   396  	execArgv0 := ""
   397  	execArgs := []string{}
   398  	execEnv := []string{}
   399  	restore := snapExec.MockSyscallExec(func(argv0 string, argv []string, env []string) error {
   400  		execArgv0 = argv0
   401  		execArgs = argv
   402  		execEnv = env
   403  		return nil
   404  	})
   405  	defer restore()
   406  
   407  	// launch and verify its run the right way
   408  	err := snapExec.ExecApp("snapname.app", "42", "shell", []string{"-c", "echo foo"})
   409  	c.Assert(err, IsNil)
   410  	c.Check(execArgv0, Equals, "/bin/bash")
   411  	c.Check(execArgs, DeepEquals, []string{execArgv0, "-c", "echo foo"})
   412  	c.Check(execEnv, testutil.Contains, "LD_LIBRARY_PATH=/some/path/lib")
   413  
   414  	// launch and verify shell still runs the command chain
   415  	err = snapExec.ExecApp("snapname.app2", "42", "shell", []string{"-c", "echo foo"})
   416  	c.Assert(err, IsNil)
   417  	chain1 := fmt.Sprintf("%s/snapname/42/chain1", dirs.SnapMountDir)
   418  	chain2 := fmt.Sprintf("%s/snapname/42/chain2", dirs.SnapMountDir)
   419  	c.Check(execArgv0, Equals, chain1)
   420  	c.Check(execArgs, DeepEquals, []string{chain1, chain2, "/bin/bash", "-c", "echo foo"})
   421  }
   422  
   423  func (s *snapExecSuite) TestSnapExecAppIntegrationWithVars(c *C) {
   424  	dirs.SetRootDir(c.MkDir())
   425  	snaptest.MockSnap(c, string(mockYaml), &snap.SideInfo{
   426  		Revision: snap.R("42"),
   427  	})
   428  
   429  	execArgv0 := ""
   430  	execArgs := []string{}
   431  	execEnv := []string{}
   432  	restore := snapExec.MockSyscallExec(func(argv0 string, argv []string, env []string) error {
   433  		execArgv0 = argv0
   434  		execArgs = argv
   435  		execEnv = env
   436  		return nil
   437  	})
   438  	defer restore()
   439  
   440  	// setup env
   441  	os.Setenv("SNAP_DATA", "/var/snap/snapname/42")
   442  	defer os.Unsetenv("SNAP_DATA")
   443  
   444  	// launch and verify its run the right way
   445  	err := snapExec.ExecApp("snapname.app", "42", "", []string{"user-arg1"})
   446  	c.Assert(err, IsNil)
   447  	c.Check(execArgv0, Equals, fmt.Sprintf("%s/snapname/42/run-app", dirs.SnapMountDir))
   448  	c.Check(execArgs, DeepEquals, []string{execArgv0, "cmd-arg1", "/var/snap/snapname/42", "user-arg1"})
   449  	c.Check(execEnv, testutil.Contains, "BASE_PATH=/some/path")
   450  	c.Check(execEnv, testutil.Contains, "LD_LIBRARY_PATH=/some/path/lib")
   451  	c.Check(execEnv, testutil.Contains, fmt.Sprintf("MY_PATH=%s", os.Getenv("PATH")))
   452  }
   453  
   454  func (s *snapExecSuite) TestSnapExecExpandEnvCmdArgs(c *C) {
   455  	for _, t := range []struct {
   456  		args     []string
   457  		env      map[string]string
   458  		expected []string
   459  	}{
   460  		{
   461  			args:     []string{"foo"},
   462  			env:      nil,
   463  			expected: []string{"foo"},
   464  		},
   465  		{
   466  			args:     []string{"$var"},
   467  			env:      map[string]string{"var": "value"},
   468  			expected: []string{"value"},
   469  		},
   470  		{
   471  			args:     []string{"foo", "$not_existing"},
   472  			env:      nil,
   473  			expected: []string{"foo"},
   474  		},
   475  		{
   476  			args:     []string{"foo", "$var", "baz"},
   477  			env:      map[string]string{"var": "bar", "unrelated": "env"},
   478  			expected: []string{"foo", "bar", "baz"},
   479  		},
   480  	} {
   481  		env := osutil.Environment(t.env)
   482  		c.Check(snapExec.ExpandEnvCmdArgs(t.args, env), DeepEquals, t.expected)
   483  	}
   484  }
   485  
   486  func (s *snapExecSuite) TestSnapExecCompleteError(c *C) {
   487  	dirs.SetRootDir(c.MkDir())
   488  	snaptest.MockSnap(c, string(mockYaml), &snap.SideInfo{
   489  		Revision: snap.R("42"),
   490  	})
   491  
   492  	restore := snapExec.MockOsReadlink(func(p string) (string, error) {
   493  		c.Assert(p, Equals, "/proc/self/exe")
   494  		return "", fmt.Errorf("fail")
   495  	})
   496  	defer restore()
   497  
   498  	// setup env
   499  	os.Setenv("SNAP_DATA", "/var/snap/snapname/42")
   500  	defer os.Unsetenv("SNAP_DATA")
   501  
   502  	// launch and verify its run the right way
   503  	err := snapExec.ExecApp("snapname.app", "42", "complete", []string{"foo"})
   504  	c.Assert(err, ErrorMatches, "cannot find completion helper: fail")
   505  }
   506  
   507  func (s *snapExecSuite) TestSnapExecCompleteConfined(c *C) {
   508  	dirs.SetRootDir(c.MkDir())
   509  	snaptest.MockSnap(c, string(mockYaml), &snap.SideInfo{
   510  		Revision: snap.R("42"),
   511  	})
   512  
   513  	execArgv0 := ""
   514  	execArgs := []string{}
   515  	restore := snapExec.MockSyscallExec(func(argv0 string, argv []string, env []string) error {
   516  		execArgv0 = argv0
   517  		execArgs = argv
   518  		return nil
   519  	})
   520  	defer restore()
   521  
   522  	restore = snapExec.MockOsReadlink(func(p string) (string, error) {
   523  		c.Assert(p, Equals, "/proc/self/exe")
   524  		// as if running inside the snap mount namespace
   525  		return "/usr/lib/snapd/snap-exec", nil
   526  	})
   527  	defer restore()
   528  
   529  	// setup env
   530  	os.Setenv("SNAP_DATA", "/var/snap/snapname/42")
   531  	defer os.Unsetenv("SNAP_DATA")
   532  
   533  	// launch and verify its run the right way
   534  	err := snapExec.ExecApp("snapname.app", "42", "complete", []string{"foo"})
   535  	c.Assert(err, IsNil)
   536  	c.Check(execArgv0, Equals, "/bin/bash")
   537  	c.Check(execArgs, DeepEquals, []string{execArgv0,
   538  		dirs.CompletionHelperInCore,
   539  		filepath.Join(dirs.SnapMountDir, "snapname/42/you/complete/me"),
   540  		"foo"})
   541  }
   542  
   543  func (s *snapExecSuite) TestSnapExecCompleteClassicReexec(c *C) {
   544  	restore := release.MockReleaseInfo(&release.OS{ID: "ubuntu"})
   545  	defer restore()
   546  	dirs.SetRootDir(c.MkDir())
   547  	snaptest.MockSnap(c, string(mockClassicYaml), &snap.SideInfo{
   548  		Revision: snap.R("42"),
   549  	})
   550  
   551  	execArgv0 := ""
   552  	execArgs := []string{}
   553  	restore = snapExec.MockSyscallExec(func(argv0 string, argv []string, env []string) error {
   554  		execArgv0 = argv0
   555  		execArgs = argv
   556  		return nil
   557  	})
   558  	defer restore()
   559  
   560  	restore = snapExec.MockOsReadlink(func(p string) (string, error) {
   561  		c.Assert(p, Equals, "/proc/self/exe")
   562  		// as if it's reexeced from the snap
   563  		return filepath.Join(dirs.SnapMountDir, "core/current", dirs.CoreLibExecDir, "snap-exec"), nil
   564  	})
   565  	defer restore()
   566  
   567  	// setup env
   568  	os.Setenv("SNAP_DATA", "/var/snap/snapname/42")
   569  	defer os.Unsetenv("SNAP_DATA")
   570  
   571  	// launch and verify its run the right way
   572  	err := snapExec.ExecApp("snapname.app", "42", "complete", []string{"foo"})
   573  	c.Assert(err, IsNil)
   574  	c.Check(execArgv0, Equals, "/bin/bash")
   575  	c.Check(execArgs, DeepEquals, []string{execArgv0,
   576  		filepath.Join(dirs.SnapMountDir, "core/current", dirs.CompletionHelperInCore),
   577  		filepath.Join(dirs.SnapMountDir, "snapname/42/you/complete/me"),
   578  		"foo"})
   579  }
   580  
   581  func (s *snapExecSuite) TestSnapExecCompleteClassicNoReexec(c *C) {
   582  	restore := release.MockReleaseInfo(&release.OS{ID: "centos"})
   583  	defer restore()
   584  	dirs.SetRootDir(c.MkDir())
   585  	snaptest.MockSnap(c, string(mockClassicYaml), &snap.SideInfo{
   586  		Revision: snap.R("42"),
   587  	})
   588  
   589  	execArgv0 := ""
   590  	execArgs := []string{}
   591  	execEnv := []string{}
   592  	restore = snapExec.MockSyscallExec(func(argv0 string, argv []string, env []string) error {
   593  		execArgv0 = argv0
   594  		execArgs = argv
   595  		execEnv = env
   596  		return nil
   597  	})
   598  	defer restore()
   599  
   600  	restore = snapExec.MockOsReadlink(func(p string) (string, error) {
   601  		c.Assert(p, Equals, "/proc/self/exe")
   602  		// running from distro libexecdir
   603  		return filepath.Join(dirs.DistroLibExecDir, "snap-exec"), nil
   604  	})
   605  	defer restore()
   606  
   607  	// setup env
   608  	os.Setenv("SNAP_DATA", "/var/snap/snapname/42")
   609  	defer os.Unsetenv("SNAP_DATA")
   610  	os.Setenv("SNAP_SAVED_TMPDIR", "/var/tmp99")
   611  	defer os.Unsetenv("SNAP_SAVED_TMPDIR")
   612  
   613  	// launch and verify its run the right way
   614  	err := snapExec.ExecApp("snapname.app", "42", "complete", []string{"foo"})
   615  	c.Assert(err, IsNil)
   616  	c.Check(execArgv0, Equals, "/bin/bash")
   617  	c.Check(execArgs, DeepEquals, []string{execArgv0,
   618  		filepath.Join(dirs.DistroLibExecDir, "etelpmoc.sh"),
   619  		filepath.Join(dirs.SnapMountDir, "snapname/42/you/complete/me"),
   620  		"foo"})
   621  	c.Check(execEnv, testutil.Contains, "SNAP_DATA=/var/snap/snapname/42")
   622  	c.Check(execEnv, testutil.Contains, "TMPDIR=/var/tmp99")
   623  }