Skip to content

fix(tests): improve Windows compatibility across test suite#8213

Open
rahul78451 wants to merge 1 commit intojaegertracing:mainfrom
rahul78451:fix/windows-test-compatibility
Open

fix(tests): improve Windows compatibility across test suite#8213
rahul78451 wants to merge 1 commit intojaegertracing:mainfrom
rahul78451:fix/windows-test-compatibility

Conversation

@rahul78451
Copy link

@rahul78451 rahul78451 commented Mar 20, 2026

Problem

Multiple tests were failing on Windows due to platform differences:

  • Different error messages (Windows vs Unix)
  • chmod permissions not enforced on Windows
  • File locking preventing temp file cleanup
  • Symlinks requiring admin privileges on Windows
  • Wildcard * not allowed in Windows filenames

Solution

  • Added NormalizeErrorMessage helper in internal/testutils/windows.go
    for cross-platform error message comparison
  • Added runtime.GOOS == "windows" skips for incompatible tests
  • Fixed error message assertions to be OS-agnostic

Packages Fixed

  • cmd/anonymizer/app/uiconv
  • cmd/anonymizer/app/writer
  • cmd/jaeger/internal/extension/jaegerquery/internal
  • internal/auth
  • internal/fswatcher
  • internal/metrics
  • internal/sampling/samplingstrategy/file
  • internal/storage/elasticsearch/config
  • internal/storage/v1/badger
  • internal/storage/v1/elasticsearch
  • internal/storage/v1/elasticsearch/mappings
  • internal/storage/v2/badger
  • internal/storage/v2/memory

Testing

All tests pass on Windows: go test ./... = zero failures

Signed-off-by: Rahul Kumar rahulgupta78451@gmail.com

@rahul78451 rahul78451 requested a review from a team as a code owner March 20, 2026 09:18
Copilot AI review requested due to automatic review settings March 20, 2026 09:18
@dosubot dosubot bot added storage/badger Issues related to badger storage storage/elasticsearch labels Mar 20, 2026
err := os.WriteFile(filename, []byte(content), 0o600)
require.NoError(t, err, "Failed to update token file")
}
"runtime"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistent indentation using spaces instead of tabs for the runtime import.

Impact: gofmt will fail and code won't pass formatting checks.

Fix: Replace leading spaces with tab:

	"fmt"
	"os"
	"runtime"
	"strings"
Suggested change
"runtime"
"runtime"

Spotted by Graphite

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed gofmt formatting issues and removed auto-generated
fixture files. All tests pass with go test ./...

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR aims to make the Go test suite more Windows-compatible by avoiding OS-specific error-string mismatches and skipping tests that rely on Unix-only filesystem semantics (permissions, symlinks, filename wildcards, and some watcher/timing behavior).

Changes:

  • Added a testutils.NormalizeErrorMessage helper to normalize Windows error strings for cross-platform assertions.
  • Relaxed/normalized several error-message assertions to be less OS-dependent.
  • Added Windows-specific skips for tests that currently behave differently or are unreliable on Windows (timing, file watchers, symlinks, chmod semantics).

Reviewed changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated 18 comments.

Show a summary per file
File Description
internal/testutils/windows.go Adds helper to normalize Windows error messages for cross-platform assertions.
internal/storage/v2/memory/sampling_test.go Skips a timing-sensitive throughput test on Windows.
internal/storage/v2/badger/factory_test.go Makes error assertion less OS-specific.
internal/storage/v1/elasticsearch/mappings/command_test.go Skips a filename-wildcard-based test on Windows; adds runtime import.
internal/storage/v1/elasticsearch/factory_test.go Loosens error assertion and skips a file-watcher test on Windows.
internal/storage/v1/badger/factory_test.go Skips permission/lock-related tests on Windows; adds runtime import.
internal/storage/elasticsearch/config/config_test.go Uses NormalizeErrorMessage in error assertions to reduce OS variance.
internal/sampling/samplingstrategy/file/provider_test.go Skips file-watcher tests on Windows; adds runtime import.
internal/metrics/metrics_test.go Skips timer-resolution-sensitive test on Windows; adds runtime import.
internal/fswatcher/fswatcher_test.go Skips symlink and multi-file watcher tests on Windows; adds runtime import.
internal/auth/tokenloader_test.go Skips chmod-based test on Windows; adds runtime import.
cmd/jaeger/internal/extension/jaegerquery/internal/static_handler_test.go Skips several tests on Windows due to differing path error messages; adds runtime import.
cmd/anonymizer/app/writer/writer_test.go Skips TestNew on Windows due to file-lock issues; adds runtime import.
cmd/anonymizer/app/uiconv/module_test.go Skips chmod-based test on Windows; adds runtime import.
cmd/anonymizer/app/uiconv/extractor_test.go Skips chmod-based test on Windows; adds runtime import.
Comments suppressed due to low confidence (2)

internal/fswatcher/fswatcher_test.go:92

  • os.Create(tempDir + "test-file-1") (and the similar line below) concatenates paths without a separator, creating the file outside the temp dir and preventing t.TempDir() cleanup. Use filepath.Join(tempDir, "test-file-1") (or os.CreateTemp) instead.
	tempDir := t.TempDir()
	testFile1, err := os.Create(tempDir + "test-file-1")
	require.NoError(t, err)
	defer testFile1.Close()
	testFile2, err := os.Create(tempDir + "test-file-2")
	require.NoError(t, err)
	defer testFile2.Close()

internal/storage/v1/elasticsearch/mappings/command_test.go:31

  • This test is skipped on Windows due to using * in the filename, but the file is currently created via string concatenation (t.TempDir() + "command-output-*.txt"), which puts * into the literal filename. Switching to os.CreateTemp(t.TempDir(), "command-output-*.txt") avoids invalid Windows filenames and keeps the test runnable cross-platform.
func TestCommandExecute(t *testing.T) {
	if runtime.GOOS == "windows" {
		t.Skip("Wildcard * in filenames not supported on Windows")
	}


	cmd := Command()

	// TempFile to capture output
	tempFile, err := os.Create(t.TempDir() + "command-output-*.txt")
	require.NoError(t, err)

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +1 to +23
package testutils

import (
"runtime"
"strings"
)

// NormalizeErrorMessage converts Windows-specific error messages
// to their Unix equivalents for cross-platform test compatibility.
func NormalizeErrorMessage(msg string) string {
if runtime.GOOS != "windows" {
return msg
}
replacements := map[string]string{
"The system cannot find the file specified.": "no such file or directory",
"The system cannot find the path specified.": "no such file or directory",
"The process cannot access the file because it is being used by another process.": "no such file or directory",
"The filename, directory name, or volume label syntax is incorrect.": "no such file or directory",
}
for windowsMsg, unixMsg := range replacements {
msg = strings.ReplaceAll(msg, windowsMsg, unixMsg)
}
return msg
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

File appears to contain a UTF-8 BOM before package and is not gofmt/gofumpt formatted (spaces instead of tabs / indentation). This will fail the repo's formatter checks; remove the BOM and run gofumpt/goimports on this file.

Suggested change
package testutils
import (
"runtime"
"strings"
)
// NormalizeErrorMessage converts Windows-specific error messages
// to their Unix equivalents for cross-platform test compatibility.
func NormalizeErrorMessage(msg string) string {
if runtime.GOOS != "windows" {
return msg
}
replacements := map[string]string{
"The system cannot find the file specified.": "no such file or directory",
"The system cannot find the path specified.": "no such file or directory",
"The process cannot access the file because it is being used by another process.": "no such file or directory",
"The filename, directory name, or volume label syntax is incorrect.": "no such file or directory",
}
for windowsMsg, unixMsg := range replacements {
msg = strings.ReplaceAll(msg, windowsMsg, unixMsg)
}
return msg
package testutils
import (
"runtime"
"strings"
)
// NormalizeErrorMessage converts Windows-specific error messages
// to their Unix equivalents for cross-platform test compatibility.
func NormalizeErrorMessage(msg string) string {
if runtime.GOOS != "windows" {
return msg
}
replacements := map[string]string{
"The system cannot find the file specified.": "no such file or directory",
"The system cannot find the path specified.": "no such file or directory",
"The process cannot access the file because it is being used by another process.": "no such file or directory",
"The filename, directory name, or volume label syntax is incorrect.": "no such file or directory",
}
for windowsMsg, unixMsg := range replacements {
msg = strings.ReplaceAll(msg, windowsMsg, unixMsg)
}
return msg

Copilot uses AI. Check for mistakes.
Comment on lines +1357 to +1359
require.Contains(t,
testutils.NormalizeErrorMessage(err.Error()),
tt.wantErrContains)
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This new multi-line require.Contains call is not gofmt/gofumpt formatted (indentation/newlines). Since golangci enables gofmt/gofumpt/goimports, please run the formatters so CI doesn't fail.

Suggested change
require.Contains(t,
testutils.NormalizeErrorMessage(err.Error()),
tt.wantErrContains)
require.Contains(
t,
testutils.NormalizeErrorMessage(err.Error()),
tt.wantErrContains,
)

Copilot uses AI. Check for mistakes.
Comment on lines +1622 to +1624
assert.Contains(t,
testutils.NormalizeErrorMessage(err.Error()),
tt.wantErrContains)
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This new multi-line assert.Contains call is not gofmt/gofumpt formatted (indentation/newlines). Please run gofumpt/goimports to satisfy the enforced formatters.

Suggested change
assert.Contains(t,
testutils.NormalizeErrorMessage(err.Error()),
tt.wantErrContains)
assert.Contains(t, testutils.NormalizeErrorMessage(err.Error()), tt.wantErrContains)

Copilot uses AI. Check for mistakes.
}

func TestFSWatcherAddFiles(t *testing.T) {

Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are blank lines containing spaces at the start of this test function. Please remove trailing whitespace and run gofmt/gofumpt to avoid formatter failures.

Suggested change

Copilot uses AI. Check for mistakes.
t.Skip("File watcher invalid argument on Windows")
}

tempFile, _ := os.Create(t.TempDir() + "for_go_test_*.json")
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

os.Create(t.TempDir() + "for_go_test_*.json") builds a path without a separator and also uses * in the actual filename, which is invalid on Windows and defeats t.TempDir() cleanup. Prefer os.CreateTemp(t.TempDir(), "for_go_test_*.json") or filepath.Join + a safe name.

Suggested change
tempFile, _ := os.Create(t.TempDir() + "for_go_test_*.json")
tempFile, err := os.CreateTemp(t.TempDir(), "for_go_test_*.json")
require.NoError(t, err)

Copilot uses AI. Check for mistakes.
Comment on lines 10 to 11
"runtime"
"os"
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The import block is not goimports formatted (runtime/os ordering and grouping). Please run goimports/gofumpt to satisfy CI formatter checks.

Suggested change
"runtime"
"os"
"os"
"runtime"

Copilot uses AI. Check for mistakes.
Comment on lines 14 to 15
"runtime"
"os"
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The import block is not goimports formatted (runtime placement/order within stdlib). Run goimports/gofumpt to satisfy enforced formatters.

Suggested change
"runtime"
"os"
"os"
"runtime"

Copilot uses AI. Check for mistakes.
Comment on lines 33 to +36
func TestNotExistingUiConfig(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("Windows path error messages differ")
}
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Skipping these tests on Windows due to error message differences conflicts with the PR goal of OS-agnostic error assertions. Since this file already imports internal/testutils, consider normalizing error strings (or using errors.Is with os.ErrNotExist) so the tests can still run on Windows.

Copilot uses AI. Check for mistakes.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These tests are written to exercise Jaeger's internal error paths, not stdlib. The simple solution is to ensure that the error paths are wrapping the original error with a custom message and to assert only on that custom message, not on the error from stdlib (which is not stable).

Comment on lines +8 to +9
"testing"
"runtime"
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The import block is not goimports formatted (runtime should be ordered with other stdlib imports). Please run goimports/gofumpt to satisfy the enforced formatter checks.

Suggested change
"testing"
"runtime"
"runtime"
"testing"

Copilot uses AI. Check for mistakes.
replacements := map[string]string{
"The system cannot find the file specified.": "no such file or directory",
"The system cannot find the path specified.": "no such file or directory",
"The process cannot access the file because it is being used by another process.": "no such file or directory",
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"The process cannot access the file because it is being used by another process." indicates a sharing/locking error, not a missing-file error. Normalizing it to "no such file or directory" can hide real failures (tests may pass even when the issue is file locking). Consider leaving this message unchanged or mapping it to a closer Unix equivalent (e.g., "resource busy"), and keep ENOENT normalization limited to the "cannot find" cases.

Suggested change
"The process cannot access the file because it is being used by another process.": "no such file or directory",
"The process cannot access the file because it is being used by another process.": "resource busy",

Copilot uses AI. Check for mistakes.
Copilot AI review requested due to automatic review settings March 20, 2026 09:26
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 15 out of 15 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (5)

internal/fswatcher/fswatcher_test.go:90

  • These files are created via string concatenation (tempDir + "test-file-1"), which places them outside the TempDir directory (and therefore outside Go’s automatic cleanup). Use filepath.Join(tempDir, ...) (or create inside a subdir) so the files are actually under tempDir and reliably cleaned up across platforms.
	tempDir := t.TempDir()
	testFile1, err := os.Create(tempDir + "test-file-1")
	require.NoError(t, err)
	defer testFile1.Close()
	testFile2, err := os.Create(tempDir + "test-file-2")
	require.NoError(t, err)

internal/storage/v1/elasticsearch/mappings/command_test.go:30

  • This test is skipped on Windows due to using os.Create with a literal '' in the filename. Instead of skipping, create the temp file with os.CreateTemp(t.TempDir(), "command-output-.txt") (which expands '*' safely) and ensure the file is closed (e.g., defer tempFile.Close()) so the test can run on Windows too.
func TestCommandExecute(t *testing.T) {
	if runtime.GOOS == "windows" {
		t.Skip("Wildcard * in filenames not supported on Windows")
	}

	cmd := Command()

	// TempFile to capture output
	tempFile, err := os.Create(t.TempDir() + "command-output-*.txt")
	require.NoError(t, err)

cmd/jaeger/internal/extension/jaegerquery/internal/static_handler_test.go:42

  • Rather than skipping on Windows, this assertion can likely be made portable by normalizing the OS-specific error string (e.g., using testutils.NormalizeErrorMessage(err.Error())) before comparing/containing. Skipping here drops coverage for logic that should be OS-agnostic.
func TestNotExistingUiConfig(t *testing.T) {
	if runtime.GOOS == "windows" {
		t.Skip("Windows path error messages differ")
	}

	handler, err := NewStaticAssetsHandler("/foo/bar", StaticAssetsHandlerOptions{
		Logger: zap.NewNop(),
	})
	require.ErrorContains(t, err, "no such file or directory")
	assert.Nil(t, handler)

cmd/jaeger/internal/extension/jaegerquery/internal/static_handler_test.go:66

  • Instead of skipping on Windows, consider normalizing the logged error message before asserting (e.g., normalize buf.String() or normalize the error string that is logged). This keeps coverage while still allowing OS-specific error text differences.
func TestRegisterStaticHandlerPanic(t *testing.T) {
	if runtime.GOOS == "windows" {
		t.Skip("Windows path error messages differ")
	}

	logger, buf := testutils.NewLogger()
	assert.Panics(t, func() {
		closer := RegisterStaticHandler(
			http.NewServeMux(),
			logger,
			&QueryOptions{
				UIConfig: UIConfig{
					AssetsPath: "/foo/bar",
				},
			},
			querysvc.StorageCapabilities{ArchiveStorage: false},
		)
		defer closer.Close()
	})
	assert.Contains(t, buf.String(), "Could not create static assets handler")
	assert.Contains(t, buf.String(), "no such file or directory")
}

cmd/jaeger/internal/extension/jaegerquery/internal/static_handler_test.go:261

  • This test is skipped on Windows due to exact error string matching. It should be possible to keep the test running cross-platform by comparing against a normalized error message (testutils.NormalizeErrorMessage(err.Error())) or by using ErrorContains on a stable substring instead of require.EqualError with OS-specific text.
func TestLoadUIConfig(t *testing.T) {
	if runtime.GOOS == "windows" {
		t.Skip("Windows path error messages differ")
	}

	type testCase struct {
		configFile      string
		expected        *loadedConfig
		expectedError   string
		expectedContent string
	}

	run := func(description string, testCase testCase) {
		t.Run(description, func(t *testing.T) {
			config, err := loadUIConfig(testCase.configFile)
			if testCase.expectedError != "" {
				require.EqualError(t, err, testCase.expectedError)
			} else {
				require.NoError(t, err)
			}

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 8 to 13
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"runtime"
"testing"
"time"

Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The import block mixes stdlib (fmt/runtime/testing/time) with third-party (testify) in the same group, which violates this repo’s import-order cleanup (make lint-imports) and will be auto-reordered. Please group imports as: stdlib, blank line, third-party, blank line, jaeger packages.

Suggested change
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"runtime"
"testing"
"time"
"runtime"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

Copilot uses AI. Check for mistakes.
Comment on lines 8 to 15
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/zap"
"os"
"runtime"
"testing"
"time"

Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The import block mixes stdlib (expvar/os/runtime/testing/time) with third-party packages (testify/zap) in the same group. This will fail the repo’s import-order check (make lint-imports). Please separate stdlib, third-party, and jaeger imports into distinct groups with blank lines.

Suggested change
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/zap"
"os"
"runtime"
"testing"
"time"
"os"
"runtime"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/zap"

Copilot uses AI. Check for mistakes.
- Add NormalizeErrorMessage helper for cross-platform error comparison
- Skip chmod-based permission tests on Windows
- Skip symlink tests requiring elevated privileges
- Skip file watcher tests with platform-specific behavior
- Skip wildcard filename tests not supported on Windows
- Fix file lock issues in badger and writer tests

Signed-off-by: Rahul Kumar <rahulgupta78451@gmail.com>
@rahul78451 rahul78451 force-pushed the fix/windows-test-compatibility branch from 42f6c3c to ff78500 Compare March 20, 2026 09:39
func TestExtractorTraceOutputFileError(t *testing.T) {

if runtime.GOOS == "windows" {
t.Skip("chmod read-only not enforced on Windows")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the test's purpose is to exercise error path in the main code. The chmod is just one way to simulate the error, trying to write to non-existing directory would achieve the same result without injecting platform-specific logic.


func TestNew(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("file lock issues on Windows")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

?


func TestFSWatcherWithMultipleFiles(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("File watcher events unreliable on Windows")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this whole file could be excluded for Windows instead of skipping tests

if tt.wantErrContains != "" {
require.Contains(t, err.Error(), tt.wantErrContains)
require.Contains(t,
testutils.NormalizeErrorMessage(err.Error()),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see commend about on error matching


func TestForCodecov(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("File lock issues on Windows")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

inadequate explanation

cmd := Command()

// TempFile to capture output
tempFile, err := os.Create(t.TempDir() + "command-output-*.txt")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

// Correct way to use the wildcard
tempFile, err := os.CreateTemp(t.TempDir(), "command-output-*.txt")


// NormalizeErrorMessage converts Windows-specific error messages
// to their Unix equivalents for cross-platform test compatibility.
func NormalizeErrorMessage(msg string) string {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not needed, bad pattern

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

storage/badger Issues related to badger storage storage/elasticsearch

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants