github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/overlord/cmdstate/cmdstate_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 cmdstate_test
    21  
    22  import (
    23  	"path/filepath"
    24  	"strings"
    25  	"testing"
    26  	"time"
    27  
    28  	"gopkg.in/check.v1"
    29  
    30  	"github.com/snapcore/snapd/dirs"
    31  	"github.com/snapcore/snapd/osutil"
    32  	"github.com/snapcore/snapd/overlord"
    33  	"github.com/snapcore/snapd/overlord/cmdstate"
    34  	"github.com/snapcore/snapd/overlord/state"
    35  )
    36  
    37  // hook up gocheck to testing
    38  func TestCommand(t *testing.T) { check.TestingT(t) }
    39  
    40  type cmdSuite struct {
    41  	rootdir string
    42  	state   *state.State
    43  	se      *overlord.StateEngine
    44  	manager overlord.StateManager
    45  	restore func()
    46  }
    47  
    48  var _ = check.Suite(&cmdSuite{})
    49  
    50  type statr interface {
    51  	Status() state.Status
    52  }
    53  
    54  func (s *cmdSuite) waitfor(thing statr) {
    55  	s.state.Unlock()
    56  	for i := 0; i < 5; i++ {
    57  		s.se.Ensure()
    58  		s.se.Wait()
    59  		s.state.Lock()
    60  		if thing.Status().Ready() {
    61  			return
    62  		}
    63  		s.state.Unlock()
    64  	}
    65  	s.state.Lock()
    66  }
    67  
    68  func (s *cmdSuite) SetUpTest(c *check.C) {
    69  	d := c.MkDir()
    70  	dirs.SetRootDir(d)
    71  	s.rootdir = d
    72  	s.state = state.New(nil)
    73  	s.se = overlord.NewStateEngine(s.state)
    74  	runner := state.NewTaskRunner(s.state)
    75  	s.manager = cmdstate.Manager(s.state, runner)
    76  	s.se.AddManager(s.manager)
    77  	s.se.AddManager(runner)
    78  	c.Assert(s.se.StartUp(), check.IsNil)
    79  	s.restore = cmdstate.MockDefaultExecTimeout(time.Second / 10)
    80  }
    81  
    82  func (s *cmdSuite) TearDownTest(c *check.C) {
    83  	s.restore()
    84  }
    85  
    86  func (s *cmdSuite) TestExecTask(c *check.C) {
    87  	s.state.Lock()
    88  	defer s.state.Unlock()
    89  	argvIn := []string{"/bin/echo", "hello"}
    90  	tasks := cmdstate.ExecWithTimeout(s.state, "this is the summary", argvIn, time.Second/10).Tasks()
    91  	c.Assert(tasks, check.HasLen, 1)
    92  	task := tasks[0]
    93  	c.Check(task.Kind(), check.Equals, "exec-command")
    94  
    95  	var argvOut []string
    96  	c.Check(task.Get("argv", &argvOut), check.IsNil)
    97  	c.Check(argvOut, check.DeepEquals, argvIn)
    98  }
    99  
   100  func (s *cmdSuite) TestExecHappy(c *check.C) {
   101  	s.state.Lock()
   102  	defer s.state.Unlock()
   103  
   104  	fn := filepath.Join(s.rootdir, "flag")
   105  	ts := cmdstate.ExecWithTimeout(s.state, "Doing the thing", []string{"touch", fn}, time.Second/10)
   106  	chg := s.state.NewChange("do-the-thing", "Doing the thing")
   107  	chg.AddAll(ts)
   108  
   109  	s.waitfor(chg)
   110  
   111  	c.Check(osutil.FileExists(fn), check.Equals, true)
   112  	c.Check(chg.Status(), check.Equals, state.DoneStatus)
   113  }
   114  
   115  func (s *cmdSuite) TestExecIgnore(c *check.C) {
   116  	s.state.Lock()
   117  	defer s.state.Unlock()
   118  
   119  	fn := filepath.Join(s.rootdir, "flag")
   120  	ts := cmdstate.ExecWithTimeout(s.state, "Doing the thing", []string{"touch", fn}, time.Second/10)
   121  	c.Assert(ts.Tasks(), check.HasLen, 1)
   122  	ignore := true
   123  	ts.Tasks()[0].Set("ignore", ignore)
   124  
   125  	chg := s.state.NewChange("do-the-thing", "Doing the thing")
   126  	chg.AddAll(ts)
   127  
   128  	s.waitfor(chg)
   129  
   130  	// file not created
   131  	c.Check(osutil.FileExists(fn), check.Equals, false)
   132  	c.Check(chg.Status(), check.Equals, state.DoneStatus)
   133  
   134  	c.Check(strings.Join(ts.Tasks()[0].Log(), ""), check.Matches, `.*task ignored`)
   135  }
   136  
   137  func (s *cmdSuite) TestExecSad(c *check.C) {
   138  	s.state.Lock()
   139  	defer s.state.Unlock()
   140  
   141  	ts := cmdstate.ExecWithTimeout(s.state, "Doing the thing", []string{"sh", "-c", "echo hello; false"}, time.Second/10)
   142  	chg := s.state.NewChange("do-the-thing", "Doing the thing")
   143  	chg.AddAll(ts)
   144  
   145  	s.waitfor(chg)
   146  
   147  	c.Check(chg.Status(), check.Equals, state.ErrorStatus)
   148  }
   149  
   150  func (s *cmdSuite) TestExecAbort(c *check.C) {
   151  	s.state.Lock()
   152  	defer s.state.Unlock()
   153  
   154  	ts := cmdstate.ExecWithTimeout(s.state, "Doing the thing", []string{"sleep", "1h"}, time.Second/10)
   155  	chg := s.state.NewChange("do-the-thing", "Doing the thing")
   156  	chg.AddAll(ts)
   157  
   158  	s.state.Unlock()
   159  	s.se.Ensure()
   160  	s.state.Lock()
   161  
   162  	c.Assert(chg.Status(), check.Equals, state.DoingStatus)
   163  
   164  	chg.Abort()
   165  
   166  	s.waitfor(chg)
   167  
   168  	c.Check(chg.Status(), check.Equals, state.ErrorStatus)
   169  	c.Check(strings.Join(chg.Tasks()[0].Log(), "\n"), check.Matches, `(?s).*ERROR aborted`)
   170  }
   171  
   172  func (s *cmdSuite) TestExecStop(c *check.C) {
   173  	s.state.Lock()
   174  	defer s.state.Unlock()
   175  
   176  	ts := cmdstate.ExecWithTimeout(s.state, "Doing the thing", []string{"sleep", "1h"}, time.Second/10)
   177  	chg := s.state.NewChange("do-the-thing", "Doing the thing")
   178  	chg.AddAll(ts)
   179  
   180  	c.Assert(chg.Status(), check.Equals, state.DoStatus)
   181  
   182  	s.state.Unlock()
   183  	s.se.Stop()
   184  	s.state.Lock()
   185  
   186  	c.Check(chg.Status(), check.Equals, state.DoStatus)
   187  	chg.Abort()
   188  }
   189  
   190  func (s *cmdSuite) TestExecTimesOut(c *check.C) {
   191  	s.state.Lock()
   192  	defer s.state.Unlock()
   193  
   194  	ts := cmdstate.ExecWithTimeout(s.state, "Doing the thing", []string{"sleep", "1m"}, time.Second/10)
   195  	chg := s.state.NewChange("do-the-thing", "Doing the thing")
   196  	chg.AddAll(ts)
   197  
   198  	s.waitfor(chg)
   199  
   200  	c.Check(chg.Status(), check.Equals, state.ErrorStatus)
   201  	c.Check(strings.Join(chg.Tasks()[0].Log(), "\n"), check.Matches, `(?s).*ERROR exceeded maximum runtime.*`)
   202  }
   203  
   204  func (s *cmdSuite) TestExecTimeoutMissing(c *check.C) {
   205  	s.state.Lock()
   206  	defer s.state.Unlock()
   207  
   208  	restore := cmdstate.MockDefaultExecTimeout(1 * time.Second)
   209  	defer restore()
   210  
   211  	ts := cmdstate.ExecWithTimeout(s.state, "Doing the thing", []string{"sleep", "0.3"}, time.Second/10)
   212  	c.Assert(len(ts.Tasks()), check.Equals, 1)
   213  	t := ts.Tasks()[0]
   214  	// no timeout means the default timeout will be used
   215  	t.Clear("timeout")
   216  	chg := s.state.NewChange("do-the-thing", "Doing the thing")
   217  	chg.AddAll(ts)
   218  
   219  	s.waitfor(chg)
   220  
   221  	// slept for
   222  	c.Check(chg.Status(), check.Equals, state.DoneStatus)
   223  }