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