mirror of
https://github.com/token2/snapd.git
synced 2026-03-13 11:15:47 -07:00
398 lines
9.2 KiB
Go
398 lines
9.2 KiB
Go
// -*- 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 <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
package patch_test
|
|
|
|
import (
|
|
"fmt"
|
|
"sort"
|
|
"testing"
|
|
|
|
. "gopkg.in/check.v1"
|
|
|
|
"github.com/snapcore/snapd/overlord/patch"
|
|
"github.com/snapcore/snapd/overlord/state"
|
|
"github.com/snapcore/snapd/snap"
|
|
"github.com/snapcore/snapd/snapdtool"
|
|
"github.com/snapcore/snapd/testutil"
|
|
)
|
|
|
|
func Test(t *testing.T) { TestingT(t) }
|
|
|
|
type patchSuite struct {
|
|
restoreSanitize func()
|
|
}
|
|
|
|
var _ = Suite(&patchSuite{})
|
|
|
|
func (s *patchSuite) SetUpTest(c *C) {
|
|
s.restoreSanitize = snap.MockSanitizePlugsSlots(func(snapInfo *snap.Info) {})
|
|
}
|
|
|
|
func (s *patchSuite) TearDownTest(c *C) {
|
|
s.restoreSanitize()
|
|
}
|
|
|
|
func (s *patchSuite) TestInit(c *C) {
|
|
restore := patch.Mock(2, 1, nil)
|
|
defer restore()
|
|
|
|
st := state.New(nil)
|
|
patch.Init(st)
|
|
|
|
st.Lock()
|
|
defer st.Unlock()
|
|
var patchLevel int
|
|
err := st.Get("patch-level", &patchLevel)
|
|
c.Assert(err, IsNil)
|
|
c.Check(patchLevel, Equals, 2)
|
|
|
|
var patchSublevel int
|
|
c.Assert(st.Get("patch-sublevel", &patchSublevel), IsNil)
|
|
c.Check(patchSublevel, Equals, 1)
|
|
}
|
|
|
|
func (s *patchSuite) TestNothingToDo(c *C) {
|
|
restore := patch.Mock(2, 1, nil)
|
|
defer restore()
|
|
|
|
st := state.New(nil)
|
|
st.Lock()
|
|
st.Set("patch-level", 2)
|
|
st.Unlock()
|
|
err := patch.Apply(st)
|
|
c.Assert(err, IsNil)
|
|
}
|
|
|
|
func (s *patchSuite) TestNoDowngrade(c *C) {
|
|
restore := patch.Mock(2, 0, nil)
|
|
defer restore()
|
|
|
|
st := state.New(nil)
|
|
st.Lock()
|
|
st.Set("patch-level", 3)
|
|
st.Unlock()
|
|
err := patch.Apply(st)
|
|
c.Assert(err, ErrorMatches, `cannot downgrade: snapd is too old for the current system state \(patch level 3\)`)
|
|
}
|
|
|
|
func (s *patchSuite) TestApply(c *C) {
|
|
p12 := func(st *state.State) error {
|
|
var n int
|
|
st.Get("n", &n)
|
|
st.Set("n", n+1)
|
|
return nil
|
|
}
|
|
p121 := func(st *state.State) error {
|
|
var o int
|
|
st.Get("o", &o)
|
|
st.Set("o", o+1)
|
|
return nil
|
|
}
|
|
p23 := func(st *state.State) error {
|
|
var n int
|
|
st.Get("n", &n)
|
|
st.Set("n", n*10)
|
|
return nil
|
|
}
|
|
|
|
// patch level 3, sublevel 1
|
|
restore := patch.Mock(3, 1, map[int][]patch.PatchFunc{
|
|
2: {p12, p121},
|
|
3: {p23},
|
|
})
|
|
defer restore()
|
|
|
|
st := state.New(nil)
|
|
st.Lock()
|
|
st.Set("patch-level", 1)
|
|
st.Unlock()
|
|
err := patch.Apply(st)
|
|
c.Assert(err, IsNil)
|
|
|
|
st.Lock()
|
|
defer st.Unlock()
|
|
|
|
var level int
|
|
err = st.Get("patch-level", &level)
|
|
c.Assert(err, IsNil)
|
|
c.Check(level, Equals, 3)
|
|
|
|
var sublevel int
|
|
c.Assert(st.Get("patch-sublevel", &sublevel), IsNil)
|
|
c.Check(sublevel, Equals, 0)
|
|
|
|
var n, o int
|
|
err = st.Get("n", &n)
|
|
c.Assert(err, IsNil)
|
|
c.Check(n, Equals, 10)
|
|
|
|
c.Assert(st.Get("o", &o), IsNil)
|
|
c.Assert(o, Equals, 1)
|
|
}
|
|
|
|
func (s *patchSuite) TestApplyLevel6(c *C) {
|
|
var sequence []int
|
|
p50 := generatePatchFunc(50, &sequence)
|
|
p60 := generatePatchFunc(60, &sequence)
|
|
p61 := generatePatchFunc(61, &sequence)
|
|
|
|
restore := patch.Mock(6, 1, map[int][]patch.PatchFunc{
|
|
5: {p50},
|
|
6: {p60, p61},
|
|
})
|
|
defer restore()
|
|
|
|
// simulate the special case where sublevel is introduced for system that's already on patch level 6.
|
|
// only p61 patch should be applied.
|
|
st := state.New(nil)
|
|
st.Lock()
|
|
st.Set("patch-level", 6)
|
|
st.Unlock()
|
|
c.Assert(patch.Apply(st), IsNil)
|
|
|
|
st.Lock()
|
|
defer st.Unlock()
|
|
|
|
var level, sublevel int
|
|
c.Assert(sequence, DeepEquals, []int{61})
|
|
c.Assert(st.Get("patch-level", &level), IsNil)
|
|
c.Assert(st.Get("patch-sublevel", &sublevel), IsNil)
|
|
c.Check(level, Equals, 6)
|
|
c.Check(sublevel, Equals, 1)
|
|
}
|
|
|
|
func (s *patchSuite) TestApplyFromSublevel(c *C) {
|
|
var sequence []int
|
|
p60 := generatePatchFunc(60, &sequence)
|
|
p61 := generatePatchFunc(61, &sequence)
|
|
p62 := generatePatchFunc(62, &sequence)
|
|
p70 := generatePatchFunc(70, &sequence)
|
|
p71 := generatePatchFunc(71, &sequence)
|
|
|
|
restore := patch.Mock(7, 1, map[int][]patch.PatchFunc{
|
|
6: {p60, p61, p62},
|
|
7: {p70, p71},
|
|
})
|
|
defer restore()
|
|
|
|
// we'll be patching from 6.0 -> 7.1
|
|
st := state.New(nil)
|
|
st.Lock()
|
|
st.Set("patch-level", 6)
|
|
st.Set("patch-sublevel", 0)
|
|
st.Unlock()
|
|
c.Assert(patch.Apply(st), IsNil)
|
|
|
|
st.Lock()
|
|
|
|
var level, sublevel int
|
|
c.Assert(st.Get("patch-level", &level), IsNil)
|
|
c.Assert(st.Get("patch-sublevel", &sublevel), IsNil)
|
|
c.Check(level, Equals, 7)
|
|
c.Check(sublevel, Equals, 1)
|
|
c.Assert(sequence, DeepEquals, []int{61, 62, 70, 71})
|
|
|
|
// now patching from 7.1 -> 7.2
|
|
sequence = []int{}
|
|
p72 := generatePatchFunc(72, &sequence)
|
|
patch.Mock(7, 2, map[int][]patch.PatchFunc{
|
|
6: {p60, p61, p62},
|
|
7: {p70, p71, p72},
|
|
})
|
|
|
|
st.Unlock()
|
|
c.Assert(patch.Apply(st), IsNil)
|
|
c.Assert(sequence, DeepEquals, []int{72})
|
|
|
|
st.Lock()
|
|
defer st.Unlock()
|
|
|
|
c.Assert(st.Get("patch-level", &level), IsNil)
|
|
c.Assert(st.Get("patch-sublevel", &sublevel), IsNil)
|
|
c.Check(level, Equals, 7)
|
|
c.Check(sublevel, Equals, 2)
|
|
}
|
|
|
|
func (s *patchSuite) TestMissing(c *C) {
|
|
restore := patch.Mock(3, 0, map[int][]patch.PatchFunc{
|
|
3: {func(s *state.State) error { return nil }},
|
|
})
|
|
defer restore()
|
|
|
|
st := state.New(nil)
|
|
st.Lock()
|
|
st.Set("patch-level", 1)
|
|
st.Unlock()
|
|
err := patch.Apply(st)
|
|
c.Assert(err, ErrorMatches, `cannot upgrade: snapd is too new for the current system state \(patch level 1\)`)
|
|
}
|
|
|
|
func (s *patchSuite) TestDowngradeSublevel(c *C) {
|
|
restore := patch.Mock(3, 1, map[int][]patch.PatchFunc{
|
|
3: {func(s *state.State) error { return nil }},
|
|
})
|
|
defer restore()
|
|
|
|
st := state.New(nil)
|
|
st.Lock()
|
|
st.Set("patch-level", 3)
|
|
st.Set("patch-sublevel", 6)
|
|
st.Unlock()
|
|
|
|
// we're at patch level 3, sublevel 6 according to state, but the implemented level is 3,1
|
|
c.Assert(patch.Apply(st), IsNil)
|
|
|
|
st.Lock()
|
|
defer st.Unlock()
|
|
var level, sublevel int
|
|
c.Assert(st.Get("patch-level", &level), IsNil)
|
|
c.Assert(st.Get("patch-sublevel", &sublevel), IsNil)
|
|
c.Check(level, Equals, 3)
|
|
c.Check(sublevel, Equals, 1)
|
|
}
|
|
|
|
func (s *patchSuite) TestError(c *C) {
|
|
p12 := func(st *state.State) error {
|
|
var n int
|
|
st.Get("n", &n)
|
|
st.Set("n", n+1)
|
|
return nil
|
|
}
|
|
p23 := func(st *state.State) error {
|
|
var n int
|
|
st.Get("n", &n)
|
|
st.Set("n", n*10)
|
|
return fmt.Errorf("boom")
|
|
}
|
|
p34 := func(st *state.State) error {
|
|
var n int
|
|
st.Get("n", &n)
|
|
st.Set("n", n*100)
|
|
return nil
|
|
}
|
|
restore := patch.Mock(3, 0, map[int][]patch.PatchFunc{
|
|
2: {p12},
|
|
3: {p23},
|
|
4: {p34},
|
|
})
|
|
defer restore()
|
|
|
|
st := state.New(nil)
|
|
st.Lock()
|
|
st.Set("patch-level", 1)
|
|
st.Unlock()
|
|
err := patch.Apply(st)
|
|
c.Assert(err, ErrorMatches, `cannot patch system state to level 3, sublevel 0: boom`)
|
|
|
|
st.Lock()
|
|
defer st.Unlock()
|
|
|
|
var level int
|
|
err = st.Get("patch-level", &level)
|
|
c.Assert(err, IsNil)
|
|
c.Check(level, Equals, 2)
|
|
|
|
var n int
|
|
err = st.Get("n", &n)
|
|
c.Assert(err, IsNil)
|
|
c.Check(n, Equals, 10)
|
|
}
|
|
|
|
func (s *patchSuite) testMaybeResetPatchLevel6(c *C, snapdVersion, lastVersion string, expectedPatches []int) {
|
|
var sequence []int
|
|
|
|
snapdtool.MockVersion(snapdVersion)
|
|
|
|
p60 := generatePatchFunc(60, &sequence)
|
|
p61 := generatePatchFunc(61, &sequence)
|
|
p62 := generatePatchFunc(62, &sequence)
|
|
|
|
restore := patch.Mock(6, 2, map[int][]patch.PatchFunc{
|
|
6: {p60, p61, p62},
|
|
})
|
|
|
|
defer restore()
|
|
|
|
st := state.New(nil)
|
|
st.Lock()
|
|
|
|
if lastVersion != "" {
|
|
st.Set("patch-sublevel-last-version", lastVersion)
|
|
}
|
|
st.Set("patch-level", 6)
|
|
st.Set("patch-sublevel", 2)
|
|
|
|
st.Unlock()
|
|
|
|
c.Assert(patch.Apply(st), IsNil)
|
|
c.Assert(sequence, DeepEquals, expectedPatches)
|
|
|
|
st.Lock()
|
|
defer st.Unlock()
|
|
var level, sublevel int
|
|
var ver string
|
|
var lastRefresh interface{}
|
|
c.Assert(st.Get("patch-level", &level), IsNil)
|
|
c.Assert(st.Get("patch-sublevel", &sublevel), IsNil)
|
|
c.Assert(st.Get("patch-sublevel-last-version", &ver), IsNil)
|
|
c.Assert(st.Get("patch-sublevel-reset", &lastRefresh), testutil.ErrorIs, state.ErrNoState)
|
|
c.Check(ver, Equals, "snapd-version-1")
|
|
c.Check(level, Equals, 6)
|
|
c.Check(sublevel, Equals, 2)
|
|
}
|
|
|
|
func (s *patchSuite) TestSameSnapdVersionLvl60PatchesNotApplied(c *C) {
|
|
// sublevel patches not applied if snapd version is same
|
|
s.testMaybeResetPatchLevel6(c, "snapd-version-1", "snapd-version-1", nil)
|
|
}
|
|
|
|
func (s *patchSuite) TestDifferentSnapdVersionPatchLevel6NoLastVersion(c *C) {
|
|
s.testMaybeResetPatchLevel6(c, "snapd-version-1", "", []int{61, 62})
|
|
}
|
|
|
|
func (s *patchSuite) TestDifferentSnapdVersionPatchLevel6(c *C) {
|
|
s.testMaybeResetPatchLevel6(c, "snapd-version-1", "snapd-version-2", []int{61, 62})
|
|
}
|
|
|
|
func (s *patchSuite) TestValidity(c *C) {
|
|
patches := patch.PatchesForTest()
|
|
levels := make([]int, 0, len(patches))
|
|
for l := range patches {
|
|
levels = append(levels, l)
|
|
}
|
|
sort.Ints(levels)
|
|
// all steps present
|
|
for i, level := range levels {
|
|
c.Check(level, Equals, i+1)
|
|
}
|
|
// ends at implemented patch level
|
|
c.Check(levels[len(levels)-1], Equals, patch.Level)
|
|
|
|
// Sublevel matches the number of patches for last Level.
|
|
c.Check(len(patches[patch.Level])-1, Equals, patch.Sublevel)
|
|
}
|
|
|
|
func generatePatchFunc(testValue int, sequence *[]int) patch.PatchFunc {
|
|
return func(st *state.State) error {
|
|
*sequence = append(*sequence, testValue)
|
|
return nil
|
|
}
|
|
}
|