@@ -1648,6 +1648,90 @@ func TestManagerAlwaysEmitsStoppedStatesForComponents(t *testing.T) {
16481648 }, time .Millisecond , time .Second * 5 )
16491649}
16501650
1651+ func TestManagerEmitsStartingStatesWhenHealthcheckIsUnavailable (t * testing.T ) {
1652+ testLogger , _ := loggertest .New ("test" )
1653+ agentInfo := & info.AgentInfo {}
1654+ beatMonitoringConfigGetter := mockBeatMonitoringConfigGetter
1655+ collectorStarted := make (chan struct {})
1656+
1657+ execution := & mockExecution {
1658+ collectorStarted : collectorStarted ,
1659+ }
1660+
1661+ // Create manager with test dependencies
1662+ mgr , err := NewOTelManager (
1663+ testLogger ,
1664+ logp .DebugLevel ,
1665+ testLogger ,
1666+ config .SubprocessExecutionMode , // irrelevant, we'll override it
1667+ agentInfo ,
1668+ nil ,
1669+ beatMonitoringConfigGetter ,
1670+ time .Second ,
1671+ )
1672+ require .NoError (t , err )
1673+ mgr .recoveryTimer = newRestarterNoop ()
1674+ mgr .execution = execution
1675+
1676+ // Start manager in a goroutine
1677+ ctx , cancel := context .WithTimeout (context .Background (), time .Minute * 5 )
1678+ defer cancel ()
1679+
1680+ go func () {
1681+ err := mgr .Run (ctx )
1682+ assert .ErrorIs (t , err , context .Canceled )
1683+ }()
1684+
1685+ testComp := testComponent ("test" )
1686+ components := []component.Component {testComp }
1687+ otelStatus := & status.AggregateStatus {
1688+ Event : componentstatus .NewEvent (componentstatus .StatusStarting ),
1689+ }
1690+ // start the collector by giving it a mock config
1691+ mgr .Update (nil , components )
1692+ select {
1693+ case <- ctx .Done ():
1694+ t .Fatal ("timeout waiting for collector status update" )
1695+ case <- execution .collectorStarted :
1696+ }
1697+
1698+ // send the status from the execution
1699+ select {
1700+ case <- ctx .Done ():
1701+ t .Fatal ("timeout waiting for collector status update" )
1702+ case execution .statusCh <- otelStatus :
1703+ }
1704+
1705+ // verify we get the component Starting state from the manager
1706+ componentStates , err := getFromChannelOrErrorWithContext (t , ctx , mgr .WatchComponents (), mgr .Errors ())
1707+ require .NoError (t , err )
1708+ require .NotNil (t , componentStates )
1709+ require .Len (t , componentStates , 1 )
1710+ componentState := componentStates [0 ]
1711+ assert .Equal (t , componentState .State .State , client .UnitStateStarting )
1712+ assert .Equal (t , componentState .State .Message , "STARTING" )
1713+
1714+ // stop the component by sending a nil config
1715+ mgr .Update (nil , nil )
1716+
1717+ // then send a nil status, indicating the collector is not running the component anymore
1718+ // do this a few times to see if the STOPPED state isn't lost along the way
1719+ for range 3 {
1720+ reportCollectorStatus (ctx , execution .statusCh , nil )
1721+ time .Sleep (time .Millisecond * 100 ) // TODO: Replace this with synctest after we upgrade to Go 1.25
1722+ }
1723+
1724+ // verify that we get a STOPPED state for the component
1725+ assert .EventuallyWithT (t , func (collect * assert.CollectT ) {
1726+ componentStates , err := getFromChannelOrErrorWithContext (t , ctx , mgr .WatchComponents (), mgr .Errors ())
1727+ require .NoError (collect , err )
1728+ require .NotNil (collect , componentStates )
1729+ require .Len (collect , componentStates , 1 )
1730+ componentState := componentStates [0 ]
1731+ assert .Equal (collect , componentState .State .State , client .UnitStateStopped )
1732+ }, time .Millisecond , time .Second * 5 )
1733+ }
1734+
16511735func getFromChannelOrErrorWithContext [T any ](t * testing.T , ctx context.Context , ch <- chan T , errCh <- chan error ) (T , error ) {
16521736 t .Helper ()
16531737 var result T
0 commit comments