gopkg.in/ubuntu-core/snappy.v0@v0.0.0-20210902073436-25a8614f10a6/overlord/state/warning_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2018 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 state_test 21 22 import ( 23 "bytes" 24 "encoding/json" 25 "fmt" 26 "time" 27 28 "gopkg.in/check.v1" 29 30 "github.com/snapcore/snapd/overlord/state" 31 ) 32 33 var never time.Time 34 35 func (stateSuite) testMarshalWarning(shown bool, c *check.C) { 36 st := state.New(nil) 37 st.Lock() 38 defer st.Unlock() 39 40 st.Warnf("hello") 41 now := time.Now() 42 43 expectedNumKeys := 5 44 if shown { 45 expectedNumKeys++ // last-shown 46 st.OkayWarnings(now) 47 } 48 49 ws := st.AllWarnings() 50 c.Assert(ws, check.HasLen, 1) 51 c.Check(ws[0].String(), check.Equals, "hello") 52 buf, err := json.Marshal(ws) 53 c.Assert(err, check.IsNil) 54 55 var v []map[string]string 56 c.Assert(json.Unmarshal(buf, &v), check.IsNil) 57 c.Assert(v, check.HasLen, 1) 58 c.Check(v[0], check.HasLen, expectedNumKeys) 59 c.Check(v[0]["message"], check.DeepEquals, "hello") 60 c.Check(v[0]["expire-after"], check.Equals, state.DefaultExpireAfter.String()) 61 c.Check(v[0]["repeat-after"], check.Equals, state.DefaultRepeatAfter.String()) 62 c.Check(v[0]["first-added"], check.Equals, v[0]["last-added"]) 63 t, err := time.Parse(time.RFC3339, v[0]["first-added"]) 64 c.Assert(err, check.IsNil) 65 dt := t.Sub(now) 66 // 'now' was just *after* creating the warning 67 c.Check(dt <= 0, check.Equals, true) 68 c.Check(-time.Minute < dt, check.Equals, true) 69 if shown { 70 t, err := time.Parse(time.RFC3339, v[0]["last-shown"]) 71 c.Assert(err, check.IsNil) 72 dt := t.Sub(now) 73 // 'now' was just *before* marking the warning as shown 74 c.Check(0 <= dt, check.Equals, true) 75 c.Check(dt < time.Minute, check.Equals, true) 76 } 77 78 var ws2 []*state.Warning 79 c.Assert(json.Unmarshal(buf, &ws2), check.IsNil) 80 c.Assert(ws2, check.HasLen, 1) 81 c.Check(ws2[0], check.DeepEquals, ws[0]) 82 } 83 84 func (s stateSuite) TestMarshalWarning(c *check.C) { 85 s.testMarshalWarning(false, c) 86 } 87 88 func (s stateSuite) TestMarshalShownWarning(c *check.C) { 89 s.testMarshalWarning(true, c) 90 } 91 92 func (stateSuite) TestUnmarshalErrors(c *check.C) { 93 var w state.Warning 94 c.Check(json.Unmarshal([]byte(`42`), &w), check.ErrorMatches, ".* cannot unmarshal .*") 95 96 type T1 struct { 97 b string 98 e error 99 } 100 101 for _, t := range []T1{ 102 // sanity check 103 {`{"message": "x", "first-added": "2006-01-02T15:04:05Z", "expire-after": "1h", "repeat-after": "1h"}`, nil}, 104 // remove one field at a time: 105 {`{ "first-added": "2006-01-02T15:04:05Z", "expire-after": "1h", "repeat-after": "1h"}`, state.ErrNoWarningMessage}, 106 {`{"message": "x", "expire-after": "1h", "repeat-after": "1h"}`, state.ErrNoWarningFirstAdded}, 107 {`{"message": "x", "first-added": "2006-01-02T15:04:05Z", "repeat-after": "1h"}`, state.ErrNoWarningExpireAfter}, 108 {`{"message": "x", "first-added": "2006-01-02T15:04:05Z", "expire-after": "1h" }`, state.ErrNoWarningRepeatAfter}, 109 } { 110 var w state.Warning 111 c.Check(json.Unmarshal([]byte(t.b), &w), check.Equals, t.e) 112 } 113 114 type T2 struct{ b, e string } 115 116 for _, t := range []T2{ 117 // some bogus values 118 {`{"message": " ", "first-added": "2006-01-02T15:04:05Z", "expire-after": "1h", "repeat-after": "1h"}`, "malformed warning message"}, 119 {`{"message": "x", "first-added": "2006", "expire-after": "1h", "repeat-after": "1h"}`, "parsing time .* cannot parse .*"}, 120 {`{"message": "x", "first-added": "2006-01-02T15:04:05Z", "expire-after": "1d", "repeat-after": "1h"}`, ".* unknown unit \"?d\"? .*"}, 121 {`{"message": "x", "first-added": "2006-01-02T15:04:05Z", "expire-after": "1h", "repeat-after": "1d"}`, ".* unknown unit \"?d\"? .*"}, 122 } { 123 var w state.Warning 124 c.Check(json.Unmarshal([]byte(t.b), &w), check.ErrorMatches, t.e) 125 } 126 } 127 128 func (stateSuite) TestEmptyStateWarnings(c *check.C) { 129 st := state.New(nil) 130 st.Lock() 131 defer st.Unlock() 132 ws := st.AllWarnings() 133 c.Check(ws, check.HasLen, 0) 134 } 135 136 func (stateSuite) TestDeleteExpired(c *check.C) { 137 const dt = 20 * time.Millisecond 138 oldTime := time.Now() 139 st := state.New(nil) 140 st.Lock() 141 defer st.Unlock() 142 st.Warnf("hello again") // adding this twice to trigger the swap in sort 143 st.AddWarning("hello", oldTime, never, dt, state.DefaultRepeatAfter) 144 st.Warnf("hello again") 145 146 allWs := st.AllWarnings() 147 c.Assert(allWs, check.HasLen, 2) 148 149 time.Sleep(2 * dt) 150 now := time.Now() 151 152 c.Assert(allWs, check.HasLen, 2) 153 c.Check(fmt.Sprintf("%q", allWs), check.Equals, `["hello" "hello again"]`) 154 c.Check(allWs[0].ExpiredBefore(now), check.Equals, true) 155 c.Check(allWs[0].ShowAfter(now), check.Equals, true) 156 c.Check(allWs[1].ExpiredBefore(now), check.Equals, false) 157 c.Check(allWs[1].ShowAfter(now), check.Equals, true) 158 159 allWs = st.AllWarnings() 160 c.Check(allWs, check.HasLen, 1) 161 c.Check(fmt.Sprintf("%q", allWs), check.Equals, `["hello again"]`) 162 } 163 164 func (stateSuite) TestOldRepeatedWarning(c *check.C) { 165 now := time.Now() 166 oldTime := now.UTC().Add(-2 * state.DefaultExpireAfter) 167 st := state.New(nil) 168 st.Lock() 169 defer st.Unlock() 170 st.AddWarning("hello", oldTime, never, state.DefaultExpireAfter, state.DefaultRepeatAfter) 171 st.Warnf("hello") 172 173 allWs := st.AllWarnings() 174 c.Assert(allWs, check.HasLen, 1) 175 w := allWs[0] 176 c.Check(w.ExpiredBefore(now), check.Equals, false) 177 c.Check(w.ShowAfter(now), check.Equals, true) 178 } 179 180 func (stateSuite) TestCheckpoint(c *check.C) { 181 b := &fakeStateBackend{} 182 st := state.New(b) 183 st.Lock() 184 st.Warnf("hello") 185 st.Unlock() 186 c.Assert(b.checkpoints, check.HasLen, 1) 187 188 st2, err := state.ReadState(nil, bytes.NewReader(b.checkpoints[0])) 189 c.Assert(err, check.IsNil) 190 st2.Lock() 191 defer st2.Unlock() 192 ws := st2.AllWarnings() 193 c.Assert(ws, check.HasLen, 1) 194 c.Check(fmt.Sprintf("%q", ws), check.Equals, `["hello"]`) 195 } 196 197 func (stateSuite) TestWarningsSummaryReturnsLastLastAdded(c *check.C) { 198 st := state.New(nil) 199 st.Lock() 200 defer st.Unlock() 201 t0 := time.Now().Add(-100 * time.Hour) 202 st.AddWarning("hello", t0, never, state.DefaultExpireAfter, state.DefaultRepeatAfter) 203 n, t := st.WarningsSummary() 204 c.Check(n, check.Equals, 1) 205 c.Check(t, check.DeepEquals, t0) 206 } 207 208 func (stateSuite) TestShowAndOkay(c *check.C) { 209 st := state.New(nil) 210 st.Lock() 211 defer st.Unlock() 212 st.Warnf("number one") 213 n, _ := st.WarningsSummary() 214 c.Check(n, check.Equals, 1) 215 ws1, t1 := st.PendingWarnings() 216 c.Assert(ws1, check.HasLen, 1) 217 c.Check(fmt.Sprintf("%q", ws1), check.Equals, `["number one"]`) 218 219 st.Warnf("number two") 220 ws2, t2 := st.PendingWarnings() 221 c.Assert(ws2, check.HasLen, 2) 222 c.Check(fmt.Sprintf("%q", ws2), check.Equals, `["number one" "number two"]`) 223 c.Assert(t2.After(t1), check.Equals, true) 224 225 n = st.OkayWarnings(t1) 226 c.Check(n, check.Equals, 1) 227 228 ws, _ := st.PendingWarnings() 229 c.Assert(ws, check.HasLen, 1) 230 c.Check(fmt.Sprintf("%q", ws), check.Equals, `["number two"]`) 231 232 n = st.OkayWarnings(t2) 233 c.Check(n, check.Equals, 1) 234 235 ws, _ = st.PendingWarnings() 236 c.Check(ws, check.HasLen, 0) 237 238 st.UnshowAllWarnings() 239 ws, _ = st.PendingWarnings() 240 c.Check(ws, check.HasLen, 2) 241 } 242 243 func (stateSuite) TestShowAndOkayWithRepeats(c *check.C) { 244 st := state.New(nil) 245 st.Lock() 246 defer st.Unlock() 247 const myRepeatAfter = 2 * time.Second 248 t0 := time.Now() 249 st.AddWarning("hello", t0, never, state.DefaultExpireAfter, myRepeatAfter) 250 ws, t1 := st.PendingWarnings() 251 c.Assert(ws, check.HasLen, 1) 252 c.Check(fmt.Sprintf("%q", ws), check.Equals, `["hello"]`) 253 254 n := st.OkayWarnings(t1) 255 c.Check(n, check.Equals, 1) 256 257 st.Warnf("hello") 258 259 ws, _ = st.PendingWarnings() 260 c.Check(ws, check.HasLen, 0) // not enough time has passed 261 262 time.Sleep(myRepeatAfter) 263 264 ws, _ = st.PendingWarnings() 265 c.Check(ws, check.HasLen, 1) 266 c.Check(fmt.Sprintf("%q", ws), check.Equals, `["hello"]`) 267 }