github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/state/testing/suite.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package testing
     5  
     6  import (
     7  	"sync"
     8  	"time"
     9  
    10  	"github.com/juju/clock/testclock"
    11  	"github.com/juju/loggo"
    12  	mgotesting "github.com/juju/mgo/v3/testing"
    13  	"github.com/juju/names/v5"
    14  	jujutesting "github.com/juju/testing"
    15  	jc "github.com/juju/testing/checkers"
    16  	gc "gopkg.in/check.v1"
    17  
    18  	"github.com/juju/juju/cloud"
    19  	"github.com/juju/juju/environs/config"
    20  	"github.com/juju/juju/state"
    21  	statewatcher "github.com/juju/juju/state/watcher"
    22  	"github.com/juju/juju/testing"
    23  	"github.com/juju/juju/testing/factory"
    24  )
    25  
    26  var _ = gc.Suite(&StateSuite{})
    27  
    28  // StateSuite provides setup and teardown for tests that require a
    29  // state.State.
    30  type StateSuite struct {
    31  	mgotesting.MgoSuite
    32  	testing.BaseSuite
    33  	NewPolicy                 state.NewPolicyFunc
    34  	Controller                *state.Controller
    35  	StatePool                 *state.StatePool
    36  	State                     *state.State
    37  	Model                     *state.Model
    38  	Owner                     names.UserTag
    39  	AdminPassword             string
    40  	Factory                   *factory.Factory
    41  	InitialConfig             *config.Config
    42  	ControllerConfig          map[string]interface{}
    43  	ControllerInheritedConfig map[string]interface{}
    44  	ControllerModelType       state.ModelType
    45  	RegionConfig              cloud.RegionConfig
    46  	Clock                     testclock.AdvanceableClock
    47  	txnSyncNotify             chan struct{}
    48  	modelWatcherIdle          chan string
    49  	modelWatcherMutex         *sync.Mutex
    50  }
    51  
    52  func (s *StateSuite) SetUpSuite(c *gc.C) {
    53  	s.MgoSuite.SetUpSuite(c)
    54  	s.BaseSuite.SetUpSuite(c)
    55  }
    56  
    57  func (s *StateSuite) TearDownSuite(c *gc.C) {
    58  	s.BaseSuite.TearDownSuite(c)
    59  	s.MgoSuite.TearDownSuite(c)
    60  }
    61  
    62  func (s *StateSuite) SetUpTest(c *gc.C) {
    63  	s.MgoSuite.SetUpTest(c)
    64  	s.BaseSuite.SetUpTest(c)
    65  
    66  	s.txnSyncNotify = make(chan struct{})
    67  	s.modelWatcherIdle = nil
    68  	s.modelWatcherMutex = &sync.Mutex{}
    69  	s.PatchValue(&statewatcher.TxnPollNotifyFunc, s.txnNotifyFunc)
    70  	s.PatchValue(&statewatcher.HubWatcherIdleFunc, s.hubWatcherIdleFunc)
    71  
    72  	s.Owner = names.NewLocalUserTag("test-admin")
    73  
    74  	if s.Clock == nil {
    75  		s.Clock = testclock.NewDilatedWallClock(100 * time.Millisecond)
    76  	}
    77  
    78  	s.AdminPassword = "admin-secret"
    79  	s.Controller = InitializeWithArgs(c, InitializeArgs{
    80  		Owner:                     s.Owner,
    81  		AdminPassword:             s.AdminPassword,
    82  		InitialConfig:             s.InitialConfig,
    83  		ControllerConfig:          s.ControllerConfig,
    84  		ControllerInheritedConfig: s.ControllerInheritedConfig,
    85  		ControllerModelType:       s.ControllerModelType,
    86  		RegionConfig:              s.RegionConfig,
    87  		NewPolicy:                 s.NewPolicy,
    88  		Clock:                     s.Clock,
    89  	})
    90  	s.AddCleanup(func(*gc.C) {
    91  		_ = s.Controller.Close()
    92  		close(s.txnSyncNotify)
    93  	})
    94  	s.StatePool = s.Controller.StatePool()
    95  	var err error
    96  	s.State, err = s.StatePool.SystemState()
    97  	c.Assert(err, jc.ErrorIsNil)
    98  	model, err := s.State.Model()
    99  	c.Assert(err, jc.ErrorIsNil)
   100  	s.Model = model
   101  
   102  	s.Factory = factory.NewFactory(s.State, s.StatePool)
   103  }
   104  
   105  func (s *StateSuite) TearDownTest(c *gc.C) {
   106  	s.BaseSuite.TearDownTest(c)
   107  	s.MgoSuite.TearDownTest(c)
   108  }
   109  
   110  func (s *StateSuite) txnNotifyFunc() {
   111  	select {
   112  	case s.txnSyncNotify <- struct{}{}:
   113  		// Try to send something down the channel.
   114  	default:
   115  		// However don't get stressed if no one is listening.
   116  	}
   117  }
   118  
   119  func (s *StateSuite) hubWatcherIdleFunc(modelUUID string) {
   120  	s.modelWatcherMutex.Lock()
   121  	idleChan := s.modelWatcherIdle
   122  	s.modelWatcherMutex.Unlock()
   123  	if idleChan == nil {
   124  		return
   125  	}
   126  	// There is a very small race condition between when the
   127  	// idle channel is cleared and when the function exits.
   128  	// Under normal circumstances, there is a goroutine in a tight loop
   129  	// reading off the idle channel. If the channel isn't read
   130  	// within a short wait, we don't send the message.
   131  	select {
   132  	case idleChan <- modelUUID:
   133  	case <-time.After(testing.ShortWait):
   134  	}
   135  }
   136  
   137  // WaitForNextSync repeatedly advances the testing clock
   138  // with short waits between until the txn poller doesn't find
   139  // any more changes.
   140  func (s *StateSuite) WaitForNextSync(c *gc.C) {
   141  	done := make(chan struct{})
   142  	go func() {
   143  		<-s.txnSyncNotify
   144  		close(done)
   145  	}()
   146  	timeout := time.After(jujutesting.LongWait)
   147  	for {
   148  		select {
   149  		case <-done:
   150  			return
   151  		case <-timeout:
   152  			c.Fatal("no sync event sent, is the watcher dead?")
   153  		}
   154  	}
   155  }
   156  
   157  // WaitForModelWatchersIdle firstly waits for the txn poller to process
   158  // all pending changes, then waits for the hub watcher on the state object
   159  // to have finished processing all those events.
   160  func (s *StateSuite) WaitForModelWatchersIdle(c *gc.C, modelUUID string) {
   161  	// Use a logger rather than c.Log so we get timestamps.
   162  	logger := loggo.GetLogger("test")
   163  	logger.Infof("waiting for model %s to be idle", modelUUID)
   164  	s.WaitForNextSync(c)
   165  	// Create idle channel after the sync so as to be sure that at least
   166  	// one sync is complete before signalling the idle timer.
   167  	s.modelWatcherMutex.Lock()
   168  	idleChan := make(chan string)
   169  	s.modelWatcherIdle = idleChan
   170  	s.modelWatcherMutex.Unlock()
   171  
   172  	defer func() {
   173  		s.modelWatcherMutex.Lock()
   174  		s.modelWatcherIdle = nil
   175  		s.modelWatcherMutex.Unlock()
   176  		// Clear out any pending events.
   177  		for {
   178  			select {
   179  			case <-idleChan:
   180  			default:
   181  				return
   182  			}
   183  		}
   184  	}()
   185  
   186  	timeout := time.After(jujutesting.LongWait)
   187  	for {
   188  		loop := time.After(10 * time.Millisecond)
   189  		select {
   190  		case <-loop:
   191  		case uuid := <-idleChan:
   192  			if uuid == modelUUID {
   193  				return
   194  			} else {
   195  				logger.Infof("model %s is idle", uuid)
   196  			}
   197  		case <-timeout:
   198  			c.Fatal("no sync event sent, is the watcher dead?")
   199  		}
   200  	}
   201  }