github.com/hugh712/snapd@v0.0.0-20200910133618-1a99902bd583/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  }