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 }