github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/snap/epoch_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2017 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 snap_test
    21  
    22  import (
    23  	"encoding/json"
    24  
    25  	"gopkg.in/check.v1"
    26  	"gopkg.in/yaml.v2"
    27  
    28  	"github.com/snapcore/snapd/snap"
    29  )
    30  
    31  type epochSuite struct{}
    32  
    33  var _ = check.Suite(&epochSuite{})
    34  
    35  var (
    36  	// some duplication here maybe
    37  	epochZeroStar                 = `0\* is an invalid epoch`
    38  	hugeEpochNumber               = `epoch numbers must be less than 2³², but got .*`
    39  	badEpochNumber                = `epoch numbers must be base 10 with no zero padding, but got .*`
    40  	badEpochList                  = "epoch read/write attributes must be lists of epoch numbers"
    41  	emptyEpochList                = "epoch list cannot be explicitly empty"
    42  	epochListNotIncreasing        = "epoch list must be a strictly increasing sequence"
    43  	epochListJustRidiculouslyLong = "epoch list must not have more than 10 entries"
    44  	noEpochIntersection           = "epoch read and write lists must have a non-empty intersection"
    45  )
    46  
    47  func (s epochSuite) TestBadEpochs(c *check.C) {
    48  	type Tt struct {
    49  		s string
    50  		e string
    51  		y int
    52  	}
    53  
    54  	tests := []Tt{
    55  		{s: `"rubbish"`, e: badEpochNumber},                        // SISO
    56  		{s: `0xA`, e: badEpochNumber, y: 1},                        // no hex
    57  		{s: `"0xA"`, e: badEpochNumber},                            //
    58  		{s: `001`, e: badEpochNumber, y: 1},                        // no octal, in fact no zero prefixes at all
    59  		{s: `"001"`, e: badEpochNumber},                            //
    60  		{s: `{"read": 5}`, e: badEpochList},                        // when split, must be list
    61  		{s: `{"write": 5}`, e: badEpochList},                       //
    62  		{s: `{"read": "5"}`, e: badEpochList},                      //
    63  		{s: `{"write": "5"}`, e: badEpochList},                     //
    64  		{s: `{"read": "1*"}`, e: badEpochList},                     // what
    65  		{s: `{"read": [-1]}`, e: badEpochNumber},                   // negative not allowed
    66  		{s: `{"write": [-1]}`, e: badEpochNumber},                  //
    67  		{s: `{"read": ["-1"]}`, e: badEpochNumber},                 //
    68  		{s: `{"write": ["-1"]}`, e: badEpochNumber},                //
    69  		{s: `{"read": ["yes"]}`, e: badEpochNumber},                // must be numbers
    70  		{s: `{"write": ["yes"]}`, e: badEpochNumber},               //
    71  		{s: `{"read": ["Ⅰ","Ⅱ"]}`, e: badEpochNumber},              // not roman numerals you idiot
    72  		{s: `{"read": [0xA]}`, e: badEpochNumber, y: 1},            //
    73  		{s: `{"read": [010]}`, e: badEpochNumber, y: 1},            //
    74  		{s: `{"read": [9999999999]}`, e: hugeEpochNumber},          // you done yet?
    75  		{s: `"0*"`, e: epochZeroStar},                              // 0* means nothing
    76  		{s: `"42**"`, e: badEpochNumber},                           // N** is dead
    77  		{s: `{"read": []}`, e: emptyEpochList},                     // explicitly empty is bad
    78  		{s: `{"write": []}`, e: emptyEpochList},                    //
    79  		{s: `{"read": [1,2,4,3]}`, e: epochListNotIncreasing},      // must be ordered
    80  		{s: `{"read": [1,2,2,3]}`, e: epochListNotIncreasing},      // must be strictly increasing
    81  		{s: `{"write": [4,3,2,1]}`, e: epochListNotIncreasing},     // ...*increasing*
    82  		{s: `{"read": [0], "write": [1]}`, e: noEpochIntersection}, // must have at least one in common
    83  		{s: `{"read": [0,1,2,3,4,5,6,7,8,9,10],
    84   "write": [0,1,2,3,4,5,6,7,8,9,10]}`, e: epochListJustRidiculouslyLong}, // must have <10 elements
    85  	}
    86  
    87  	for _, test := range tests {
    88  		var v snap.Epoch
    89  		err := yaml.Unmarshal([]byte(test.s), &v)
    90  		c.Check(err, check.ErrorMatches, test.e, check.Commentf("YAML: %#q", test.s))
    91  
    92  		if test.y == 1 {
    93  			continue
    94  		}
    95  		err = json.Unmarshal([]byte(test.s), &v)
    96  		c.Check(err, check.ErrorMatches, test.e, check.Commentf("JSON: %#q", test.s))
    97  	}
    98  }
    99  
   100  func (s epochSuite) TestGoodEpochs(c *check.C) {
   101  	type Tt struct {
   102  		s string
   103  		e snap.Epoch
   104  		y int
   105  	}
   106  
   107  	tests := []Tt{
   108  		{s: `0`, e: snap.Epoch{Read: []uint32{0}, Write: []uint32{0}}, y: 1},
   109  		{s: `""`, e: snap.Epoch{Read: []uint32{0}, Write: []uint32{0}}},
   110  		{s: `"0"`, e: snap.Epoch{Read: []uint32{0}, Write: []uint32{0}}},
   111  		{s: `{}`, e: snap.Epoch{Read: []uint32{0}, Write: []uint32{0}}},
   112  		{s: `"2*"`, e: snap.Epoch{Read: []uint32{1, 2}, Write: []uint32{2}}},
   113  		{s: `{"read": [2]}`, e: snap.Epoch{Read: []uint32{2}, Write: []uint32{2}}},
   114  		{s: `{"read": [1, 2]}`, e: snap.Epoch{Read: []uint32{1, 2}, Write: []uint32{2}}},
   115  		{s: `{"write": [2]}`, e: snap.Epoch{Read: []uint32{2}, Write: []uint32{2}}},
   116  		{s: `{"write": [1, 2]}`, e: snap.Epoch{Read: []uint32{1, 2}, Write: []uint32{1, 2}}},
   117  		{s: `{"read": [2,4,8], "write": [2,3,5]}`, e: snap.Epoch{Read: []uint32{2, 4, 8}, Write: []uint32{2, 3, 5}}},
   118  	}
   119  
   120  	for _, test := range tests {
   121  		var v snap.Epoch
   122  		err := yaml.Unmarshal([]byte(test.s), &v)
   123  		c.Check(err, check.IsNil, check.Commentf("YAML: %s", test.s))
   124  		c.Check(v, check.DeepEquals, test.e)
   125  
   126  		if test.y > 0 {
   127  			continue
   128  		}
   129  
   130  		err = json.Unmarshal([]byte(test.s), &v)
   131  		c.Check(err, check.IsNil, check.Commentf("JSON: %s", test.s))
   132  		c.Check(v, check.DeepEquals, test.e)
   133  	}
   134  }
   135  
   136  func (s epochSuite) TestGoodEpochsInSnapYAML(c *check.C) {
   137  	defer snap.MockSanitizePlugsSlots(func(snapInfo *snap.Info) {})()
   138  
   139  	type Tt struct {
   140  		s string
   141  		e snap.Epoch
   142  	}
   143  
   144  	tests := []Tt{
   145  		{s: ``, e: snap.Epoch{Read: []uint32{0}, Write: []uint32{0}}},
   146  		{s: `epoch: null`, e: snap.Epoch{Read: []uint32{0}, Write: []uint32{0}}},
   147  		{s: `epoch: 0`, e: snap.Epoch{Read: []uint32{0}, Write: []uint32{0}}},
   148  		{s: `epoch: "0"`, e: snap.Epoch{Read: []uint32{0}, Write: []uint32{0}}},
   149  		{s: `epoch: {}`, e: snap.Epoch{Read: []uint32{0}, Write: []uint32{0}}},
   150  		{s: `epoch: "2*"`, e: snap.Epoch{Read: []uint32{1, 2}, Write: []uint32{2}}},
   151  		{s: `epoch: {"read": [2]}`, e: snap.Epoch{Read: []uint32{2}, Write: []uint32{2}}},
   152  		{s: `epoch: {"read": [1, 2]}`, e: snap.Epoch{Read: []uint32{1, 2}, Write: []uint32{2}}},
   153  		{s: `epoch: {"write": [2]}`, e: snap.Epoch{Read: []uint32{2}, Write: []uint32{2}}},
   154  		{s: `epoch: {"write": [1, 2]}`, e: snap.Epoch{Read: []uint32{1, 2}, Write: []uint32{1, 2}}},
   155  		{s: `epoch: {"read": [2,4,8], "write": [2,3,5]}`, e: snap.Epoch{Read: []uint32{2, 4, 8}, Write: []uint32{2, 3, 5}}},
   156  	}
   157  
   158  	for _, test := range tests {
   159  		info, err := snap.InfoFromSnapYaml([]byte(test.s))
   160  		c.Check(err, check.IsNil, check.Commentf("YAML: %s", test.s))
   161  		c.Check(info.Epoch, check.DeepEquals, test.e)
   162  	}
   163  }
   164  
   165  func (s epochSuite) TestGoodEpochsInJSON(c *check.C) {
   166  	type Tt struct {
   167  		s string
   168  		e snap.Epoch
   169  	}
   170  
   171  	type Tinfo struct {
   172  		Epoch snap.Epoch `json:"epoch"`
   173  	}
   174  
   175  	tests := []Tt{
   176  		// {} should give snap.Epoch{Read: []uint32{0}, Write: []uint32{0}} but needs an UnmarshalJSON on the parent
   177  		{s: `{"epoch": null}`, e: snap.Epoch{Read: []uint32{0}, Write: []uint32{0}}},
   178  		{s: `{"epoch": "0"}`, e: snap.Epoch{Read: []uint32{0}, Write: []uint32{0}}},
   179  		{s: `{"epoch": {}}`, e: snap.Epoch{Read: []uint32{0}, Write: []uint32{0}}},
   180  		{s: `{"epoch": "2*"}`, e: snap.Epoch{Read: []uint32{1, 2}, Write: []uint32{2}}},
   181  		{s: `{"epoch": {"read": [0]}}`, e: snap.Epoch{Read: []uint32{0}, Write: []uint32{0}}},
   182  		{s: `{"epoch": {"write": [0]}}`, e: snap.Epoch{Read: []uint32{0}, Write: []uint32{0}}},
   183  		{s: `{"epoch": {"read": [2]}}`, e: snap.Epoch{Read: []uint32{2}, Write: []uint32{2}}},
   184  		{s: `{"epoch": {"read": [1, 2]}}`, e: snap.Epoch{Read: []uint32{1, 2}, Write: []uint32{2}}},
   185  		{s: `{"epoch": {"write": [2]}}`, e: snap.Epoch{Read: []uint32{2}, Write: []uint32{2}}},
   186  		{s: `{"epoch": {"write": [1, 2]}}`, e: snap.Epoch{Read: []uint32{1, 2}, Write: []uint32{1, 2}}},
   187  		{s: `{"epoch": {"read": [2,4,8], "write": [2,3,5]}}`, e: snap.Epoch{Read: []uint32{2, 4, 8}, Write: []uint32{2, 3, 5}}},
   188  	}
   189  
   190  	for _, test := range tests {
   191  		var info Tinfo
   192  		err := json.Unmarshal([]byte(test.s), &info)
   193  		c.Check(err, check.IsNil, check.Commentf("JSON: %s", test.s))
   194  		c.Check(info.Epoch, check.DeepEquals, test.e, check.Commentf("JSON: %s", test.s))
   195  	}
   196  }
   197  
   198  func (s *epochSuite) TestEpochValidate(c *check.C) {
   199  	validEpochs := []snap.Epoch{
   200  		{},
   201  		{Read: []uint32{0}, Write: []uint32{0}},
   202  		{Read: []uint32{0, 1}, Write: []uint32{1}},
   203  		{Read: []uint32{1}, Write: []uint32{1}},
   204  		{Read: []uint32{399, 400}, Write: []uint32{400}},
   205  		{Read: []uint32{1, 2, 3}, Write: []uint32{1, 2, 3}},
   206  	}
   207  	for _, epoch := range validEpochs {
   208  		err := epoch.Validate()
   209  		c.Check(err, check.IsNil, check.Commentf("%s", epoch))
   210  	}
   211  	invalidEpochs := []struct {
   212  		epoch snap.Epoch
   213  		err   string
   214  	}{
   215  		{epoch: snap.Epoch{Read: []uint32{}}, err: emptyEpochList},
   216  		{epoch: snap.Epoch{Write: []uint32{}}, err: emptyEpochList},
   217  		{epoch: snap.Epoch{Read: []uint32{}, Write: []uint32{}}, err: emptyEpochList},
   218  		{epoch: snap.Epoch{Read: []uint32{1}, Write: []uint32{2}}, err: noEpochIntersection},
   219  		{epoch: snap.Epoch{Read: []uint32{1, 3, 5}, Write: []uint32{2, 4, 6}}, err: noEpochIntersection},
   220  		{epoch: snap.Epoch{Read: []uint32{1, 2, 3}, Write: []uint32{3, 2, 1}}, err: epochListNotIncreasing},
   221  		{epoch: snap.Epoch{Read: []uint32{3, 2, 1}, Write: []uint32{1, 2, 3}}, err: epochListNotIncreasing},
   222  		{epoch: snap.Epoch{Read: []uint32{3, 2, 1}, Write: []uint32{3, 2, 1}}, err: epochListNotIncreasing},
   223  		{epoch: snap.Epoch{Read: []uint32{0, 0, 0}, Write: []uint32{0}}, err: epochListNotIncreasing},
   224  		{epoch: snap.Epoch{Read: []uint32{0}, Write: []uint32{0, 0, 0}}, err: epochListNotIncreasing},
   225  		{epoch: snap.Epoch{Read: []uint32{0, 0, 0}, Write: []uint32{0, 0, 0}}, err: epochListNotIncreasing},
   226  		{epoch: snap.Epoch{
   227  			Read:  []uint32{0},
   228  			Write: []uint32{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10},
   229  		}, err: epochListJustRidiculouslyLong},
   230  		{epoch: snap.Epoch{
   231  			Read:  []uint32{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10},
   232  			Write: []uint32{0},
   233  		}, err: epochListJustRidiculouslyLong},
   234  		{epoch: snap.Epoch{
   235  			Read:  []uint32{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10},
   236  			Write: []uint32{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10},
   237  		}, err: epochListJustRidiculouslyLong},
   238  	}
   239  	for _, test := range invalidEpochs {
   240  		err := test.epoch.Validate()
   241  		c.Check(err, check.ErrorMatches, test.err, check.Commentf("%s", test.epoch))
   242  	}
   243  }
   244  
   245  func (s *epochSuite) TestEpochString(c *check.C) {
   246  	tests := []struct {
   247  		e snap.Epoch
   248  		s string
   249  	}{
   250  		{e: snap.Epoch{}, s: "0"},
   251  		{e: snap.Epoch{Read: []uint32{0}}, s: "0"},
   252  		{e: snap.Epoch{Write: []uint32{0}}, s: "0"},
   253  		{e: snap.Epoch{Read: []uint32{0}, Write: []uint32{}}, s: "0"},
   254  		{e: snap.Epoch{Read: []uint32{}, Write: []uint32{0}}, s: "0"},
   255  		{e: snap.Epoch{Read: []uint32{}, Write: []uint32{}}, s: "0"},
   256  		{e: snap.Epoch{Read: []uint32{0}, Write: []uint32{0}}, s: "0"},
   257  		{e: snap.Epoch{Read: []uint32{0, 1}, Write: []uint32{1}}, s: "1*"},
   258  		{e: snap.Epoch{Read: []uint32{1}, Write: []uint32{1}}, s: "1"},
   259  		{e: snap.Epoch{Read: []uint32{399, 400}, Write: []uint32{400}}, s: "400*"},
   260  		{e: snap.Epoch{Read: []uint32{1, 2, 3}, Write: []uint32{1, 2, 3}}, s: `{"read":[1,2,3],"write":[1,2,3]}`},
   261  	}
   262  	for _, test := range tests {
   263  		c.Check(test.e.String(), check.Equals, test.s, check.Commentf(test.s))
   264  	}
   265  }
   266  
   267  func (s *epochSuite) TestEpochMarshal(c *check.C) {
   268  	tests := []struct {
   269  		e snap.Epoch
   270  		s string
   271  	}{
   272  		{e: snap.Epoch{}, s: `{"read":[0],"write":[0]}`},
   273  		{e: snap.Epoch{Read: []uint32{0}}, s: `{"read":[0],"write":[0]}`},
   274  		{e: snap.Epoch{Write: []uint32{0}}, s: `{"read":[0],"write":[0]}`},
   275  		{e: snap.Epoch{Read: []uint32{0}, Write: []uint32{}}, s: `{"read":[0],"write":[0]}`},
   276  		{e: snap.Epoch{Read: []uint32{}, Write: []uint32{0}}, s: `{"read":[0],"write":[0]}`},
   277  		{e: snap.Epoch{Read: []uint32{0}, Write: []uint32{0}}, s: `{"read":[0],"write":[0]}`},
   278  		{e: snap.Epoch{Read: []uint32{0, 1}, Write: []uint32{1}}, s: `{"read":[0,1],"write":[1]}`},
   279  		{e: snap.Epoch{Read: []uint32{1}, Write: []uint32{1}}, s: `{"read":[1],"write":[1]}`},
   280  		{e: snap.Epoch{Read: []uint32{399, 400}, Write: []uint32{400}}, s: `{"read":[399,400],"write":[400]}`},
   281  		{e: snap.Epoch{Read: []uint32{1, 2, 3}, Write: []uint32{1, 2, 3}}, s: `{"read":[1,2,3],"write":[1,2,3]}`},
   282  	}
   283  	for _, test := range tests {
   284  		bs, err := test.e.MarshalJSON()
   285  		c.Assert(err, check.IsNil)
   286  		c.Check(string(bs), check.Equals, test.s, check.Commentf(test.s))
   287  		bs, err = json.Marshal(test.e)
   288  		c.Assert(err, check.IsNil)
   289  		c.Check(string(bs), check.Equals, test.s, check.Commentf(test.s))
   290  	}
   291  }
   292  
   293  func (s *epochSuite) TestE(c *check.C) {
   294  	tests := []struct {
   295  		e snap.Epoch
   296  		s string
   297  	}{
   298  		{s: "0", e: snap.Epoch{Read: []uint32{0}, Write: []uint32{0}}},
   299  		{s: "1", e: snap.Epoch{Read: []uint32{1}, Write: []uint32{1}}},
   300  		{s: "1*", e: snap.Epoch{Read: []uint32{0, 1}, Write: []uint32{1}}},
   301  		{s: "400*", e: snap.Epoch{Read: []uint32{399, 400}, Write: []uint32{400}}},
   302  	}
   303  	for _, test := range tests {
   304  		c.Check(snap.E(test.s), check.DeepEquals, test.e, check.Commentf(test.s))
   305  		c.Check(test.e.String(), check.Equals, test.s, check.Commentf(test.s))
   306  	}
   307  }
   308  
   309  func (s *epochSuite) TestIsZero(c *check.C) {
   310  	for _, e := range []*snap.Epoch{
   311  		nil,
   312  		{},
   313  		{Read: []uint32{0}},
   314  		{Write: []uint32{0}},
   315  		{Read: []uint32{0}, Write: []uint32{}},
   316  		{Read: []uint32{}, Write: []uint32{0}},
   317  		{Read: []uint32{0}, Write: []uint32{0}},
   318  	} {
   319  		c.Check(e.IsZero(), check.Equals, true, check.Commentf("%#v", e))
   320  	}
   321  	for _, e := range []*snap.Epoch{
   322  		{Read: []uint32{0, 1}, Write: []uint32{0}},
   323  		{Read: []uint32{1}, Write: []uint32{1, 2}},
   324  	} {
   325  		c.Check(e.IsZero(), check.Equals, false, check.Commentf("%#v", e))
   326  	}
   327  }
   328  
   329  func (s *epochSuite) TestCanRead(c *check.C) {
   330  	tests := []struct {
   331  		a, b   snap.Epoch
   332  		ab, ba bool
   333  	}{
   334  		{ab: true, ba: true},                 // test for empty epoch
   335  		{a: snap.E("0"), ab: true, ba: true}, // hybrid empty / zero
   336  		{a: snap.E("0"), b: snap.E("1"), ab: false, ba: false},
   337  		{a: snap.E("0"), b: snap.E("1*"), ab: false, ba: true},
   338  		{a: snap.E("0"), b: snap.E("2*"), ab: false, ba: false},
   339  
   340  		{
   341  			a:  snap.Epoch{Read: []uint32{1, 2, 3}, Write: []uint32{2}},
   342  			b:  snap.Epoch{Read: []uint32{1, 3, 4}, Write: []uint32{4}},
   343  			ab: false,
   344  			ba: false,
   345  		},
   346  		{
   347  			a:  snap.Epoch{Read: []uint32{1, 2, 3}, Write: []uint32{3}},
   348  			b:  snap.Epoch{Read: []uint32{1, 2, 3}, Write: []uint32{2}},
   349  			ab: true,
   350  			ba: true,
   351  		},
   352  	}
   353  	for i, test := range tests {
   354  		c.Assert(test.a.CanRead(test.b), check.Equals, test.ab, check.Commentf("ab/%d", i))
   355  		c.Assert(test.b.CanRead(test.a), check.Equals, test.ba, check.Commentf("ba/%d", i))
   356  	}
   357  }
   358  
   359  func (s *epochSuite) TestEqual(c *check.C) {
   360  	tests := []struct {
   361  		a, b *snap.Epoch
   362  		eq   bool
   363  	}{
   364  		{a: &snap.Epoch{}, b: nil, eq: true},
   365  		{a: &snap.Epoch{Read: []uint32{}, Write: []uint32{}}, b: nil, eq: true},
   366  		{a: &snap.Epoch{Read: []uint32{1}, Write: []uint32{1}}, b: &snap.Epoch{Read: []uint32{1}, Write: []uint32{1}}, eq: true},
   367  		{a: &snap.Epoch{Read: []uint32{0, 1}, Write: []uint32{1}}, b: &snap.Epoch{Read: []uint32{0, 1}, Write: []uint32{1}}, eq: true},
   368  		{a: &snap.Epoch{Read: []uint32{0, 1}, Write: []uint32{1}}, b: &snap.Epoch{Read: []uint32{1}, Write: []uint32{1}}, eq: false},
   369  		{a: &snap.Epoch{Read: []uint32{1, 2, 3, 4}, Write: []uint32{7}}, b: &snap.Epoch{Read: []uint32{1, 2, 3, 7}, Write: []uint32{7}}, eq: false},
   370  	}
   371  
   372  	for i, test := range tests {
   373  		c.Check(test.a.Equal(test.b), check.Equals, test.eq, check.Commentf("ab/%d", i))
   374  		c.Check(test.b.Equal(test.a), check.Equals, test.eq, check.Commentf("ab/%d", i))
   375  	}
   376  }