-
Notifications
You must be signed in to change notification settings - Fork 238
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add windows integration test #1912
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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/[email protected]/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" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would that become another place where we need to update Go version? What's the process currently to update these? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes, and afaik it's just "search and replace" through the codebase :/ |
||
- name: Run tests | ||
run: make integration-test |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,3 @@ | ||
version: "3" | ||
services: | ||
|
||
mimir: | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,6 +4,7 @@ import ( | |
"fmt" | ||
"os" | ||
"path/filepath" | ||
"runtime" | ||
"strings" | ||
|
||
"github.com/spf13/cobra" | ||
|
@@ -30,26 +31,37 @@ func main() { | |
|
||
func runIntegrationTests(cmd *cobra.Command, args []string) { | ||
defer reportResults() | ||
defer cleanUpEnvironment() | ||
|
||
testFolder := "./tests/" | ||
alloyBinaryPath := "../../../../../build/alloy" | ||
alloyBinary := "build/alloy" | ||
Comment on lines
+35
to
+37
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's switch to using https://pkg.go.dev/path#Join |
||
|
||
if runtime.GOOS == "windows" { | ||
testFolder = "./tests-windows/" | ||
alloyBinaryPath = "..\\..\\..\\..\\..\\build\\alloy.exe" | ||
alloyBinary = "build/alloy.exe" | ||
} else { | ||
setupEnvironment() | ||
defer cleanUpEnvironment() | ||
Comment on lines
+44
to
+45
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why no setup or clean up on windows? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can't use the linux images in the windows runner so we are quite limited in terms of environment. For now not using docker and only running one Alloy to run the windows exporter seems to be sufficient. I can rename the function to "setupDockerEnv" and "cleanupDockerEnv" to make it more clear There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wonder if for clarity we should have two types of tests:
And have them somewhat more clearly separated in the code. Otherwise it becomes a bit convoluted 🤔 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure that it would be really worth it because besides the docker part which is quite small, they would re-use everything. Also I don't think that we need a linux standalone. And maybe one day the windows runner will be able to use WSL 2 and it will work with Mimir linux image or at some point we may want to run a docker windows image and the difference will be gone. |
||
} | ||
|
||
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
} | ||
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) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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", | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
//go:build windows | ||
|
||
package main | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"io" | ||
"net" | ||
"net/http" | ||
"strings" | ||
"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, _, err := handlePost(t, w, r) | ||
|
||
if err != nil { | ||
t.Log("Cancel processing request.") | ||
return | ||
} | ||
|
||
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, error) { | ||
defer r.Body.Close() | ||
data, err := io.ReadAll(r.Body) | ||
|
||
// ignore this error because the server might shutdown while a request is being processed | ||
if opErr, ok := err.(*net.OpError); ok && strings.Contains(opErr.Err.Error(), "use of closed network connection") { | ||
return nil, nil, err | ||
} | ||
|
||
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, nil | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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) | ||
|
||
Comment on lines
+87
to
+89
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instead of sleeps, can we have a retry and backoff (without panic on first failure) to remove all files? |
||
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() | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Doesn't this enable workflow for pull requests? is this temporary?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yep, I did not change it since our discussion, I will remove it before merging to make sure that the test is running properly