diff --git a/.github/workflows/integration-tests-win.yml b/.github/workflows/integration-tests-win.yml new file mode 100644 index 0000000000..5716bbf54f --- /dev/null +++ b/.github/workflows/integration-tests-win.yml @@ -0,0 +1,21 @@ +name: Integration Tests Windows +on: + # Run tests on main just so the module and build cache can be saved and used + # in PRs. This speeds up the time it takes to test PRs dramatically. + # (More information on https://docs.github.com/en/enterprise-server@3.6/actions/using-workflows/caching-dependencies-to-speed-up-workflows) + push: + branches: + - main + pull_request: +jobs: + run_tests: + runs-on: windows-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: "1.22" + - name: Run tests + run: make integration-test diff --git a/internal/cmd/integration-tests/docker-compose.yaml b/internal/cmd/integration-tests/docker-compose.yaml index a74285de7d..84d97c49ae 100644 --- a/internal/cmd/integration-tests/docker-compose.yaml +++ b/internal/cmd/integration-tests/docker-compose.yaml @@ -1,4 +1,3 @@ -version: "3" services: mimir: diff --git a/internal/cmd/integration-tests/main.go b/internal/cmd/integration-tests/main.go index b21340ff34..dd38328ce8 100644 --- a/internal/cmd/integration-tests/main.go +++ b/internal/cmd/integration-tests/main.go @@ -4,6 +4,7 @@ import ( "fmt" "os" "path/filepath" + "runtime" "strings" "github.com/spf13/cobra" @@ -32,24 +33,35 @@ func runIntegrationTests(cmd *cobra.Command, args []string) { defer reportResults() defer cleanUpEnvironment() + testFolder := "./tests/" + alloyBinaryPath := "../../../../../build/alloy" + alloyBinary := "build/alloy" + + if runtime.GOOS == "windows" { + testFolder = "./tests-windows/" + alloyBinaryPath = "..\\..\\..\\..\\..\\build\\alloy.exe" + alloyBinary = "build/alloy.exe" + } else { + setupEnvironment() + } + if !skipBuild { - buildAlloy() + buildAlloy(alloyBinary) } - setupEnvironment() if specificTest != "" { fmt.Println("Running", specificTest) - if !filepath.IsAbs(specificTest) && !strings.HasPrefix(specificTest, "./tests/") { - specificTest = "./tests/" + specificTest + if !filepath.IsAbs(specificTest) && !strings.HasPrefix(specificTest, testFolder) { + specificTest = testFolder + specificTest } logChan = make(chan TestLog, 1) - runSingleTest(specificTest, 12345) + runSingleTest(alloyBinaryPath, specificTest, 12345) } else { - testDirs, err := filepath.Glob("./tests/*") + testDirs, err := filepath.Glob(testFolder + "*") if err != nil { panic(err) } logChan = make(chan TestLog, len(testDirs)) - runAllTests() + runAllTests(alloyBinaryPath, testFolder) } } diff --git a/internal/cmd/integration-tests/tests-windows/windows/config.alloy b/internal/cmd/integration-tests/tests-windows/windows/config.alloy new file mode 100644 index 0000000000..82aea7b83e --- /dev/null +++ b/internal/cmd/integration-tests/tests-windows/windows/config.alloy @@ -0,0 +1,23 @@ +prometheus.exporter.windows "default" { } + +prometheus.scrape "default" { + targets = prometheus.exporter.windows.default.targets + forward_to = [prometheus.remote_write.default.receiver] + scrape_interval = "1s" + scrape_timeout = "500ms" +} + +prometheus.remote_write "default" { + endpoint { + url = "http://localhost:9090/receive" + metadata_config { + send_interval = "1s" + } + queue_config { + max_samples_per_send = 100 + } + } + external_labels = { + test_name = "win_metrics", + } +} \ No newline at end of file diff --git a/internal/cmd/integration-tests/tests-windows/windows/windows_metrics_test.go b/internal/cmd/integration-tests/tests-windows/windows/windows_metrics_test.go new file mode 100644 index 0000000000..5eee007b0e --- /dev/null +++ b/internal/cmd/integration-tests/tests-windows/windows/windows_metrics_test.go @@ -0,0 +1,110 @@ +//go:build windows + +package main + +import ( + "context" + "fmt" + "io" + "net/http" + "testing" + "time" + + "github.com/golang/snappy" + "github.com/prometheus/prometheus/prompb" + "github.com/stretchr/testify/require" +) + +// List of expected Windows metrics +var winMetrics = []string{ + "windows_cpu_time_total", // cpu + "windows_cs_logical_processors", // cs + "windows_logical_disk_info", // logical_disk + "windows_net_bytes_received_total", // net + "windows_os_info", // os + "windows_service_info", // service + "windows_system_system_up_time", // system +} + +// TestWindowsMetrics sets up a server to receive remote write requests +// and checks if required metrics appear within a one minute timeout +func TestWindowsMetrics(t *testing.T) { + foundMetrics := make(map[string]bool) + for _, metric := range winMetrics { + foundMetrics[metric] = false + } + + done := make(chan bool) + srv := &http.Server{Addr: ":9090"} + http.HandleFunc("/receive", func(w http.ResponseWriter, r *http.Request) { + ts, _ := handlePost(t, w, r) + + for _, timeseries := range ts { + var metricName string + for _, label := range timeseries.Labels { + if label.Name == "__name__" { + metricName = label.Value + break + } + } + for _, requiredMetric := range winMetrics { + if requiredMetric == metricName && !foundMetrics[requiredMetric] { + foundMetrics[requiredMetric] = true + } + } + } + + allFound := true + for _, found := range foundMetrics { + if !found { + allFound = false + break + } + } + + if allFound { + done <- true + } + }) + + go func() { + if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { + panic(fmt.Errorf("could not start server: %v", err)) + } + }() + defer srv.Shutdown(context.Background()) + + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute) + defer cancel() + + select { + case <-ctx.Done(): + missingMetrics := []string{} + for metric, found := range foundMetrics { + if !found { + missingMetrics = append(missingMetrics, metric) + } + } + if len(missingMetrics) > 0 { + t.Errorf("Timeout reached. Missing metrics: %v", missingMetrics) + } else { + t.Log("All required metrics received.") + } + case <-done: + t.Log("All required metrics received within the timeout.") + } +} + +func handlePost(t *testing.T, _ http.ResponseWriter, r *http.Request) ([]prompb.TimeSeries, []prompb.MetricMetadata) { + defer r.Body.Close() + data, err := io.ReadAll(r.Body) + require.NoError(t, err) + + data, err = snappy.Decode(nil, data) + require.NoError(t, err) + + var req prompb.WriteRequest + err = req.Unmarshal(data) + require.NoError(t, err) + return req.GetTimeseries(), req.Metadata +} diff --git a/internal/cmd/integration-tests/utils.go b/internal/cmd/integration-tests/utils.go index 33ab7beb62..d9f837817a 100644 --- a/internal/cmd/integration-tests/utils.go +++ b/internal/cmd/integration-tests/utils.go @@ -12,10 +12,6 @@ import ( "time" ) -const ( - alloyBinaryPath = "../../../../../build/alloy" -) - type TestLog struct { TestDir string AlloyLog string @@ -34,8 +30,8 @@ func executeCommand(command string, args []string, taskDescription string) { } } -func buildAlloy() { - executeCommand("make", []string{"-C", "../../..", "alloy"}, "Building Alloy") +func buildAlloy(alloyBinary string) { + executeCommand("make", []string{"-C", "../../..", "alloy", fmt.Sprintf("ALLOY_BINARY=%s", alloyBinary)}, "Building Alloy") } func setupEnvironment() { @@ -44,7 +40,7 @@ func setupEnvironment() { time.Sleep(45 * time.Second) } -func runSingleTest(testDir string, port int) { +func runSingleTest(alloyBinaryPath string, testDir string, port int) { info, err := os.Stat(testDir) if err != nil { panic(err) @@ -88,14 +84,17 @@ func runSingleTest(testDir string, port int) { } } + // sleep for a few seconds before deleting the files to make sure that they are not use anymore + time.Sleep(5 * time.Second) + err = os.RemoveAll(filepath.Join(testDir, "data-alloy")) if err != nil { panic(err) } } -func runAllTests() { - testDirs, err := filepath.Glob("./tests/*") +func runAllTests(alloyBinaryPath string, testFolder string) { + testDirs, err := filepath.Glob(testFolder + "*") if err != nil { panic(err) } @@ -106,7 +105,7 @@ func runAllTests() { wg.Add(1) go func(td string, offset int) { defer wg.Done() - runSingleTest(td, port+offset) + runSingleTest(alloyBinaryPath, td, port+offset) }(testDir, i) } wg.Wait()