Repository: prometheus/prometheus
File: notifier/manager_test.go (Go)

Code before:
			}
		}
	}
}

func TestStop_DrainingDisabled(t *testing.T) {
	synctest.Test(t, func(t *testing.T) {
		const alertmanagerURL = "http://alertmanager:9093/api/v2/alerts"

		handlerSync := make(chan struct{})
		alertsReceived := atomic.NewInt64(0)

		// Fake Do function that simulates alertmanager behavior in-process.
		fakeDo := func(_ context.Context, _ *http.Client, req *http.Request) (*http.Response, error) {
			var alerts []*Alert
			b, err := io.ReadAll(req.Body)
			if err != nil {
				return nil, fmt.Errorf("read request body: %w", err)
			}
			if err := json.Unmarshal(b, &alerts); err != nil {
				return nil, fmt.Errorf("unmarshal request body: %w", err)
			}
			alertsReceived.Add(int64(len(alerts)))

			// Signal arrival, then block until test releases us.
			handlerSync <- struct{}{}
			<-handlerSync

			return &http.Response{
				StatusCode: http.StatusOK,
				Body:       io.NopCloser(bytes.NewBuffer(nil)),
			}, nil
		}

		reg := prometheus.NewRegistry()
		m := NewManager(
			&Options{
				QueueCapacity:   10,
				DrainOnShutdown: false,
				Registerer:      reg,
				Do:              fakeDo,
			},
			model.UTF8Validation,
			nil,
		)

		m.alertmanagers = make(map[string]*alertmanagerSet)

		am1Cfg := config.DefaultAlertmanagerConfig
		am1Cfg.Timeout = model.Duration(time.Second)
		m.alertmanagers["1"] = newTestAlertmanagerSet(&am1Cfg, nil, m.opts, m.metrics, alertmanagerURL)

Review comment (suggestion):
Reusing same channel for both way signaling is odd. I can see a race where this send is received below 🙈 

In other PR you used a different method (context)? Can we do the same? or just different channel?

<img width="698" height="355" alt="Image" src="https://github.com/user-attachments/assets/441f889d-a80f-4987-8220-d6b0e28da9ad" />

Code after:
		const alertmanagerURL = "http://alertmanager:9093/api/v2/alerts"

		handlerStarted := make(chan struct{})
		alertsReceived := atomic.NewInt64(0)

		// Fake Do function that simulates a hanging alertmanager that times out.
		fakeDo := func(ctx context.Context, _ *http.Client, req *http.Request) (*http.Response, error) {
			var alerts []*Alert
			b, err := io.ReadAll(req.Body)
			if err != nil {
				return nil, fmt.Errorf("read request body: %w", err)
			}
			if err := json.Unmarshal(b, &alerts); err != nil {
				return nil, fmt.Errorf("unmarshal request body: %w", err)
			}
			alertsReceived.Add(int64(len(alerts)))

			// Signal arrival, then block until context times out.
			handlerStarted <- struct{}{}
			<-ctx.Done()

			return nil, ctx.Err()
		}

		reg := prometheus.NewRegistry()
		m := NewManager(
			&Options{
				QueueCapacity:   10,
				DrainOnShutdown: false,
				Registerer:      reg,
				Do:              fakeDo,
			},
			model.UTF8Validation,
			nil,
		)

		m.alertmanagers = make(map[string]*alertmanagerSet)

		am1Cfg := config.DefaultAlertmanagerConfig
		am1Cfg.Timeout = model.Duration(time.Second)
		m.alertmanagers["1"] = newTestAlertmanagerSet(&am1Cfg, nil, m.opts, m.metrics, alertmanagerURL)

		for _, ams := range m.alertmanagers {
			ams.startSendLoops(ams.ams)
		}

		// This will be waited on automatically when synctest.Test exits.
		go m.Run(nil)

		// Queue two alerts. The first should be immediately sent to the receiver, which should block until we release it later.
		m.Send(&Alert{Labels: labels.FromStrings(labels.AlertName, "alert-1")})