diff --git a/cmd/snap/cmd_run.go b/cmd/snap/cmd_run.go
index 717bada98fc..1ea99365753 100644
--- a/cmd/snap/cmd_run.go
+++ b/cmd/snap/cmd_run.go
@@ -125,6 +125,10 @@ func createUserDataDirs(info *snap.Info) error {
return fmt.Errorf(i18n.G("cannot get the current user: %v"), err)
}
+ snapDir := filepath.Join(usr.HomeDir, dirs.UserHomeSnapDir)
+ if err := os.MkdirAll(snapDir, 0700); err != nil {
+ return fmt.Errorf(i18n.G("cannot create snap home dir: %w"), err)
+ }
// see snapenv.User
userData := info.UserDataDir(usr.HomeDir)
commonUserData := info.UserCommonDataDir(usr.HomeDir)
diff --git a/cmd/snap/cmd_run.go.orig b/cmd/snap/cmd_run.go.orig
new file mode 100644
index 00000000000..717bada98fc
--- /dev/null
+++ b/cmd/snap/cmd_run.go.orig
@@ -0,0 +1,202 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2014-2016 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package main
+
+import (
+ "fmt"
+ "os"
+ "os/user"
+ "strings"
+ "syscall"
+
+ "github.com/jessevdk/go-flags"
+
+ "github.com/snapcore/snapd/i18n"
+ "github.com/snapcore/snapd/logger"
+ "github.com/snapcore/snapd/snap"
+ "github.com/snapcore/snapd/snap/snapenv"
+)
+
+var (
+ syscallExec = syscall.Exec
+ userCurrent = user.Current
+)
+
+type cmdRun struct {
+ Command string `long:"command" hidden:"yes"`
+ Hook string `long:"hook" hidden:"yes"`
+ Revision string `short:"r" default:"unset" hidden:"yes"`
+ Shell bool `long:"shell" `
+}
+
+func init() {
+ addCommand("run",
+ i18n.G("Run the given snap command"),
+ i18n.G("Run the given snap command with the right confinement and environment"),
+ func() flags.Commander {
+ return &cmdRun{}
+ }, map[string]string{
+ "command": i18n.G("Alternative command to run"),
+ "hook": i18n.G("Hook to run"),
+ "r": i18n.G("Use a specific snap revision when running hook"),
+ "shell": i18n.G("Run a shell instead of the command (useful for debugging)"),
+ }, nil)
+}
+
+func (x *cmdRun) Execute(args []string) error {
+ if len(args) == 0 {
+ return fmt.Errorf(i18n.G("need the application to run as argument"))
+ }
+ snapApp := args[0]
+ args = args[1:]
+
+ // Catch some invalid parameter combinations, provide helpful errors
+ if x.Hook != "" && x.Command != "" {
+ return fmt.Errorf(i18n.G("cannot use --hook and --command together"))
+ }
+ if x.Revision != "unset" && x.Revision != "" && x.Hook == "" {
+ return fmt.Errorf(i18n.G("-r can only be used with --hook"))
+ }
+ if x.Hook != "" && len(args) > 0 {
+ // TRANSLATORS: %q is the hook name; %s a space-separated list of extra arguments
+ return fmt.Errorf(i18n.G("too many arguments for hook %q: %s"), x.Hook, strings.Join(args, " "))
+ }
+
+ // Now actually handle the dispatching
+ if x.Hook != "" {
+ return snapRunHook(snapApp, x.Revision, x.Hook)
+ }
+
+ // pass shell as a special command to snap-exec
+ if x.Shell {
+ x.Command = "shell"
+ }
+
+ return snapRunApp(snapApp, x.Command, args)
+}
+
+func getSnapInfo(snapName string, revision snap.Revision) (*snap.Info, error) {
+ if revision.Unset() {
+ // User didn't supply a revision, so we need to get it via the snapd API
+ // here because once we're inside the confinement it may be unavailable.
+ snaps, err := Client().List([]string{snapName})
+ if err != nil {
+ return nil, err
+ }
+ if len(snaps) == 0 {
+ return nil, fmt.Errorf("cannot find snap %q", snapName)
+ }
+ if len(snaps) > 1 {
+ return nil, fmt.Errorf(i18n.G("multiple snaps for %q: %d"), snapName, len(snaps))
+ }
+ revision = snaps[0].Revision
+ }
+
+ info, err := snap.ReadInfo(snapName, &snap.SideInfo{
+ Revision: revision,
+ })
+ if err != nil {
+ return nil, err
+ }
+
+ return info, nil
+}
+
+func createUserDataDirs(info *snap.Info) error {
+ usr, err := userCurrent()
+ if err != nil {
+ return fmt.Errorf(i18n.G("cannot get the current user: %v"), err)
+ }
+
+ // see snapenv.User
+ userData := info.UserDataDir(usr.HomeDir)
+ commonUserData := info.UserCommonDataDir(usr.HomeDir)
+ for _, d := range []string{userData, commonUserData} {
+ if err := os.MkdirAll(d, 0755); err != nil {
+ // TRANSLATORS: %q is the directory whose creation failed, %v the error message
+ return fmt.Errorf(i18n.G("cannot create %q: %v"), d, err)
+ }
+ }
+ return nil
+}
+
+func snapRunApp(snapApp, command string, args []string) error {
+ snapName, appName := snap.SplitSnapApp(snapApp)
+ info, err := getSnapInfo(snapName, snap.R(0))
+ if err != nil {
+ return err
+ }
+
+ app := info.Apps[appName]
+ if app == nil {
+ return fmt.Errorf(i18n.G("cannot find app %q in %q"), appName, snapName)
+ }
+
+ return runSnapConfine(info, app.SecurityTag(), snapApp, command, "", args)
+}
+
+func snapRunHook(snapName, snapRevision, hookName string) error {
+ revision, err := snap.ParseRevision(snapRevision)
+ if err != nil {
+ return err
+ }
+
+ info, err := getSnapInfo(snapName, revision)
+ if err != nil {
+ return err
+ }
+
+ hook := info.Hooks[hookName]
+
+ // Make sure this hook is valid for this snap. If not, don't run it. This
+ // isn't an error, e.g. it will happen if a snap doesn't ship a system hook.
+ if hook == nil {
+ return nil
+ }
+
+ return runSnapConfine(info, hook.SecurityTag(), snapName, "", hook.Name, nil)
+}
+
+func runSnapConfine(info *snap.Info, securityTag, snapApp, command, hook string, args []string) error {
+ if err := createUserDataDirs(info); err != nil {
+ logger.Noticef("WARNING: cannot create user data directory: %s", err)
+ }
+
+ cmd := []string{
+ "/usr/bin/ubuntu-core-launcher",
+ securityTag,
+ securityTag,
+ "/usr/lib/snapd/snap-exec",
+ }
+
+ if command != "" {
+ cmd = append(cmd, "--command="+command)
+ }
+
+ if hook != "" {
+ cmd = append(cmd, "--hook="+hook)
+ }
+
+ // snap-exec is POSIXly-- options must come before positionals.
+ cmd = append(cmd, snapApp)
+ cmd = append(cmd, args...)
+
+ return syscallExec(cmd[0], cmd, snapenv.ExecEnv(info))
+}
diff --git a/cmd/snap/cmd_run_test.go b/cmd/snap/cmd_run_test.go
index 1d799e9de1e..a74b894a254 100644
--- a/cmd/snap/cmd_run_test.go
+++ b/cmd/snap/cmd_run_test.go
@@ -393,3 +393,20 @@ func (s *SnapSuite) TestSnapRunSaneEnvironmentHandling(c *check.C) {
c.Check(execEnv, check.Not(testutil.Contains), "SNAP_ARCH=PDP-7")
c.Check(execEnv, testutil.Contains, "SNAP_THE_WORLD=YES")
}
+
+func (s *RunSuite) TestCreateSnapDirPermissions(c *check.C) {
+ usr, err := user.Current()
+ c.Assert(err, check.IsNil)
+
+ usr.HomeDir = s.fakeHome
+ snaprun.MockUserCurrent(func() (*user.User, error) {
+ return usr, nil
+ })
+
+ info := &snap.Info{SuggestedName: "some-snap"}
+ c.Assert(snaprun.CreateUserDataDirs(info), check.IsNil)
+
+ fi, err := os.Stat(filepath.Join(s.fakeHome, dirs.UserHomeSnapDir))
+ c.Assert(err, check.IsNil)
+ c.Assert(fi.Mode()&os.ModePerm, check.Equals, os.FileMode(0700))
+}
diff --git a/cmd/snap/cmd_run_test.go.orig b/cmd/snap/cmd_run_test.go.orig
new file mode 100644
index 00000000000..1d799e9de1e
--- /dev/null
+++ b/cmd/snap/cmd_run_test.go.orig
@@ -0,0 +1,395 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2016 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package main_test
+
+import (
+ "fmt"
+ "net/http"
+ "os"
+ "os/user"
+ "path/filepath"
+
+ "gopkg.in/check.v1"
+
+ snaprun "github.com/snapcore/snapd/cmd/snap"
+ "github.com/snapcore/snapd/dirs"
+ "github.com/snapcore/snapd/osutil"
+ "github.com/snapcore/snapd/snap"
+ "github.com/snapcore/snapd/snap/snaptest"
+ "github.com/snapcore/snapd/testutil"
+)
+
+var mockYaml = []byte(`name: snapname
+version: 1.0
+apps:
+ app:
+ command: run-app
+hooks:
+ apply-config:
+`)
+
+func (s *SnapSuite) TestInvalidParameters(c *check.C) {
+ invalidParameters := []string{"run", "--hook=apply-config", "--command=command-name", "snap-name"}
+ _, err := snaprun.Parser().ParseArgs(invalidParameters)
+ c.Check(err, check.ErrorMatches, ".*cannot use --hook and --command together.*")
+
+ invalidParameters = []string{"run", "-r=1", "--command=command-name", "snap-name"}
+ _, err = snaprun.Parser().ParseArgs(invalidParameters)
+ c.Check(err, check.ErrorMatches, ".*-r can only be used with --hook.*")
+
+ invalidParameters = []string{"run", "-r=1", "snap-name"}
+ _, err = snaprun.Parser().ParseArgs(invalidParameters)
+ c.Check(err, check.ErrorMatches, ".*-r can only be used with --hook.*")
+
+ invalidParameters = []string{"run", "--hook=apply-config", "foo", "bar", "snap-name"}
+ _, err = snaprun.Parser().ParseArgs(invalidParameters)
+ c.Check(err, check.ErrorMatches, ".*too many arguments for hook \"apply-config\": bar.*")
+}
+
+func (s *SnapSuite) TestSnapRunAppIntegration(c *check.C) {
+ // mock installed snap
+ dirs.SetRootDir(c.MkDir())
+ defer func() { dirs.SetRootDir("/") }()
+
+ snaptest.MockSnap(c, string(mockYaml), &snap.SideInfo{
+ Revision: snap.R(42),
+ })
+
+ // and mock the server
+ s.mockServer(c)
+
+ // redirect exec
+ execArg0 := ""
+ execArgs := []string{}
+ execEnv := []string{}
+ restorer := snaprun.MockSyscallExec(func(arg0 string, args []string, envv []string) error {
+ execArg0 = arg0
+ execArgs = args
+ execEnv = envv
+ return nil
+ })
+ defer restorer()
+
+ // and run it!
+ rest, err := snaprun.Parser().ParseArgs([]string{"run", "snapname.app", "--arg1", "arg2"})
+ c.Assert(err, check.IsNil)
+ c.Assert(rest, check.DeepEquals, []string{"snapname.app", "--arg1", "arg2"})
+ c.Check(execArg0, check.Equals, "/usr/bin/ubuntu-core-launcher")
+ c.Check(execArgs, check.DeepEquals, []string{
+ "/usr/bin/ubuntu-core-launcher",
+ "snap.snapname.app",
+ "snap.snapname.app",
+ "/usr/lib/snapd/snap-exec",
+ "snapname.app",
+ "--arg1", "arg2"})
+ c.Check(execEnv, testutil.Contains, "SNAP_REVISION=42")
+}
+
+func (s *SnapSuite) TestSnapRunAppWithCommandIntegration(c *check.C) {
+ // mock installed snap
+ dirs.SetRootDir(c.MkDir())
+ defer func() { dirs.SetRootDir("/") }()
+
+ snaptest.MockSnap(c, string(mockYaml), &snap.SideInfo{
+ Revision: snap.R(42),
+ })
+
+ // and mock the server
+ s.mockServer(c)
+
+ // redirect exec
+ execArg0 := ""
+ execArgs := []string{}
+ execEnv := []string{}
+ restorer := snaprun.MockSyscallExec(func(arg0 string, args []string, envv []string) error {
+ execArg0 = arg0
+ execArgs = args
+ execEnv = envv
+ return nil
+ })
+ defer restorer()
+
+ // and run it!
+ err := snaprun.SnapRunApp("snapname.app", "my-command", []string{"arg1", "arg2"})
+ c.Assert(err, check.IsNil)
+ c.Check(execArg0, check.Equals, "/usr/bin/ubuntu-core-launcher")
+ c.Check(execArgs, check.DeepEquals, []string{
+ "/usr/bin/ubuntu-core-launcher",
+ "snap.snapname.app",
+ "snap.snapname.app",
+ "/usr/lib/snapd/snap-exec",
+ "--command=my-command", "snapname.app",
+ "arg1", "arg2"})
+ c.Check(execEnv, testutil.Contains, "SNAP_REVISION=42")
+}
+
+func (s *SnapSuite) TestSnapRunCreateDataDirs(c *check.C) {
+ info, err := snap.InfoFromSnapYaml(mockYaml)
+ c.Assert(err, check.IsNil)
+ info.SideInfo.Revision = snap.R(42)
+
+ fakeHome := c.MkDir()
+ restorer := snaprun.MockUserCurrent(func() (*user.User, error) {
+ return &user.User{HomeDir: fakeHome}, nil
+ })
+ defer restorer()
+
+ err = snaprun.CreateUserDataDirs(info)
+ c.Assert(err, check.IsNil)
+ c.Check(osutil.FileExists(filepath.Join(fakeHome, "/snap/snapname/42")), check.Equals, true)
+ c.Check(osutil.FileExists(filepath.Join(fakeHome, "/snap/snapname/common")), check.Equals, true)
+}
+
+func (s *SnapSuite) TestSnapRunHookIntegration(c *check.C) {
+ // mock installed snap
+ dirs.SetRootDir(c.MkDir())
+ defer func() { dirs.SetRootDir("/") }()
+
+ snaptest.MockSnap(c, string(mockYaml), &snap.SideInfo{
+ Revision: snap.R(42),
+ })
+
+ // and mock the server
+ s.mockServer(c)
+
+ // redirect exec
+ execArg0 := ""
+ execArgs := []string{}
+ execEnv := []string{}
+ restorer := snaprun.MockSyscallExec(func(arg0 string, args []string, envv []string) error {
+ execArg0 = arg0
+ execArgs = args
+ execEnv = envv
+ return nil
+ })
+ defer restorer()
+
+ // Run a hook from the active revision
+ _, err := snaprun.Parser().ParseArgs([]string{"run", "--hook=apply-config", "snapname"})
+ c.Assert(err, check.IsNil)
+ c.Check(execArg0, check.Equals, "/usr/bin/ubuntu-core-launcher")
+ c.Check(execArgs, check.DeepEquals, []string{
+ "/usr/bin/ubuntu-core-launcher",
+ "snap.snapname.hook.apply-config",
+ "snap.snapname.hook.apply-config",
+ "/usr/lib/snapd/snap-exec",
+ "--hook=apply-config", "snapname"})
+ c.Check(execEnv, testutil.Contains, "SNAP_REVISION=42")
+}
+
+func (s *SnapSuite) TestSnapRunHookUnsetRevisionIntegration(c *check.C) {
+ // mock installed snap
+ dirs.SetRootDir(c.MkDir())
+ defer func() { dirs.SetRootDir("/") }()
+
+ snaptest.MockSnap(c, string(mockYaml), &snap.SideInfo{
+ Revision: snap.R(42),
+ })
+
+ // and mock the server
+ s.mockServer(c)
+
+ // redirect exec
+ execArg0 := ""
+ execArgs := []string{}
+ execEnv := []string{}
+ restorer := snaprun.MockSyscallExec(func(arg0 string, args []string, envv []string) error {
+ execArg0 = arg0
+ execArgs = args
+ execEnv = envv
+ return nil
+ })
+ defer restorer()
+
+ // Specifically pass "unset" which would use the active version.
+ _, err := snaprun.Parser().ParseArgs([]string{"run", "--hook=apply-config", "-r=unset", "snapname"})
+ c.Assert(err, check.IsNil)
+ c.Check(execArg0, check.Equals, "/usr/bin/ubuntu-core-launcher")
+ c.Check(execArgs, check.DeepEquals, []string{
+ "/usr/bin/ubuntu-core-launcher",
+ "snap.snapname.hook.apply-config",
+ "snap.snapname.hook.apply-config",
+ "/usr/lib/snapd/snap-exec",
+ "--hook=apply-config", "snapname"})
+ c.Check(execEnv, testutil.Contains, "SNAP_REVISION=42")
+}
+
+func (s *SnapSuite) TestSnapRunHookSpecificRevisionIntegration(c *check.C) {
+ // mock installed snap
+ dirs.SetRootDir(c.MkDir())
+ defer func() { dirs.SetRootDir("/") }()
+
+ // Create both revisions 41 and 42
+ snaptest.MockSnap(c, string(mockYaml), &snap.SideInfo{
+ Revision: snap.R(41),
+ })
+ snaptest.MockSnap(c, string(mockYaml), &snap.SideInfo{
+ Revision: snap.R(42),
+ })
+
+ // and mock the server
+ s.mockServer(c)
+
+ // redirect exec
+ execArg0 := ""
+ execArgs := []string{}
+ execEnv := []string{}
+ restorer := snaprun.MockSyscallExec(func(arg0 string, args []string, envv []string) error {
+ execArg0 = arg0
+ execArgs = args
+ execEnv = envv
+ return nil
+ })
+ defer restorer()
+
+ // Run a hook on revision 41
+ _, err := snaprun.Parser().ParseArgs([]string{"run", "--hook=apply-config", "-r=41", "snapname"})
+ c.Assert(err, check.IsNil)
+ c.Check(execArg0, check.Equals, "/usr/bin/ubuntu-core-launcher")
+ c.Check(execArgs, check.DeepEquals, []string{
+ "/usr/bin/ubuntu-core-launcher",
+ "snap.snapname.hook.apply-config",
+ "snap.snapname.hook.apply-config",
+ "/usr/lib/snapd/snap-exec",
+ "--hook=apply-config", "snapname"})
+ c.Check(execEnv, testutil.Contains, "SNAP_REVISION=41")
+}
+
+func (s *SnapSuite) TestSnapRunHookMissingRevisionIntegration(c *check.C) {
+ // mock installed snap
+ dirs.SetRootDir(c.MkDir())
+ defer func() { dirs.SetRootDir("/") }()
+
+ // Only create revision 42
+ snaptest.MockSnap(c, string(mockYaml), &snap.SideInfo{
+ Revision: snap.R(42),
+ })
+
+ // and mock the server
+ s.mockServer(c)
+
+ // redirect exec
+ restorer := snaprun.MockSyscallExec(func(arg0 string, args []string, envv []string) error {
+ return nil
+ })
+ defer restorer()
+
+ // Attempt to run a hook on revision 41, which doesn't exist
+ _, err := snaprun.Parser().ParseArgs([]string{"run", "--hook=apply-config", "-r=41", "snapname"})
+ c.Assert(err, check.NotNil)
+ c.Check(err, check.ErrorMatches, "cannot find .*")
+}
+
+func (s *SnapSuite) TestSnapRunHookInvalidRevisionIntegration(c *check.C) {
+ _, err := snaprun.Parser().ParseArgs([]string{"run", "--hook=apply-config", "-r=invalid", "snapname"})
+ c.Assert(err, check.NotNil)
+ c.Check(err, check.ErrorMatches, "invalid snap revision: \"invalid\"")
+}
+
+func (s *SnapSuite) TestSnapRunHookMissingHookIntegration(c *check.C) {
+ // mock installed snap
+ dirs.SetRootDir(c.MkDir())
+ defer func() { dirs.SetRootDir("/") }()
+
+ // Only create revision 42
+ snaptest.MockSnap(c, string(mockYaml), &snap.SideInfo{
+ Revision: snap.R(42),
+ })
+
+ // and mock the server
+ s.mockServer(c)
+
+ // redirect exec
+ called := false
+ restorer := snaprun.MockSyscallExec(func(arg0 string, args []string, envv []string) error {
+ called = true
+ return nil
+ })
+ defer restorer()
+
+ err := snaprun.SnapRunHook("snapname", "unset", "missing-hook")
+ c.Assert(err, check.IsNil)
+ c.Check(called, check.Equals, false)
+}
+
+func (s *SnapSuite) mockServer(c *check.C) {
+ n := 0
+ s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
+ switch n {
+ case 0:
+ c.Check(r.Method, check.Equals, "GET")
+ c.Check(r.URL.Path, check.Equals, "/v2/snaps")
+ fmt.Fprintln(w, `{"type": "sync", "result": [{"name": "snapname", "status": "active", "version": "1.0", "developer": "someone", "revision":42}]}`)
+ default:
+ c.Fatalf("expected to get 1 requests, now on %d", n+1)
+ }
+
+ n++
+ })
+}
+
+func (s *SnapSuite) TestSnapRunErorsForUnknownRunArg(c *check.C) {
+ _, err := snaprun.Parser().ParseArgs([]string{"run", "--unknown", "snapname.app", "--arg1", "arg2"})
+ c.Assert(err, check.ErrorMatches, "unknown flag `unknown'")
+}
+
+func (s *SnapSuite) TestSnapRunErorsForMissingApp(c *check.C) {
+ _, err := snaprun.Parser().ParseArgs([]string{"run", "--command=shell"})
+ c.Assert(err, check.ErrorMatches, "need the application to run as argument")
+}
+
+func (s *SnapSuite) TestSnapRunSaneEnvironmentHandling(c *check.C) {
+ // mock installed snap
+ dirs.SetRootDir(c.MkDir())
+ defer func() { dirs.SetRootDir("/") }()
+
+ snaptest.MockSnap(c, string(mockYaml), &snap.SideInfo{
+ Revision: snap.R(42),
+ })
+
+ // and mock the server
+ s.mockServer(c)
+
+ // redirect exec
+ execEnv := []string{}
+ restorer := snaprun.MockSyscallExec(func(arg0 string, args []string, envv []string) error {
+ execEnv = envv
+ return nil
+ })
+ defer restorer()
+
+ // set a SNAP{,_*} variable in the environment
+ os.Setenv("SNAP_NAME", "something-else")
+ os.Setenv("SNAP_ARCH", "PDP-7")
+ defer os.Unsetenv("SNAP_NAME")
+ defer os.Unsetenv("SNAP_ARCH")
+ // but unreleated stuff is ok
+ os.Setenv("SNAP_THE_WORLD", "YES")
+ defer os.Unsetenv("SNAP_THE_WORLD")
+
+ // and ensure those SNAP_ vars get overriden
+ rest, err := snaprun.Parser().ParseArgs([]string{"run", "snapname.app", "--arg1", "arg2"})
+ c.Assert(err, check.IsNil)
+ c.Assert(rest, check.DeepEquals, []string{"snapname.app", "--arg1", "arg2"})
+ c.Check(execEnv, testutil.Contains, "SNAP_REVISION=42")
+ c.Check(execEnv, check.Not(testutil.Contains), "SNAP_NAME=something-else")
+ c.Check(execEnv, check.Not(testutil.Contains), "SNAP_ARCH=PDP-7")
+ c.Check(execEnv, testutil.Contains, "SNAP_THE_WORLD=YES")
+}