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