github.com/kubiko/snapd@v0.0.0-20201013125620-d4f3094d9ddf/overlord/snapstate/snapstate_remove_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2016-2020 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 snapstate_test
    21  
    22  import (
    23  	"path/filepath"
    24  
    25  	. "gopkg.in/check.v1"
    26  
    27  	"github.com/snapcore/snapd/dirs"
    28  	"github.com/snapcore/snapd/osutil"
    29  	"github.com/snapcore/snapd/overlord/configstate/config"
    30  	"github.com/snapcore/snapd/overlord/snapstate"
    31  	"github.com/snapcore/snapd/overlord/state"
    32  	"github.com/snapcore/snapd/snap"
    33  	"github.com/snapcore/snapd/testutil"
    34  )
    35  
    36  func (s *snapmgrTestSuite) TestRemoveTasks(c *C) {
    37  	s.state.Lock()
    38  	defer s.state.Unlock()
    39  
    40  	snapstate.Set(s.state, "foo", &snapstate.SnapState{
    41  		Active: true,
    42  		Sequence: []*snap.SideInfo{
    43  			{RealName: "foo", Revision: snap.R(11)},
    44  		},
    45  		Current:  snap.R(11),
    46  		SnapType: "app",
    47  	})
    48  
    49  	ts, err := snapstate.Remove(s.state, "foo", snap.R(0), nil)
    50  	c.Assert(err, IsNil)
    51  
    52  	c.Assert(s.state.TaskCount(), Equals, len(ts.Tasks()))
    53  	verifyRemoveTasks(c, ts)
    54  }
    55  
    56  func (s *snapmgrTestSuite) TestRemoveTasksAutoSnapshotDisabled(c *C) {
    57  	snapstate.AutomaticSnapshot = func(st *state.State, instanceName string) (ts *state.TaskSet, err error) {
    58  		return nil, snapstate.ErrNothingToDo
    59  	}
    60  
    61  	s.state.Lock()
    62  	defer s.state.Unlock()
    63  
    64  	snapstate.Set(s.state, "foo", &snapstate.SnapState{
    65  		Active: true,
    66  		Sequence: []*snap.SideInfo{
    67  			{RealName: "foo", Revision: snap.R(11)},
    68  		},
    69  		Current:  snap.R(11),
    70  		SnapType: "app",
    71  	})
    72  
    73  	ts, err := snapstate.Remove(s.state, "foo", snap.R(0), nil)
    74  	c.Assert(err, IsNil)
    75  
    76  	c.Assert(taskKinds(ts.Tasks()), DeepEquals, []string{
    77  		"stop-snap-services",
    78  		"run-hook[remove]",
    79  		"auto-disconnect",
    80  		"remove-aliases",
    81  		"unlink-snap",
    82  		"remove-profiles",
    83  		"clear-snap",
    84  		"discard-snap",
    85  	})
    86  }
    87  
    88  func (s *snapmgrTestSuite) TestRemoveTasksAutoSnapshotDisabledByPurgeFlag(c *C) {
    89  	s.state.Lock()
    90  	defer s.state.Unlock()
    91  
    92  	snapstate.Set(s.state, "foo", &snapstate.SnapState{
    93  		Active: true,
    94  		Sequence: []*snap.SideInfo{
    95  			{RealName: "foo", Revision: snap.R(11)},
    96  		},
    97  		Current:  snap.R(11),
    98  		SnapType: "app",
    99  	})
   100  
   101  	ts, err := snapstate.Remove(s.state, "foo", snap.R(0), &snapstate.RemoveFlags{Purge: true})
   102  	c.Assert(err, IsNil)
   103  
   104  	c.Assert(taskKinds(ts.Tasks()), DeepEquals, []string{
   105  		"stop-snap-services",
   106  		"run-hook[remove]",
   107  		"auto-disconnect",
   108  		"remove-aliases",
   109  		"unlink-snap",
   110  		"remove-profiles",
   111  		"clear-snap",
   112  		"discard-snap",
   113  	})
   114  }
   115  
   116  func (s *snapmgrTestSuite) TestRemoveHookNotExecutedIfNotLastRevison(c *C) {
   117  	s.state.Lock()
   118  	defer s.state.Unlock()
   119  
   120  	snapstate.Set(s.state, "foo", &snapstate.SnapState{
   121  		Active: true,
   122  		Sequence: []*snap.SideInfo{
   123  			{RealName: "foo", Revision: snap.R(11)},
   124  			{RealName: "foo", Revision: snap.R(12)},
   125  		},
   126  		Current: snap.R(12),
   127  	})
   128  
   129  	ts, err := snapstate.Remove(s.state, "foo", snap.R(11), nil)
   130  	c.Assert(err, IsNil)
   131  
   132  	runHooks := tasksWithKind(ts, "run-hook")
   133  	// no 'remove' hook task
   134  	c.Assert(runHooks, HasLen, 0)
   135  }
   136  
   137  func (s *snapmgrTestSuite) TestRemoveConflict(c *C) {
   138  	s.state.Lock()
   139  	defer s.state.Unlock()
   140  
   141  	snapstate.Set(s.state, "some-snap", &snapstate.SnapState{
   142  		Active:   true,
   143  		Sequence: []*snap.SideInfo{{RealName: "some-snap", Revision: snap.R(11)}},
   144  		Current:  snap.R(11),
   145  	})
   146  
   147  	ts, err := snapstate.Remove(s.state, "some-snap", snap.R(0), nil)
   148  	c.Assert(err, IsNil)
   149  	// need a change to make the tasks visible
   150  	s.state.NewChange("remove", "...").AddAll(ts)
   151  
   152  	_, err = snapstate.Remove(s.state, "some-snap", snap.R(0), nil)
   153  	c.Assert(err, ErrorMatches, `snap "some-snap" has "remove" change in progress`)
   154  }
   155  
   156  func (s *snapmgrTestSuite) testRemoveDiskSpaceCheck(c *C, featureFlag, automaticSnapshot bool) error {
   157  	s.state.Lock()
   158  	defer s.state.Unlock()
   159  
   160  	restore := snapstate.MockOsutilCheckFreeSpace(func(string, uint64) error {
   161  		// osutil.CheckFreeSpace shouldn't be hit if either featureFlag
   162  		// or automaticSnapshot is false. If both are true then we return disk
   163  		// space error which should result in snapstate.InsufficientSpaceError
   164  		// on remove().
   165  		return &osutil.NotEnoughDiskSpaceError{}
   166  	})
   167  	defer restore()
   168  
   169  	var automaticSnapshotCalled bool
   170  	snapstate.AutomaticSnapshot = func(st *state.State, instanceName string) (ts *state.TaskSet, err error) {
   171  		automaticSnapshotCalled = true
   172  		if automaticSnapshot {
   173  			t := s.state.NewTask("foo", "")
   174  			ts = state.NewTaskSet(t)
   175  			return ts, nil
   176  		}
   177  		// ErrNothingToDo is returned if automatic snapshots are disabled
   178  		return nil, snapstate.ErrNothingToDo
   179  	}
   180  
   181  	tr := config.NewTransaction(s.state)
   182  	tr.Set("core", "experimental.check-disk-space-remove", featureFlag)
   183  	tr.Commit()
   184  
   185  	snapstate.Set(s.state, "some-snap", &snapstate.SnapState{
   186  		Active:   true,
   187  		Sequence: []*snap.SideInfo{{RealName: "some-snap", Revision: snap.R(11)}},
   188  		Current:  snap.R(11),
   189  		SnapType: "app",
   190  	})
   191  
   192  	_, err := snapstate.Remove(s.state, "some-snap", snap.R(0), nil)
   193  	c.Assert(automaticSnapshotCalled, Equals, true)
   194  	return err
   195  }
   196  
   197  func (s *snapmgrTestSuite) TestRemoveDiskSpaceCheckDoesNothingWhenNoSnapshot(c *C) {
   198  	featureFlag := true
   199  	snapshot := false
   200  	err := s.testRemoveDiskSpaceCheck(c, featureFlag, snapshot)
   201  	c.Assert(err, IsNil)
   202  }
   203  
   204  func (s *snapmgrTestSuite) TestRemoveDiskSpaceCheckDisabledByFeatureFlag(c *C) {
   205  	featureFlag := false
   206  	snapshot := true
   207  	err := s.testRemoveDiskSpaceCheck(c, featureFlag, snapshot)
   208  	c.Assert(err, IsNil)
   209  }
   210  
   211  func (s *snapmgrTestSuite) TestRemoveDiskSpaceForSnapshotError(c *C) {
   212  	featureFlag := true
   213  	snapshot := true
   214  	// both the snapshot and disk check feature are enabled, so we should hit
   215  	// the disk check (which fails).
   216  	err := s.testRemoveDiskSpaceCheck(c, featureFlag, snapshot)
   217  	c.Assert(err, NotNil)
   218  
   219  	diskSpaceErr := err.(*snapstate.InsufficientSpaceError)
   220  	c.Assert(diskSpaceErr, ErrorMatches, `cannot create automatic snapshot when removing last revision of the snap: insufficient space.*`)
   221  	c.Check(diskSpaceErr.Path, Equals, filepath.Join(dirs.GlobalRootDir, "/var/lib/snapd"))
   222  	c.Check(diskSpaceErr.Snaps, DeepEquals, []string{"some-snap"})
   223  	c.Check(diskSpaceErr.ChangeKind, Equals, "remove")
   224  }
   225  
   226  func (s *snapmgrTestSuite) TestRemoveRunThrough(c *C) {
   227  	c.Assert(snapstate.KeepAuxStoreInfo("some-snap-id", nil), IsNil)
   228  	c.Check(snapstate.AuxStoreInfoFilename("some-snap-id"), testutil.FilePresent)
   229  	si := snap.SideInfo{
   230  		SnapID:   "some-snap-id",
   231  		RealName: "some-snap",
   232  		Revision: snap.R(7),
   233  	}
   234  
   235  	s.state.Lock()
   236  	defer s.state.Unlock()
   237  
   238  	snapstate.Set(s.state, "some-snap", &snapstate.SnapState{
   239  		Active:   true,
   240  		Sequence: []*snap.SideInfo{&si},
   241  		Current:  si.Revision,
   242  		SnapType: "app",
   243  	})
   244  
   245  	chg := s.state.NewChange("remove", "remove a snap")
   246  	ts, err := snapstate.Remove(s.state, "some-snap", snap.R(0), nil)
   247  	c.Assert(err, IsNil)
   248  	chg.AddAll(ts)
   249  
   250  	s.state.Unlock()
   251  	defer s.se.Stop()
   252  	s.settle(c)
   253  	s.state.Lock()
   254  
   255  	expected := fakeOps{
   256  		{
   257  			op:    "auto-disconnect:Doing",
   258  			name:  "some-snap",
   259  			revno: snap.R(7),
   260  		},
   261  		{
   262  			op:   "remove-snap-aliases",
   263  			name: "some-snap",
   264  		},
   265  		{
   266  			op:   "unlink-snap",
   267  			path: filepath.Join(dirs.SnapMountDir, "some-snap/7"),
   268  		},
   269  		{
   270  			op:    "remove-profiles:Doing",
   271  			name:  "some-snap",
   272  			revno: snap.R(7),
   273  		},
   274  		{
   275  			op:   "remove-snap-data",
   276  			path: filepath.Join(dirs.SnapMountDir, "some-snap/7"),
   277  		},
   278  		{
   279  			op:   "remove-snap-common-data",
   280  			path: filepath.Join(dirs.SnapMountDir, "some-snap/7"),
   281  		},
   282  		{
   283  			op:   "remove-snap-data-dir",
   284  			name: "some-snap",
   285  			path: filepath.Join(dirs.SnapDataDir, "some-snap"),
   286  		},
   287  		{
   288  			op:    "remove-snap-files",
   289  			path:  filepath.Join(dirs.SnapMountDir, "some-snap/7"),
   290  			stype: "app",
   291  		},
   292  		{
   293  			op:   "discard-namespace",
   294  			name: "some-snap",
   295  		},
   296  		{
   297  			op:   "remove-snap-dir",
   298  			name: "some-snap",
   299  			path: filepath.Join(dirs.SnapMountDir, "some-snap"),
   300  		},
   301  	}
   302  	// start with an easier-to-read error if this fails:
   303  	c.Check(len(s.fakeBackend.ops), Equals, len(expected))
   304  	c.Assert(s.fakeBackend.ops.Ops(), DeepEquals, expected.Ops())
   305  	c.Check(s.fakeBackend.ops, DeepEquals, expected)
   306  
   307  	// verify snapSetup info
   308  	tasks := ts.Tasks()
   309  	for _, t := range tasks {
   310  		if t.Kind() == "run-hook" {
   311  			continue
   312  		}
   313  		if t.Kind() == "save-snapshot" {
   314  			continue
   315  		}
   316  		snapsup, err := snapstate.TaskSnapSetup(t)
   317  		c.Assert(err, IsNil)
   318  
   319  		var expSnapSetup *snapstate.SnapSetup
   320  		switch t.Kind() {
   321  		case "discard-conns":
   322  			expSnapSetup = &snapstate.SnapSetup{
   323  				SideInfo: &snap.SideInfo{
   324  					RealName: "some-snap",
   325  				},
   326  			}
   327  		case "clear-snap", "discard-snap":
   328  			expSnapSetup = &snapstate.SnapSetup{
   329  				SideInfo: &snap.SideInfo{
   330  					RealName: "some-snap",
   331  					SnapID:   "some-snap-id",
   332  					Revision: snap.R(7),
   333  				},
   334  			}
   335  		default:
   336  			expSnapSetup = &snapstate.SnapSetup{
   337  				SideInfo: &snap.SideInfo{
   338  					RealName: "some-snap",
   339  					SnapID:   "some-snap-id",
   340  					Revision: snap.R(7),
   341  				},
   342  				Type:      snap.TypeApp,
   343  				PlugsOnly: true,
   344  			}
   345  
   346  		}
   347  
   348  		c.Check(snapsup, DeepEquals, expSnapSetup, Commentf(t.Kind()))
   349  	}
   350  
   351  	// verify snaps in the system state
   352  	var snapst snapstate.SnapState
   353  	err = snapstate.Get(s.state, "some-snap", &snapst)
   354  	c.Assert(err, Equals, state.ErrNoState)
   355  	c.Check(snapstate.AuxStoreInfoFilename("some-snap-id"), testutil.FileAbsent)
   356  
   357  }
   358  
   359  func (s *snapmgrTestSuite) TestParallelInstanceRemoveRunThrough(c *C) {
   360  	si := snap.SideInfo{
   361  		RealName: "some-snap",
   362  		Revision: snap.R(7),
   363  	}
   364  
   365  	s.state.Lock()
   366  	defer s.state.Unlock()
   367  
   368  	// pretend we have both a regular snap and a parallel instance
   369  	snapstate.Set(s.state, "some-snap_instance", &snapstate.SnapState{
   370  		Active:      true,
   371  		Sequence:    []*snap.SideInfo{&si},
   372  		Current:     si.Revision,
   373  		SnapType:    "app",
   374  		InstanceKey: "instance",
   375  	})
   376  	snapstate.Set(s.state, "some-snap", &snapstate.SnapState{
   377  		Active:   true,
   378  		Sequence: []*snap.SideInfo{&si},
   379  		Current:  si.Revision,
   380  		SnapType: "app",
   381  	})
   382  
   383  	chg := s.state.NewChange("remove", "remove a snap")
   384  	ts, err := snapstate.Remove(s.state, "some-snap_instance", snap.R(0), nil)
   385  	c.Assert(err, IsNil)
   386  	chg.AddAll(ts)
   387  
   388  	s.state.Unlock()
   389  	s.settle(c)
   390  	s.state.Lock()
   391  
   392  	expected := fakeOps{
   393  		{
   394  			op:    "auto-disconnect:Doing",
   395  			name:  "some-snap_instance",
   396  			revno: snap.R(7),
   397  		},
   398  		{
   399  			op:   "remove-snap-aliases",
   400  			name: "some-snap_instance",
   401  		},
   402  		{
   403  			op:   "unlink-snap",
   404  			path: filepath.Join(dirs.SnapMountDir, "some-snap_instance/7"),
   405  		},
   406  		{
   407  			op:    "remove-profiles:Doing",
   408  			name:  "some-snap_instance",
   409  			revno: snap.R(7),
   410  		},
   411  		{
   412  			op:   "remove-snap-data",
   413  			path: filepath.Join(dirs.SnapMountDir, "some-snap_instance/7"),
   414  		},
   415  		{
   416  			op:   "remove-snap-common-data",
   417  			path: filepath.Join(dirs.SnapMountDir, "some-snap_instance/7"),
   418  		},
   419  		{
   420  			op:             "remove-snap-data-dir",
   421  			name:           "some-snap_instance",
   422  			path:           filepath.Join(dirs.SnapDataDir, "some-snap"),
   423  			otherInstances: true,
   424  		},
   425  		{
   426  			op:    "remove-snap-files",
   427  			path:  filepath.Join(dirs.SnapMountDir, "some-snap_instance/7"),
   428  			stype: "app",
   429  		},
   430  		{
   431  			op:   "discard-namespace",
   432  			name: "some-snap_instance",
   433  		},
   434  		{
   435  			op:             "remove-snap-dir",
   436  			name:           "some-snap_instance",
   437  			path:           filepath.Join(dirs.SnapMountDir, "some-snap"),
   438  			otherInstances: true,
   439  		},
   440  	}
   441  	// start with an easier-to-read error if this fails:
   442  	c.Check(len(s.fakeBackend.ops), Equals, len(expected))
   443  	c.Assert(s.fakeBackend.ops.Ops(), DeepEquals, expected.Ops())
   444  	c.Check(s.fakeBackend.ops, DeepEquals, expected)
   445  
   446  	// verify snapSetup info
   447  	tasks := ts.Tasks()
   448  	for _, t := range tasks {
   449  		if t.Kind() == "run-hook" {
   450  			continue
   451  		}
   452  		if t.Kind() == "save-snapshot" {
   453  			continue
   454  		}
   455  		snapsup, err := snapstate.TaskSnapSetup(t)
   456  		c.Assert(err, IsNil)
   457  
   458  		var expSnapSetup *snapstate.SnapSetup
   459  		switch t.Kind() {
   460  		case "discard-conns":
   461  			expSnapSetup = &snapstate.SnapSetup{
   462  				SideInfo: &snap.SideInfo{
   463  					RealName: "some-snap",
   464  				},
   465  				InstanceKey: "instance",
   466  			}
   467  		case "clear-snap", "discard-snap":
   468  			expSnapSetup = &snapstate.SnapSetup{
   469  				SideInfo: &snap.SideInfo{
   470  					RealName: "some-snap",
   471  					Revision: snap.R(7),
   472  				},
   473  				InstanceKey: "instance",
   474  			}
   475  		default:
   476  			expSnapSetup = &snapstate.SnapSetup{
   477  				SideInfo: &snap.SideInfo{
   478  					RealName: "some-snap",
   479  					Revision: snap.R(7),
   480  				},
   481  				Type:        snap.TypeApp,
   482  				PlugsOnly:   true,
   483  				InstanceKey: "instance",
   484  			}
   485  
   486  		}
   487  
   488  		c.Check(snapsup, DeepEquals, expSnapSetup, Commentf(t.Kind()))
   489  	}
   490  
   491  	// verify snaps in the system state
   492  	var snapst snapstate.SnapState
   493  	err = snapstate.Get(s.state, "some-snap_instance", &snapst)
   494  	c.Assert(err, Equals, state.ErrNoState)
   495  
   496  	// the non-instance snap is still there
   497  	err = snapstate.Get(s.state, "some-snap", &snapst)
   498  	c.Assert(err, IsNil)
   499  }
   500  
   501  func (s *snapmgrTestSuite) TestParallelInstanceRemoveRunThroughOtherInstances(c *C) {
   502  	si := snap.SideInfo{
   503  		RealName: "some-snap",
   504  		Revision: snap.R(7),
   505  	}
   506  
   507  	s.state.Lock()
   508  	defer s.state.Unlock()
   509  
   510  	// pretend we have both a regular snap and a parallel instance
   511  	snapstate.Set(s.state, "some-snap_instance", &snapstate.SnapState{
   512  		Active:      true,
   513  		Sequence:    []*snap.SideInfo{&si},
   514  		Current:     si.Revision,
   515  		SnapType:    "app",
   516  		InstanceKey: "instance",
   517  	})
   518  	snapstate.Set(s.state, "some-snap_other", &snapstate.SnapState{
   519  		Active:      true,
   520  		Sequence:    []*snap.SideInfo{&si},
   521  		Current:     si.Revision,
   522  		SnapType:    "app",
   523  		InstanceKey: "other",
   524  	})
   525  
   526  	chg := s.state.NewChange("remove", "remove a snap")
   527  	ts, err := snapstate.Remove(s.state, "some-snap_instance", snap.R(0), nil)
   528  	c.Assert(err, IsNil)
   529  	chg.AddAll(ts)
   530  
   531  	s.state.Unlock()
   532  	s.settle(c)
   533  	s.state.Lock()
   534  
   535  	expected := fakeOps{
   536  		{
   537  			op:    "auto-disconnect:Doing",
   538  			name:  "some-snap_instance",
   539  			revno: snap.R(7),
   540  		},
   541  		{
   542  			op:   "remove-snap-aliases",
   543  			name: "some-snap_instance",
   544  		},
   545  		{
   546  			op:   "unlink-snap",
   547  			path: filepath.Join(dirs.SnapMountDir, "some-snap_instance/7"),
   548  		},
   549  		{
   550  			op:    "remove-profiles:Doing",
   551  			name:  "some-snap_instance",
   552  			revno: snap.R(7),
   553  		},
   554  		{
   555  			op:   "remove-snap-data",
   556  			path: filepath.Join(dirs.SnapMountDir, "some-snap_instance/7"),
   557  		},
   558  		{
   559  			op:   "remove-snap-common-data",
   560  			path: filepath.Join(dirs.SnapMountDir, "some-snap_instance/7"),
   561  		},
   562  		{
   563  			op:             "remove-snap-data-dir",
   564  			name:           "some-snap_instance",
   565  			path:           filepath.Join(dirs.SnapDataDir, "some-snap"),
   566  			otherInstances: true,
   567  		},
   568  		{
   569  			op:    "remove-snap-files",
   570  			path:  filepath.Join(dirs.SnapMountDir, "some-snap_instance/7"),
   571  			stype: "app",
   572  		},
   573  		{
   574  			op:   "discard-namespace",
   575  			name: "some-snap_instance",
   576  		},
   577  		{
   578  			op:             "remove-snap-dir",
   579  			name:           "some-snap_instance",
   580  			path:           filepath.Join(dirs.SnapMountDir, "some-snap"),
   581  			otherInstances: true,
   582  		},
   583  	}
   584  	// start with an easier-to-read error if this fails:
   585  	c.Check(len(s.fakeBackend.ops), Equals, len(expected))
   586  	c.Assert(s.fakeBackend.ops.Ops(), DeepEquals, expected.Ops())
   587  	c.Check(s.fakeBackend.ops, DeepEquals, expected)
   588  
   589  	// verify snaps in the system state
   590  	var snapst snapstate.SnapState
   591  	err = snapstate.Get(s.state, "some-snap_instance", &snapst)
   592  	c.Assert(err, Equals, state.ErrNoState)
   593  
   594  	// the other instance is still there
   595  	err = snapstate.Get(s.state, "some-snap_other", &snapst)
   596  	c.Assert(err, IsNil)
   597  }
   598  
   599  func (s *snapmgrTestSuite) TestRemoveWithManyRevisionsRunThrough(c *C) {
   600  	si3 := snap.SideInfo{
   601  		SnapID:   "some-snap-id",
   602  		RealName: "some-snap",
   603  		Revision: snap.R(3),
   604  	}
   605  
   606  	si5 := snap.SideInfo{
   607  		SnapID:   "some-snap-id",
   608  		RealName: "some-snap",
   609  		Revision: snap.R(5),
   610  	}
   611  
   612  	si7 := snap.SideInfo{
   613  		SnapID:   "some-snap-id",
   614  		RealName: "some-snap",
   615  		Revision: snap.R(7),
   616  	}
   617  
   618  	s.state.Lock()
   619  	defer s.state.Unlock()
   620  
   621  	snapstate.Set(s.state, "some-snap", &snapstate.SnapState{
   622  		Active:   true,
   623  		Sequence: []*snap.SideInfo{&si5, &si3, &si7},
   624  		Current:  si7.Revision,
   625  		SnapType: "app",
   626  	})
   627  
   628  	chg := s.state.NewChange("remove", "remove a snap")
   629  	ts, err := snapstate.Remove(s.state, "some-snap", snap.R(0), nil)
   630  	c.Assert(err, IsNil)
   631  	chg.AddAll(ts)
   632  
   633  	s.state.Unlock()
   634  	defer s.se.Stop()
   635  	s.settle(c)
   636  	s.state.Lock()
   637  
   638  	expected := fakeOps{
   639  		{
   640  			op:    "auto-disconnect:Doing",
   641  			name:  "some-snap",
   642  			revno: snap.R(7),
   643  		},
   644  		{
   645  			op:   "remove-snap-aliases",
   646  			name: "some-snap",
   647  		},
   648  		{
   649  			op:   "unlink-snap",
   650  			path: filepath.Join(dirs.SnapMountDir, "some-snap/7"),
   651  		},
   652  		{
   653  			op:    "remove-profiles:Doing",
   654  			name:  "some-snap",
   655  			revno: snap.R(7),
   656  		},
   657  		{
   658  			op:   "remove-snap-data",
   659  			path: filepath.Join(dirs.SnapMountDir, "some-snap/7"),
   660  		},
   661  		{
   662  			op:    "remove-snap-files",
   663  			path:  filepath.Join(dirs.SnapMountDir, "some-snap/7"),
   664  			stype: "app",
   665  		},
   666  		{
   667  			op:   "remove-snap-data",
   668  			path: filepath.Join(dirs.SnapMountDir, "some-snap/3"),
   669  		},
   670  		{
   671  			op:    "remove-snap-files",
   672  			path:  filepath.Join(dirs.SnapMountDir, "some-snap/3"),
   673  			stype: "app",
   674  		},
   675  		{
   676  			op:   "remove-snap-data",
   677  			path: filepath.Join(dirs.SnapMountDir, "some-snap/5"),
   678  		},
   679  		{
   680  			op:   "remove-snap-common-data",
   681  			path: filepath.Join(dirs.SnapMountDir, "some-snap/5"),
   682  		},
   683  		{
   684  			op:   "remove-snap-data-dir",
   685  			name: "some-snap",
   686  			path: filepath.Join(dirs.SnapDataDir, "some-snap"),
   687  		},
   688  		{
   689  			op:    "remove-snap-files",
   690  			path:  filepath.Join(dirs.SnapMountDir, "some-snap/5"),
   691  			stype: "app",
   692  		},
   693  		{
   694  			op:   "discard-namespace",
   695  			name: "some-snap",
   696  		},
   697  		{
   698  			op:   "remove-snap-dir",
   699  			name: "some-snap",
   700  			path: filepath.Join(dirs.SnapMountDir, "some-snap"),
   701  		},
   702  	}
   703  	// start with an easier-to-read error if this fails:
   704  	c.Assert(s.fakeBackend.ops.Ops(), DeepEquals, expected.Ops())
   705  	c.Assert(s.fakeBackend.ops, DeepEquals, expected)
   706  
   707  	// verify snapSetup info
   708  	tasks := ts.Tasks()
   709  	revnos := []snap.Revision{{N: 7}, {N: 3}, {N: 5}}
   710  	whichRevno := 0
   711  	for _, t := range tasks {
   712  		if t.Kind() == "run-hook" {
   713  			continue
   714  		}
   715  		if t.Kind() == "save-snapshot" {
   716  			continue
   717  		}
   718  		snapsup, err := snapstate.TaskSnapSetup(t)
   719  		c.Assert(err, IsNil)
   720  
   721  		var expSnapSetup *snapstate.SnapSetup
   722  		switch t.Kind() {
   723  		case "discard-conns":
   724  			expSnapSetup = &snapstate.SnapSetup{
   725  				SideInfo: &snap.SideInfo{
   726  					SnapID:   "some-snap-id",
   727  					RealName: "some-snap",
   728  				},
   729  			}
   730  		case "clear-snap", "discard-snap":
   731  			expSnapSetup = &snapstate.SnapSetup{
   732  				SideInfo: &snap.SideInfo{
   733  					SnapID:   "some-snap-id",
   734  					RealName: "some-snap",
   735  					Revision: revnos[whichRevno],
   736  				},
   737  			}
   738  		default:
   739  			expSnapSetup = &snapstate.SnapSetup{
   740  				SideInfo: &snap.SideInfo{
   741  					SnapID:   "some-snap-id",
   742  					RealName: "some-snap",
   743  					Revision: snap.R(7),
   744  				},
   745  				Type:      snap.TypeApp,
   746  				PlugsOnly: true,
   747  			}
   748  
   749  		}
   750  
   751  		c.Check(snapsup, DeepEquals, expSnapSetup, Commentf(t.Kind()))
   752  
   753  		if t.Kind() == "discard-snap" {
   754  			whichRevno++
   755  		}
   756  	}
   757  
   758  	// verify snaps in the system state
   759  	var snapst snapstate.SnapState
   760  	err = snapstate.Get(s.state, "some-snap", &snapst)
   761  	c.Assert(err, Equals, state.ErrNoState)
   762  }
   763  
   764  func (s *snapmgrTestSuite) TestRemoveOneRevisionRunThrough(c *C) {
   765  	si3 := snap.SideInfo{
   766  		RealName: "some-snap",
   767  		Revision: snap.R(3),
   768  	}
   769  
   770  	si5 := snap.SideInfo{
   771  		RealName: "some-snap",
   772  		Revision: snap.R(5),
   773  	}
   774  
   775  	si7 := snap.SideInfo{
   776  		RealName: "some-snap",
   777  		Revision: snap.R(7),
   778  	}
   779  
   780  	s.state.Lock()
   781  	defer s.state.Unlock()
   782  
   783  	snapstate.Set(s.state, "some-snap", &snapstate.SnapState{
   784  		Active:   true,
   785  		Sequence: []*snap.SideInfo{&si5, &si3, &si7},
   786  		Current:  si7.Revision,
   787  		SnapType: "app",
   788  	})
   789  
   790  	chg := s.state.NewChange("remove", "remove a snap")
   791  	ts, err := snapstate.Remove(s.state, "some-snap", snap.R(3), nil)
   792  	c.Assert(err, IsNil)
   793  	chg.AddAll(ts)
   794  
   795  	s.state.Unlock()
   796  	defer s.se.Stop()
   797  	s.settle(c)
   798  	s.state.Lock()
   799  
   800  	c.Check(len(s.fakeBackend.ops), Equals, 2)
   801  	expected := fakeOps{
   802  		{
   803  			op:   "remove-snap-data",
   804  			path: filepath.Join(dirs.SnapMountDir, "some-snap/3"),
   805  		},
   806  		{
   807  			op:    "remove-snap-files",
   808  			path:  filepath.Join(dirs.SnapMountDir, "some-snap/3"),
   809  			stype: "app",
   810  		},
   811  	}
   812  	// start with an easier-to-read error if this fails:
   813  	c.Assert(s.fakeBackend.ops.Ops(), DeepEquals, expected.Ops())
   814  	c.Assert(s.fakeBackend.ops, DeepEquals, expected)
   815  
   816  	// verify snapSetup info
   817  	tasks := ts.Tasks()
   818  	for _, t := range tasks {
   819  		if t.Kind() == "save-snapshot" {
   820  			continue
   821  		}
   822  		snapsup, err := snapstate.TaskSnapSetup(t)
   823  		c.Assert(err, IsNil)
   824  
   825  		expSnapSetup := &snapstate.SnapSetup{
   826  			SideInfo: &snap.SideInfo{
   827  				RealName: "some-snap",
   828  				Revision: snap.R(3),
   829  			},
   830  		}
   831  
   832  		c.Check(snapsup, DeepEquals, expSnapSetup, Commentf(t.Kind()))
   833  	}
   834  
   835  	// verify snaps in the system state
   836  	var snapst snapstate.SnapState
   837  	err = snapstate.Get(s.state, "some-snap", &snapst)
   838  	c.Assert(err, IsNil)
   839  	c.Check(snapst.Sequence, HasLen, 2)
   840  }
   841  
   842  func (s *snapmgrTestSuite) TestRemoveLastRevisionRunThrough(c *C) {
   843  	si := snap.SideInfo{
   844  		RealName: "some-snap",
   845  		Revision: snap.R(2),
   846  	}
   847  
   848  	s.state.Lock()
   849  	defer s.state.Unlock()
   850  
   851  	snapstate.Set(s.state, "some-snap", &snapstate.SnapState{
   852  		Active:   false,
   853  		Sequence: []*snap.SideInfo{&si},
   854  		Current:  si.Revision,
   855  		SnapType: "app",
   856  	})
   857  
   858  	chg := s.state.NewChange("remove", "remove a snap")
   859  	ts, err := snapstate.Remove(s.state, "some-snap", snap.R(2), nil)
   860  	c.Assert(err, IsNil)
   861  	chg.AddAll(ts)
   862  
   863  	s.state.Unlock()
   864  	defer s.se.Stop()
   865  	s.settle(c)
   866  	s.state.Lock()
   867  
   868  	c.Check(len(s.fakeBackend.ops), Equals, 7)
   869  	expected := fakeOps{
   870  		{
   871  			op:    "auto-disconnect:Doing",
   872  			name:  "some-snap",
   873  			revno: snap.R(2),
   874  		},
   875  		{
   876  			op:   "remove-snap-data",
   877  			path: filepath.Join(dirs.SnapMountDir, "some-snap/2"),
   878  		},
   879  		{
   880  			op:   "remove-snap-common-data",
   881  			path: filepath.Join(dirs.SnapMountDir, "some-snap/2"),
   882  		},
   883  		{
   884  			op:   "remove-snap-data-dir",
   885  			name: "some-snap",
   886  			path: filepath.Join(dirs.SnapDataDir, "some-snap"),
   887  		},
   888  		{
   889  			op:    "remove-snap-files",
   890  			path:  filepath.Join(dirs.SnapMountDir, "some-snap/2"),
   891  			stype: "app",
   892  		},
   893  		{
   894  			op:   "discard-namespace",
   895  			name: "some-snap",
   896  		},
   897  		{
   898  			op:   "remove-snap-dir",
   899  			name: "some-snap",
   900  			path: filepath.Join(dirs.SnapMountDir, "some-snap"),
   901  		},
   902  	}
   903  	// start with an easier-to-read error if this fails:
   904  	c.Assert(s.fakeBackend.ops.Ops(), DeepEquals, expected.Ops())
   905  	c.Assert(s.fakeBackend.ops, DeepEquals, expected)
   906  
   907  	// verify snapSetup info
   908  	tasks := ts.Tasks()
   909  	for _, t := range tasks {
   910  		if t.Kind() == "run-hook" {
   911  			continue
   912  		}
   913  		if t.Kind() == "save-snapshot" {
   914  			continue
   915  		}
   916  		snapsup, err := snapstate.TaskSnapSetup(t)
   917  		c.Assert(err, IsNil)
   918  
   919  		expSnapSetup := &snapstate.SnapSetup{
   920  			SideInfo: &snap.SideInfo{
   921  				RealName: "some-snap",
   922  			},
   923  		}
   924  		if t.Kind() != "discard-conns" {
   925  			expSnapSetup.SideInfo.Revision = snap.R(2)
   926  		}
   927  		if t.Kind() == "auto-disconnect" {
   928  			expSnapSetup.PlugsOnly = true
   929  			expSnapSetup.Type = "app"
   930  		}
   931  
   932  		c.Check(snapsup, DeepEquals, expSnapSetup, Commentf(t.Kind()))
   933  	}
   934  
   935  	// verify snaps in the system state
   936  	var snapst snapstate.SnapState
   937  	err = snapstate.Get(s.state, "some-snap", &snapst)
   938  	c.Assert(err, Equals, state.ErrNoState)
   939  }
   940  
   941  func (s *snapmgrTestSuite) TestRemoveCurrentActiveRevisionRefused(c *C) {
   942  	si := snap.SideInfo{
   943  		RealName: "some-snap",
   944  		Revision: snap.R(2),
   945  	}
   946  
   947  	s.state.Lock()
   948  	defer s.state.Unlock()
   949  
   950  	snapstate.Set(s.state, "some-snap", &snapstate.SnapState{
   951  		Active:   true,
   952  		Sequence: []*snap.SideInfo{&si},
   953  		Current:  si.Revision,
   954  		SnapType: "app",
   955  	})
   956  
   957  	_, err := snapstate.Remove(s.state, "some-snap", snap.R(2), nil)
   958  
   959  	c.Check(err, ErrorMatches, `cannot remove active revision 2 of snap "some-snap"`)
   960  }
   961  
   962  func (s *snapmgrTestSuite) TestRemoveCurrentRevisionOfSeveralRefused(c *C) {
   963  	si := snap.SideInfo{
   964  		RealName: "some-snap",
   965  		Revision: snap.R(2),
   966  	}
   967  
   968  	s.state.Lock()
   969  	defer s.state.Unlock()
   970  
   971  	snapstate.Set(s.state, "some-snap", &snapstate.SnapState{
   972  		Active:   true,
   973  		Sequence: []*snap.SideInfo{&si, &si},
   974  		Current:  si.Revision,
   975  		SnapType: "app",
   976  	})
   977  
   978  	_, err := snapstate.Remove(s.state, "some-snap", snap.R(2), nil)
   979  	c.Assert(err, NotNil)
   980  	c.Check(err.Error(), Equals, `cannot remove active revision 2 of snap "some-snap" (revert first?)`)
   981  }
   982  
   983  func (s *snapmgrTestSuite) TestRemoveMissingRevisionRefused(c *C) {
   984  	si := snap.SideInfo{
   985  		RealName: "some-snap",
   986  		Revision: snap.R(2),
   987  	}
   988  
   989  	s.state.Lock()
   990  	defer s.state.Unlock()
   991  
   992  	snapstate.Set(s.state, "some-snap", &snapstate.SnapState{
   993  		Active:   true,
   994  		Sequence: []*snap.SideInfo{&si},
   995  		Current:  si.Revision,
   996  		SnapType: "app",
   997  	})
   998  
   999  	_, err := snapstate.Remove(s.state, "some-snap", snap.R(1), nil)
  1000  
  1001  	c.Check(err, ErrorMatches, `revision 1 of snap "some-snap" is not installed`)
  1002  }
  1003  
  1004  func (s *snapmgrTestSuite) TestRemoveRefused(c *C) {
  1005  	si := snap.SideInfo{
  1006  		RealName: "brand-gadget",
  1007  		Revision: snap.R(7),
  1008  	}
  1009  
  1010  	s.state.Lock()
  1011  	defer s.state.Unlock()
  1012  
  1013  	snapstate.Set(s.state, "brand-gadget", &snapstate.SnapState{
  1014  		Active:   true,
  1015  		Sequence: []*snap.SideInfo{&si},
  1016  		Current:  si.Revision,
  1017  		SnapType: "gadget",
  1018  	})
  1019  
  1020  	_, err := snapstate.Remove(s.state, "brand-gadget", snap.R(0), nil)
  1021  
  1022  	c.Check(err, ErrorMatches, `snap "brand-gadget" is not removable: snap is used by the model`)
  1023  }
  1024  
  1025  func (s *snapmgrTestSuite) TestRemoveRefusedLastRevision(c *C) {
  1026  	si := snap.SideInfo{
  1027  		RealName: "brand-gadget",
  1028  		Revision: snap.R(7),
  1029  	}
  1030  
  1031  	s.state.Lock()
  1032  	defer s.state.Unlock()
  1033  
  1034  	snapstate.Set(s.state, "brand-gadget", &snapstate.SnapState{
  1035  		Active:   false,
  1036  		Sequence: []*snap.SideInfo{&si},
  1037  		Current:  si.Revision,
  1038  		SnapType: "gadget",
  1039  	})
  1040  
  1041  	_, err := snapstate.Remove(s.state, "brand-gadget", snap.R(7), nil)
  1042  
  1043  	c.Check(err, ErrorMatches, `snap "brand-gadget" is not removable: snap is used by the model`)
  1044  }
  1045  
  1046  func (s *snapmgrTestSuite) TestRemoveDeletesConfigOnLastRevision(c *C) {
  1047  	si := snap.SideInfo{
  1048  		RealName: "some-snap",
  1049  		Revision: snap.R(7),
  1050  	}
  1051  
  1052  	s.state.Lock()
  1053  	defer s.state.Unlock()
  1054  
  1055  	snapstate.Set(s.state, "some-snap", &snapstate.SnapState{
  1056  		Active:   true,
  1057  		Sequence: []*snap.SideInfo{&si},
  1058  		Current:  si.Revision,
  1059  		SnapType: "app",
  1060  	})
  1061  
  1062  	snapstate.Set(s.state, "another-snap", &snapstate.SnapState{
  1063  		Active:   true,
  1064  		Sequence: []*snap.SideInfo{&si},
  1065  		Current:  si.Revision,
  1066  		SnapType: "app",
  1067  	})
  1068  
  1069  	tr := config.NewTransaction(s.state)
  1070  	tr.Set("some-snap", "foo", "bar")
  1071  	tr.Commit()
  1072  
  1073  	// a config for some other snap to verify its not accidentally destroyed
  1074  	tr = config.NewTransaction(s.state)
  1075  	tr.Set("another-snap", "bar", "baz")
  1076  	tr.Commit()
  1077  
  1078  	var res string
  1079  	tr = config.NewTransaction(s.state)
  1080  	c.Assert(tr.Get("some-snap", "foo", &res), IsNil)
  1081  	c.Assert(tr.Get("another-snap", "bar", &res), IsNil)
  1082  
  1083  	chg := s.state.NewChange("remove", "remove a snap")
  1084  	ts, err := snapstate.Remove(s.state, "some-snap", snap.R(0), nil)
  1085  	c.Assert(err, IsNil)
  1086  	chg.AddAll(ts)
  1087  
  1088  	s.state.Unlock()
  1089  	defer s.se.Stop()
  1090  	s.settle(c)
  1091  	s.state.Lock()
  1092  
  1093  	// verify snaps in the system state
  1094  	var snapst snapstate.SnapState
  1095  	err = snapstate.Get(s.state, "some-snap", &snapst)
  1096  	c.Assert(err, Equals, state.ErrNoState)
  1097  
  1098  	tr = config.NewTransaction(s.state)
  1099  	err = tr.Get("some-snap", "foo", &res)
  1100  	c.Assert(err, NotNil)
  1101  	c.Assert(err, ErrorMatches, `snap "some-snap" has no "foo" configuration option`)
  1102  
  1103  	// and another snap has its config intact
  1104  	c.Assert(tr.Get("another-snap", "bar", &res), IsNil)
  1105  	c.Assert(res, Equals, "baz")
  1106  }
  1107  
  1108  func (s *snapmgrTestSuite) TestRemoveDoesntDeleteConfigIfNotLastRevision(c *C) {
  1109  	si1 := snap.SideInfo{
  1110  		RealName: "some-snap",
  1111  		Revision: snap.R(7),
  1112  	}
  1113  	si2 := snap.SideInfo{
  1114  		RealName: "some-snap",
  1115  		Revision: snap.R(8),
  1116  	}
  1117  
  1118  	s.state.Lock()
  1119  	defer s.state.Unlock()
  1120  
  1121  	snapstate.Set(s.state, "some-snap", &snapstate.SnapState{
  1122  		Active:   true,
  1123  		Sequence: []*snap.SideInfo{&si1, &si2},
  1124  		Current:  si2.Revision,
  1125  		SnapType: "app",
  1126  	})
  1127  
  1128  	tr := config.NewTransaction(s.state)
  1129  	tr.Set("some-snap", "foo", "bar")
  1130  	tr.Commit()
  1131  
  1132  	var res string
  1133  	tr = config.NewTransaction(s.state)
  1134  	c.Assert(tr.Get("some-snap", "foo", &res), IsNil)
  1135  
  1136  	chg := s.state.NewChange("remove", "remove a snap")
  1137  	ts, err := snapstate.Remove(s.state, "some-snap", si1.Revision, nil)
  1138  	c.Assert(err, IsNil)
  1139  	chg.AddAll(ts)
  1140  
  1141  	s.state.Unlock()
  1142  	defer s.se.Stop()
  1143  	s.settle(c)
  1144  	s.state.Lock()
  1145  
  1146  	// verify snaps in the system state
  1147  	var snapst snapstate.SnapState
  1148  	err = snapstate.Get(s.state, "some-snap", &snapst)
  1149  	c.Assert(err, IsNil)
  1150  
  1151  	tr = config.NewTransaction(s.state)
  1152  	c.Assert(tr.Get("some-snap", "foo", &res), IsNil)
  1153  	c.Assert(res, Equals, "bar")
  1154  }
  1155  
  1156  func (s *snapmgrTestSuite) TestRemoveMany(c *C) {
  1157  	s.state.Lock()
  1158  	defer s.state.Unlock()
  1159  
  1160  	snapstate.Set(s.state, "one", &snapstate.SnapState{
  1161  		Active: true,
  1162  		Sequence: []*snap.SideInfo{
  1163  			{RealName: "one", SnapID: "one-id", Revision: snap.R(1)},
  1164  		},
  1165  		Current: snap.R(1),
  1166  	})
  1167  	snapstate.Set(s.state, "two", &snapstate.SnapState{
  1168  		Active: true,
  1169  		Sequence: []*snap.SideInfo{
  1170  			{RealName: "two", SnapID: "two-id", Revision: snap.R(1)},
  1171  		},
  1172  		Current: snap.R(1),
  1173  	})
  1174  
  1175  	removed, tts, err := snapstate.RemoveMany(s.state, []string{"one", "two"})
  1176  	c.Assert(err, IsNil)
  1177  	c.Assert(tts, HasLen, 2)
  1178  	c.Check(removed, DeepEquals, []string{"one", "two"})
  1179  
  1180  	c.Assert(s.state.TaskCount(), Equals, 8*2)
  1181  	for i, ts := range tts {
  1182  		c.Assert(taskKinds(ts.Tasks()), DeepEquals, []string{
  1183  			"stop-snap-services",
  1184  			"run-hook[remove]",
  1185  			"auto-disconnect",
  1186  			"remove-aliases",
  1187  			"unlink-snap",
  1188  			"remove-profiles",
  1189  			"clear-snap",
  1190  			"discard-snap",
  1191  		})
  1192  		verifyStopReason(c, ts, "remove")
  1193  		// check that tasksets are in separate lanes
  1194  		for _, t := range ts.Tasks() {
  1195  			c.Assert(t.Lanes(), DeepEquals, []int{i + 1})
  1196  		}
  1197  
  1198  	}
  1199  }
  1200  
  1201  func (s *snapmgrTestSuite) testRemoveManyDiskSpaceCheck(c *C, featureFlag, automaticSnapshot, freeSpaceCheckFail bool) error {
  1202  	s.state.Lock()
  1203  	defer s.state.Unlock()
  1204  
  1205  	var checkFreeSpaceCall, snapshotSizeCall int
  1206  
  1207  	// restored by TearDownTest
  1208  	snapstate.EstimateSnapshotSize = func(st *state.State, instanceName string, users []string) (uint64, error) {
  1209  		snapshotSizeCall++
  1210  		// expect two snapshot size estimations
  1211  		switch instanceName {
  1212  		case "one":
  1213  			return 10, nil
  1214  		case "two":
  1215  			return 20, nil
  1216  		default:
  1217  			c.Fatalf("unexpected snap: %s", instanceName)
  1218  		}
  1219  		return 1, nil
  1220  	}
  1221  
  1222  	restore := snapstate.MockOsutilCheckFreeSpace(func(path string, required uint64) error {
  1223  		checkFreeSpaceCall++
  1224  		// required size is the sum of snapshot sizes of test snaps
  1225  		c.Check(required, Equals, snapstate.SafetyMarginDiskSpace(30))
  1226  		if freeSpaceCheckFail {
  1227  			return &osutil.NotEnoughDiskSpaceError{}
  1228  		}
  1229  		return nil
  1230  	})
  1231  	defer restore()
  1232  
  1233  	var automaticSnapshotCalled bool
  1234  	snapstate.AutomaticSnapshot = func(st *state.State, instanceName string) (ts *state.TaskSet, err error) {
  1235  		automaticSnapshotCalled = true
  1236  		if automaticSnapshot {
  1237  			t := s.state.NewTask("foo", "")
  1238  			ts = state.NewTaskSet(t)
  1239  			return ts, nil
  1240  		}
  1241  		// ErrNothingToDo is returned if automatic snapshots are disabled
  1242  		return nil, snapstate.ErrNothingToDo
  1243  	}
  1244  
  1245  	tr := config.NewTransaction(s.state)
  1246  	tr.Set("core", "experimental.check-disk-space-remove", featureFlag)
  1247  	tr.Commit()
  1248  
  1249  	snapstate.Set(s.state, "one", &snapstate.SnapState{
  1250  		Active:   true,
  1251  		SnapType: "app",
  1252  		Sequence: []*snap.SideInfo{
  1253  			{RealName: "one", SnapID: "one-id", Revision: snap.R(1)},
  1254  		},
  1255  		Current: snap.R(1),
  1256  	})
  1257  	snapstate.Set(s.state, "two", &snapstate.SnapState{
  1258  		Active:   true,
  1259  		SnapType: "app",
  1260  		Sequence: []*snap.SideInfo{
  1261  			{RealName: "two", SnapID: "two-id", Revision: snap.R(1)},
  1262  		},
  1263  		Current: snap.R(1),
  1264  	})
  1265  
  1266  	_, _, err := snapstate.RemoveMany(s.state, []string{"one", "two"})
  1267  	if featureFlag && automaticSnapshot {
  1268  		c.Check(snapshotSizeCall, Equals, 2)
  1269  		c.Check(checkFreeSpaceCall, Equals, 1)
  1270  	} else {
  1271  		c.Check(checkFreeSpaceCall, Equals, 0)
  1272  		c.Check(snapshotSizeCall, Equals, 0)
  1273  	}
  1274  	c.Check(automaticSnapshotCalled, Equals, true)
  1275  
  1276  	return err
  1277  }
  1278  
  1279  func (s *snapmgrTestSuite) TestRemoveManyDiskSpaceError(c *C) {
  1280  	featureFlag := true
  1281  	automaticSnapshot := true
  1282  	freeSpaceCheckFail := true
  1283  	err := s.testRemoveManyDiskSpaceCheck(c, featureFlag, automaticSnapshot, freeSpaceCheckFail)
  1284  
  1285  	diskSpaceErr := err.(*snapstate.InsufficientSpaceError)
  1286  	c.Check(diskSpaceErr.Path, Equals, filepath.Join(dirs.GlobalRootDir, "/var/lib/snapd"))
  1287  	c.Check(diskSpaceErr.Snaps, DeepEquals, []string{"one", "two"})
  1288  	c.Check(diskSpaceErr.ChangeKind, Equals, "remove")
  1289  }
  1290  
  1291  func (s *snapmgrTestSuite) TestRemoveManyDiskSpaceCheckDisabled(c *C) {
  1292  	featureFlag := false
  1293  	automaticSnapshot := true
  1294  	freeSpaceCheckFail := true
  1295  	err := s.testRemoveManyDiskSpaceCheck(c, featureFlag, automaticSnapshot, freeSpaceCheckFail)
  1296  	c.Assert(err, IsNil)
  1297  }
  1298  
  1299  func (s *snapmgrTestSuite) TestRemoveManyDiskSpaceSnapshotDisabled(c *C) {
  1300  	featureFlag := true
  1301  	automaticSnapshot := false
  1302  	freeSpaceCheckFail := true
  1303  	err := s.testRemoveManyDiskSpaceCheck(c, featureFlag, automaticSnapshot, freeSpaceCheckFail)
  1304  	c.Assert(err, IsNil)
  1305  }
  1306  
  1307  func (s *snapmgrTestSuite) TestRemoveManyDiskSpaceCheckPasses(c *C) {
  1308  	featureFlag := true
  1309  	automaticSnapshot := true
  1310  	freeSpaceCheckFail := false
  1311  	err := s.testRemoveManyDiskSpaceCheck(c, featureFlag, automaticSnapshot, freeSpaceCheckFail)
  1312  	c.Check(err, IsNil)
  1313  }