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