Repository: prometheus/prometheus
File: cmd/prometheus/reload_test.go (Go)

Code before:

	require.NoError(t, os.WriteFile(configFilePath, []byte(testCases[0].configText), 0o644), "Failed to write initial config file")

	port := testutil.RandomUnprivilegedPort(t)

	prom := runPrometheusWithLogging(t, configFilePath, port)
	defer prom.Process.Kill()

	time.Sleep(5 * time.Second)

	for _, tc := range testCases {
		require.NoError(t, os.WriteFile(configFilePath, []byte(tc.configText), 0o644), "Failed to write config file for test case")

		time.Sleep(2 * time.Second)

		verifyScrapeInterval(t, "http://localhost:"+strconv.Itoa(port), tc.expectedInterval)
		verifyConfigReloadMetric(t, "http://localhost:"+strconv.Itoa(port), tc.expectedMetric)
	}
}

func verifyScrapeInterval(t *testing.T, baseURL, expectedInterval string) {
	resp, err := http.Get(baseURL + "/api/v1/status/config")
	require.NoError(t, err)
	defer resp.Body.Close()

	body, err := io.ReadAll(resp.Body)
	require.NoError(t, err)

	config := struct {
		Data struct {
			YAML string `json:"yaml"`
		} `json:"data"`
	}{}

	require.NoError(t, json.Unmarshal(body, &config))
	require.Contains(t, config.Data.YAML, "scrape_interval: "+expectedInterval)
}

func verifyConfigReloadMetric(t *testing.T, baseURL string, expectedValue float64) {
	resp, err := http.Get(baseURL + "/metrics")
	require.NoError(t, err)
	defer resp.Body.Close()

	body, err := io.ReadAll(resp.Body)
	require.NoError(t, err)

	lines := string(body)
	var actualValue float64
	found := false

	for _, line := range strings.Split(lines, "\n") {

Review comment (bug):
Instead of sleeping in tests, which slows them down more than necessary, I think it's better to use polling via `require.Eventual` (as is done in other Prometheus tests). An added benefit is that the reason for waiting becomes explicit.

Note also that I move killing of Prometheus to `runPrometheusWithLogging`, for full control of cleanup order.

```suggestion

	baseURL := "http://localhost:" + strconv.Itoa(port)

	runPrometheusWithLogging(t, configFilePath, port)
	require.EventuallyWithT(t, func(c *assert.CollectT) {
		resp, err := http.Get(baseURL + "/api/v1/status/config")
		assert.NoError(c, err)
		resp.Body.Close()
		assert.Equal(c, 2, resp.StatusCode/100)
	}, 5*time.Second, 100*time.Millisecond, "Wait for Prometheus to be ready")

	for _, step := range steps {
		require.NoError(t, os.WriteFile(configFilePath, []byte(step.configText), 0o644), "Failed to write config file for test case")

		require.EventuallyWithT(t, func(c *assert.CollectT) {
			verifyScrapeInterval(t, c, baseURL, step.expectedInterval)
			verifyConfigReloadMetric(t, c, "http://localhost:"+strconv.Itoa(port), step.expectedMetric)
		}, 2*time.Second, 100*time.Millisecond, "Wait for Prometheus to reload config")
	}
```

Code after:
	t.Logf("Config file path: %s", configFilePath)

	require.NoError(t, os.WriteFile(configFilePath, []byte(steps[0].configText), 0o644), "Failed to write initial config file")

	port := testutil.RandomUnprivilegedPort(t)
	runPrometheusWithLogging(t, configFilePath, port)

	baseURL := "http://localhost:" + strconv.Itoa(port)
	require.Eventually(t, func() bool {
		resp, err := http.Get(baseURL + "/-/ready")
		if err != nil {
			return false
		}
		defer resp.Body.Close()
		return resp.StatusCode == http.StatusOK
	}, 5*time.Second, 100*time.Millisecond, "Prometheus didn't become ready in time")

	for i, step := range steps {
		t.Logf("Step %d", i)
		require.NoError(t, os.WriteFile(configFilePath, []byte(step.configText), 0o644), "Failed to write config file for step")

		require.Eventually(t, func() bool {
			return verifyScrapeInterval(t, baseURL, step.expectedInterval) &&
				verifyConfigReloadMetric(t, baseURL, step.expectedMetric)
		}, 10*time.Second, 500*time.Millisecond, "Prometheus config reload didn't happen in time")
	}
}

func verifyScrapeInterval(t *testing.T, baseURL, expectedInterval string) bool {
	resp, err := http.Get(baseURL + "/api/v1/status/config")
	require.NoError(t, err)
	defer resp.Body.Close()

	body, err := io.ReadAll(resp.Body)
	require.NoError(t, err)

	config := struct {
		Data struct {
			YAML string `json:"yaml"`
		} `json:"data"`
	}{}

	require.NoError(t, json.Unmarshal(body, &config))
	return strings.Contains(config.Data.YAML, "scrape_interval: "+expectedInterval)
}

func verifyConfigReloadMetric(t *testing.T, baseURL string, expectedValue float64) bool {
	resp, err := http.Get(baseURL + "/metrics")
	require.NoError(t, err)
	defer resp.Body.Close()