diff --git a/.gitattributes b/.gitattributes
index 32f1001b..55f26a16 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1 +1,3 @@
 go.sum linguist-generated
+*       text=auto eol=lf
+*.ps1   text eol=crlf
diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml
index b067e764..565cd630 100644
--- a/.github/workflows/pr.yml
+++ b/.github/workflows/pr.yml
@@ -9,7 +9,7 @@ env:
   GO_VERSION: 1.19
 
 jobs:
-  # Check if there any dirty change for go mod tidy
+  # Check if there is any dirty change for go mod tidy
   go-mod:
     runs-on: ubuntu-latest
     steps:
@@ -41,8 +41,7 @@ jobs:
           # ex:
           # - 1.18beta1 -> 1.18.0-beta.1
           # - 1.18rc1 -> 1.18.0-rc.1
-#          go-version: ${{ env.GO_VERSION }} # todo(ldez) uncomment after the next release v1.48.0
-          go-version: 1.18
+          go-version: ${{ env.GO_VERSION }}
       - name: lint
         uses: golangci/golangci-lint-action@v3.2.0
         with:
@@ -66,7 +65,6 @@ jobs:
           go-version: ${{ env.GO_VERSION }} # test only the latest go version to speed up CI
       - name: Run tests
         run: make.exe test
-        continue-on-error: true
 
   tests-on-macos:
     needs: golangci-lint # run after golangci-lint action to not produce duplicated errors
diff --git a/Makefile b/Makefile
index 9561191f..92f3580e 100644
--- a/Makefile
+++ b/Makefile
@@ -4,17 +4,22 @@
 # enable consistent Go 1.12/1.13 GOPROXY behavior.
 export GOPROXY = https://proxy.golang.org
 
+BINARY = golangci-lint
+ifeq ($(OS),Windows_NT)
+	BINARY := $(BINARY).exe
+endif
+
 # Build
 
-build: golangci-lint
+build: $(BINARY)
 .PHONY: build
 
 build_race:
-	go build -race -o golangci-lint ./cmd/golangci-lint
+	go build -race -o $(BINARY) ./cmd/golangci-lint
 .PHONY: build_race
 
 clean:
-	rm -f golangci-lint
+	rm -f $(BINARY)
 	rm -f test/path
 	rm -f tools/Dracula.itermcolors
 	rm -f tools/goreleaser
@@ -25,7 +30,7 @@ clean:
 # Test
 test: export GOLANGCI_LINT_INSTALLED = true
 test: build
-	GL_TEST_RUN=1 ./golangci-lint run -v
+	GL_TEST_RUN=1 ./$(BINARY) run -v
 	GL_TEST_RUN=1 go test -v -parallel 2 ./...
 .PHONY: test
 
@@ -36,7 +41,7 @@ test_fix: build
 .PHONY: test_fix
 
 test_race: build_race
-	GL_TEST_RUN=1 ./golangci-lint run -v --timeout=5m
+	GL_TEST_RUN=1 ./$(BINARY) run -v --timeout=5m
 .PHONY: test_race
 
 test_linters:
@@ -67,7 +72,7 @@ snapshot: .goreleaser.yml tools/goreleaser
 
 # Non-PHONY targets (real files)
 
-golangci-lint: FORCE
+$(BINARY): FORCE
 	go build -o $@ ./cmd/golangci-lint
 
 tools/goreleaser: export GOFLAGS = -mod=readonly
@@ -87,7 +92,7 @@ tools/Dracula.itermcolors:
 assets/demo.svg: tools/svg-term tools/Dracula.itermcolors
 	./tools/svg-term --cast=183662 --out assets/demo.svg --window --width 110 --height 30 --from 2000 --to 20000 --profile ./tools/Dracula.itermcolors --term iterm2
 
-assets/github-action-config.json: FORCE golangci-lint
+assets/github-action-config.json: FORCE $(BINARY)
 	# go run ./scripts/gen_github_action_config/main.go $@
 	cd ./scripts/gen_github_action_config/; go run ./main.go ../../$@
 
diff --git a/pkg/result/processors/utils.go b/pkg/result/processors/issues.go
similarity index 72%
rename from pkg/result/processors/utils.go
rename to pkg/result/processors/issues.go
index 7108fd3b..8bc3d847 100644
--- a/pkg/result/processors/utils.go
+++ b/pkg/result/processors/issues.go
@@ -1,10 +1,6 @@
 package processors
 
 import (
-	"path/filepath"
-	"regexp"
-	"strings"
-
 	"github.com/pkg/errors"
 
 	"github.com/golangci/golangci-lint/pkg/result"
@@ -48,15 +44,3 @@ func transformIssues(issues []result.Issue, transform func(i *result.Issue) *res
 
 	return retIssues
 }
-
-var separatorToReplace = regexp.QuoteMeta(string(filepath.Separator))
-
-func normalizePathInRegex(path string) string {
-	if filepath.Separator == '/' {
-		return path
-	}
-
-	// This replacing should be safe because "/" are disallowed in Windows
-	// https://docs.microsoft.com/ru-ru/windows/win32/fileio/naming-a-file
-	return strings.ReplaceAll(path, "/", separatorToReplace)
-}
diff --git a/pkg/result/processors/path_prefixer.go b/pkg/result/processors/path_prefixer.go
index 5ce940b3..04ed8312 100644
--- a/pkg/result/processors/path_prefixer.go
+++ b/pkg/result/processors/path_prefixer.go
@@ -1,7 +1,7 @@
 package processors
 
 import (
-	"path"
+	"path/filepath"
 
 	"github.com/golangci/golangci-lint/pkg/result"
 )
@@ -27,7 +27,7 @@ func (*PathPrefixer) Name() string {
 func (p *PathPrefixer) Process(issues []result.Issue) ([]result.Issue, error) {
 	if p.prefix != "" {
 		for i := range issues {
-			issues[i].Pos.Filename = path.Join(p.prefix, issues[i].Pos.Filename)
+			issues[i].Pos.Filename = filepath.Join(p.prefix, issues[i].Pos.Filename)
 		}
 	}
 	return issues, nil
diff --git a/pkg/result/processors/path_prefixer_test.go b/pkg/result/processors/path_prefixer_test.go
index 55fdbea9..9c349d50 100644
--- a/pkg/result/processors/path_prefixer_test.go
+++ b/pkg/result/processors/path_prefixer_test.go
@@ -2,8 +2,10 @@ package processors
 
 import (
 	"go/token"
+	"path/filepath"
 	"testing"
 
+	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 
 	"github.com/golangci/golangci-lint/pkg/result"
@@ -12,26 +14,40 @@ import (
 func TestPathPrefixer_Process(t *testing.T) {
 	paths := func(ps ...string) (issues []result.Issue) {
 		for _, p := range ps {
-			issues = append(issues, result.Issue{Pos: token.Position{Filename: p}})
+			issues = append(issues, result.Issue{Pos: token.Position{Filename: filepath.FromSlash(p)}})
 		}
 		return
 	}
+
 	for _, tt := range []struct {
 		name, prefix string
 		issues, want []result.Issue
 	}{
-		{"empty prefix", "", paths("some/path", "cool"), paths("some/path", "cool")},
-		{"prefix", "ok", paths("some/path", "cool"), paths("ok/some/path", "ok/cool")},
-		{"prefix slashed", "ok/", paths("some/path", "cool"), paths("ok/some/path", "ok/cool")},
+		{
+			name:   "empty prefix",
+			issues: paths("some/path", "cool"),
+			want:   paths("some/path", "cool"),
+		},
+		{
+			name:   "prefix",
+			prefix: "ok",
+			issues: paths("some/path", "cool"),
+			want:   paths("ok/some/path", "ok/cool"),
+		},
+		{
+			name:   "prefix slashed",
+			prefix: "ok/",
+			issues: paths("some/path", "cool"),
+			want:   paths("ok/some/path", "ok/cool"),
+		},
 	} {
 		t.Run(tt.name, func(t *testing.T) {
-			r := require.New(t)
-
 			p := NewPathPrefixer(tt.prefix)
-			got, err := p.Process(tt.issues)
-			r.NoError(err, "prefixer should never error")
 
-			r.Equal(got, tt.want)
+			got, err := p.Process(tt.issues)
+			require.NoError(t, err)
+
+			assert.Equal(t, got, tt.want)
 		})
 	}
 }
diff --git a/pkg/result/processors/path_unix.go b/pkg/result/processors/path_unix.go
new file mode 100644
index 00000000..b0c7c338
--- /dev/null
+++ b/pkg/result/processors/path_unix.go
@@ -0,0 +1,8 @@
+//go:build !windows
+
+package processors
+
+// normalizePathInRegex it's a noop function on Unix.
+func normalizePathInRegex(path string) string {
+	return path
+}
diff --git a/pkg/result/processors/path_windows.go b/pkg/result/processors/path_windows.go
new file mode 100644
index 00000000..7f3e3622
--- /dev/null
+++ b/pkg/result/processors/path_windows.go
@@ -0,0 +1,19 @@
+//go:build windows
+
+package processors
+
+import (
+	"path/filepath"
+	"regexp"
+	"strings"
+)
+
+var separatorToReplace = regexp.QuoteMeta(string(filepath.Separator))
+
+// normalizePathInRegex normalizes path in regular expressions.
+// noop on Unix.
+// This replacing should be safe because "/" are disallowed in Windows
+// https://docs.microsoft.com/windows/win32/fileio/naming-a-file
+func normalizePathInRegex(path string) string {
+	return strings.ReplaceAll(path, "/", separatorToReplace)
+}
diff --git a/pkg/result/processors/severity_rules.go b/pkg/result/processors/severity_rules.go
index 7c9a4c1d..4077b340 100644
--- a/pkg/result/processors/severity_rules.go
+++ b/pkg/result/processors/severity_rules.go
@@ -49,7 +49,8 @@ func createSeverityRules(rules []SeverityRule, prefix string) []severityRule {
 			parsedRule.source = regexp.MustCompile(prefix + rule.Source)
 		}
 		if rule.Path != "" {
-			parsedRule.path = regexp.MustCompile(rule.Path)
+			path := normalizePathInRegex(rule.Path)
+			parsedRule.path = regexp.MustCompile(path)
 		}
 		parsedRules = append(parsedRules, parsedRule)
 	}
diff --git a/pkg/result/processors/skip_files_test.go b/pkg/result/processors/skip_files_test.go
index 92c50ca7..7f16b581 100644
--- a/pkg/result/processors/skip_files_test.go
+++ b/pkg/result/processors/skip_files_test.go
@@ -2,6 +2,8 @@ package processors
 
 import (
 	"go/token"
+	"path/filepath"
+	"strings"
 	"testing"
 
 	"github.com/stretchr/testify/assert"
@@ -33,14 +35,15 @@ func TestSkipFiles(t *testing.T) {
 
 	processAssertEmpty(t, newTestSkipFiles(t, ".*"), newFileIssue("any.go"))
 
-	processAssertEmpty(t, newTestSkipFiles(t, "a/b/c.go"), newFileIssue("a/b/c.go"))
-	processAssertSame(t, newTestSkipFiles(t, "a/b/c.go"), newFileIssue("a/b/d.go"))
+	cleanPath := strings.ReplaceAll(filepath.FromSlash("a/b/c.go"), `\`, `\\`)
+	processAssertEmpty(t, newTestSkipFiles(t, cleanPath), newFileIssue(filepath.FromSlash("a/b/c.go")))
+	processAssertSame(t, newTestSkipFiles(t, cleanPath), newFileIssue(filepath.FromSlash("a/b/d.go")))
 
-	processAssertEmpty(t, newTestSkipFiles(t, ".*\\.pb\\.go"), newFileIssue("a/b.pb.go"))
-	processAssertSame(t, newTestSkipFiles(t, ".*\\.pb\\.go"), newFileIssue("a/b.go"))
+	processAssertEmpty(t, newTestSkipFiles(t, ".*\\.pb\\.go"), newFileIssue(filepath.FromSlash("a/b.pb.go")))
+	processAssertSame(t, newTestSkipFiles(t, ".*\\.pb\\.go"), newFileIssue(filepath.FromSlash("a/b.go")))
 
-	processAssertEmpty(t, newTestSkipFiles(t, ".*\\.pb\\.go$"), newFileIssue("a/b.pb.go"))
-	processAssertSame(t, newTestSkipFiles(t, ".*\\.pb\\.go$"), newFileIssue("a/b.go"))
+	processAssertEmpty(t, newTestSkipFiles(t, ".*\\.pb\\.go$"), newFileIssue(filepath.FromSlash("a/b.pb.go")))
+	processAssertSame(t, newTestSkipFiles(t, ".*\\.pb\\.go$"), newFileIssue(filepath.FromSlash("a/b.go")))
 }
 
 func TestSkipFilesInvalidPattern(t *testing.T) {
diff --git a/test/fix_test.go b/test/fix_test.go
index 85653ddf..1eeb1fef 100644
--- a/test/fix_test.go
+++ b/test/fix_test.go
@@ -12,6 +12,8 @@ import (
 )
 
 func TestFix(t *testing.T) {
+	testshared.SkipOnWindows(t)
+
 	tmpDir := filepath.Join(testdataDir, "fix.tmp")
 	_ = os.RemoveAll(tmpDir) // cleanup previous runs
 
diff --git a/test/linters_test.go b/test/linters_test.go
index 0293a74f..466e983e 100644
--- a/test/linters_test.go
+++ b/test/linters_test.go
@@ -22,6 +22,8 @@ func TestSourcesFromTestdata(t *testing.T) {
 }
 
 func TestTypecheck(t *testing.T) {
+	testshared.SkipOnWindows(t)
+
 	testSourcesFromDir(t, filepath.Join(testdataDir, "notcompiles"))
 }
 
diff --git a/test/output_test.go b/test/output_test.go
index e1127b12..fba1bede 100644
--- a/test/output_test.go
+++ b/test/output_test.go
@@ -3,7 +3,6 @@ package test
 import (
 	"fmt"
 	"os"
-	"path"
 	"path/filepath"
 	"testing"
 
@@ -50,11 +49,11 @@ func TestOutput_Stderr(t *testing.T) {
 		Runner().
 		Install().
 		Run().
-		ExpectHasIssue(expectedJSONOutput)
+		ExpectHasIssue(testshared.NormalizeFilePathInJSON(expectedJSONOutput))
 }
 
 func TestOutput_File(t *testing.T) {
-	resultPath := path.Join(t.TempDir(), "golangci_lint_test_result")
+	resultPath := filepath.Join(t.TempDir(), "golangci_lint_test_result")
 
 	sourcePath := filepath.Join(testdataDir, "misspell.go")
 
@@ -74,7 +73,7 @@ func TestOutput_File(t *testing.T) {
 
 	b, err := os.ReadFile(resultPath)
 	require.NoError(t, err)
-	require.Contains(t, string(b), expectedJSONOutput)
+	require.Contains(t, string(b), testshared.NormalizeFilePathInJSON(expectedJSONOutput))
 }
 
 func TestOutput_Multiple(t *testing.T) {
@@ -94,5 +93,5 @@ func TestOutput_Multiple(t *testing.T) {
 		Run().
 		//nolint:misspell
 		ExpectHasIssue("testdata/misspell.go:6:38: `occured` is a misspelling of `occurred`").
-		ExpectOutputContains(expectedJSONOutput)
+		ExpectOutputContains(testshared.NormalizeFilePathInJSON(expectedJSONOutput))
 }
diff --git a/test/run_test.go b/test/run_test.go
index ea5013c1..d780debe 100644
--- a/test/run_test.go
+++ b/test/run_test.go
@@ -45,7 +45,7 @@ func TestNotExistingDirRun(t *testing.T) {
 		Run().
 		ExpectExitCode(exitcodes.Failure).
 		ExpectOutputContains("cannot find package").
-		ExpectOutputContains("/testdata/no_such_dir")
+		ExpectOutputContains(testshared.NormalizeFileInString("/testdata/no_such_dir"))
 }
 
 func TestSymlinkLoop(t *testing.T) {
diff --git a/test/testdata/depguard_ignore_file_rules.go b/test/testdata/depguard_ignore_file_rules.go
index c1073767..0fcc766c 100644
--- a/test/testdata/depguard_ignore_file_rules.go
+++ b/test/testdata/depguard_ignore_file_rules.go
@@ -1,3 +1,5 @@
+//go:build !windows
+
 //golangcitest:args -Edepguard
 //golangcitest:config_path testdata/configs/depguard_ignore_file_rules.yml
 //golangcitest:expected_exitcode 0
diff --git a/test/testdata/ifshort.go b/test/testdata/ifshort.go
index 20d79759..71906fa8 100644
--- a/test/testdata/ifshort.go
+++ b/test/testdata/ifshort.go
@@ -1,3 +1,5 @@
+//go:build !windows
+
 //golangcitest:args -Eifshort --internal-cmd-test
 package testdata
 
diff --git a/test/testdata/tenv_go118.go b/test/testdata/tenv_go118.go
index bcdf9dca..97a34893 100644
--- a/test/testdata/tenv_go118.go
+++ b/test/testdata/tenv_go118.go
@@ -1,5 +1,4 @@
 //go:build go1.18
-// +build go1.18
 
 //golangcitest:args -Etenv
 package testdata
diff --git a/test/testdata/thelper_go118.go b/test/testdata/thelper_go118.go
index bea2adf9..e83a387a 100644
--- a/test/testdata/thelper_go118.go
+++ b/test/testdata/thelper_go118.go
@@ -1,5 +1,4 @@
 //go:build go1.18
-// +build go1.18
 
 //golangcitest:args -Ethelper
 package testdata
diff --git a/test/testshared/analysis.go b/test/testshared/analysis.go
index be1ed731..9ef29fd8 100644
--- a/test/testshared/analysis.go
+++ b/test/testshared/analysis.go
@@ -46,7 +46,7 @@ func Analyze(t *testing.T, sourcePath string, rawData []byte) {
 
 	var reportData jsonResult
 	err = json.Unmarshal(rawData, &reportData)
-	require.NoError(t, err)
+	require.NoError(t, err, string(rawData))
 
 	for _, issue := range reportData.Issues {
 		checkMessage(t, want, issue.Pos, "diagnostic", issue.FromLinter, issue.Text)
diff --git a/test/testshared/directives.go b/test/testshared/directives.go
index b29f305f..01c011bb 100644
--- a/test/testshared/directives.go
+++ b/test/testshared/directives.go
@@ -15,7 +15,7 @@ import (
 	"github.com/golangci/golangci-lint/pkg/exitcodes"
 )
 
-// RunContext FIXME rename?
+// RunContext the information extracted from directives.
 type RunContext struct {
 	Args           []string
 	ConfigPath     string
@@ -51,11 +51,8 @@ func ParseTestDirectives(tb testing.TB, sourcePath string) *RunContext {
 			break
 		}
 
-		if strings.HasPrefix(line, "//go:build") || strings.HasPrefix(line, "// +build") {
-			parse, err := constraint.Parse(line)
-			require.NoError(tb, err)
-
-			if !parse.Eval(buildTagGoVersion) {
+		if constraint.IsGoBuild(line) {
+			if !evaluateBuildTags(tb, line) {
 				return nil
 			}
 
@@ -120,6 +117,25 @@ func skipMultilineComment(scanner *bufio.Scanner) {
 	}
 }
 
+// evaluateBuildTags Naive implementation of the evaluation of the build tags.
+// Inspired by https://github.com/golang/go/blob/1dcef7b3bdcea4a829ea22c821e6a9484c325d61/src/cmd/go/internal/modindex/build.go#L914-L972
+func evaluateBuildTags(tb testing.TB, line string) bool {
+	parse, err := constraint.Parse(line)
+	require.NoError(tb, err)
+
+	return parse.Eval(func(tag string) bool {
+		if tag == runtime.GOOS {
+			return true
+		}
+
+		if buildTagGoVersion(tag) {
+			return true
+		}
+
+		return false
+	})
+}
+
 func buildTagGoVersion(tag string) bool {
 	vRuntime, err := hcversion.NewVersion(strings.TrimPrefix(runtime.Version(), "go"))
 	if err != nil {
diff --git a/test/testshared/directives_test.go b/test/testshared/directives_test.go
index bee856cd..ffe15c90 100644
--- a/test/testshared/directives_test.go
+++ b/test/testshared/directives_test.go
@@ -1,6 +1,7 @@
 package testshared
 
 import (
+	"runtime"
 	"testing"
 
 	"github.com/stretchr/testify/assert"
@@ -21,3 +22,61 @@ func TestParseTestDirectives(t *testing.T) {
 	}
 	assert.Equal(t, expected, rc)
 }
+
+func Test_evaluateBuildTags(t *testing.T) {
+	testCases := []struct {
+		desc   string
+		tag    string
+		assert assert.BoolAssertionFunc
+	}{
+		{
+			desc:   "",
+			tag:    "// +build go1.18",
+			assert: assert.True,
+		},
+		{
+			desc:   "",
+			tag:    "// +build go1.42",
+			assert: assert.False,
+		},
+		{
+			desc:   "",
+			tag:    "//go:build go1.18",
+			assert: assert.True,
+		},
+		{
+			desc:   "",
+			tag:    "//go:build go1.42",
+			assert: assert.False,
+		},
+		{
+			desc:   "",
+			tag:    "//go:build " + runtime.GOOS,
+			assert: assert.True,
+		},
+		{
+			desc:   "",
+			tag:    "//go:build !wondiws",
+			assert: assert.True,
+		},
+		{
+			desc:   "",
+			tag:    "//go:build wondiws",
+			assert: assert.False,
+		},
+		{
+			desc:   "",
+			tag:    "//go:build go1.18 && " + runtime.GOOS,
+			assert: assert.True,
+		},
+	}
+
+	for _, test := range testCases {
+		test := test
+		t.Run(test.desc, func(t *testing.T) {
+			t.Parallel()
+
+			test.assert(t, evaluateBuildTags(t, test.tag))
+		})
+	}
+}
diff --git a/test/testshared/runner.go b/test/testshared/runner.go
index 0c54af7d..9ce8363f 100644
--- a/test/testshared/runner.go
+++ b/test/testshared/runner.go
@@ -17,8 +17,6 @@ import (
 	"github.com/golangci/golangci-lint/pkg/logutils"
 )
 
-const defaultBinPath = "../golangci-lint"
-
 type RunnerBuilder struct {
 	tb  testing.TB
 	log logutils.Log
@@ -43,7 +41,7 @@ func NewRunnerBuilder(tb testing.TB) *RunnerBuilder {
 	return &RunnerBuilder{
 		tb:                   tb,
 		log:                  log,
-		binPath:              defaultBinPath,
+		binPath:              defaultBinaryName(),
 		command:              "run",
 		allowParallelRunners: true,
 	}
@@ -68,7 +66,10 @@ func (b *RunnerBuilder) WithNoConfig() *RunnerBuilder {
 }
 
 func (b *RunnerBuilder) WithConfigFile(cfgPath string) *RunnerBuilder {
-	b.configPath = cfgPath
+	if cfgPath != "" {
+		b.configPath = filepath.FromSlash(cfgPath)
+	}
+
 	b.noConfig = cfgPath == ""
 
 	return b
@@ -293,17 +294,17 @@ func (r *RunnerResult) ExpectExitCode(possibleCodes ...int) *RunnerResult {
 }
 
 // ExpectOutputRegexp can be called with either a string or compiled regexp
-func (r *RunnerResult) ExpectOutputRegexp(s interface{}) *RunnerResult {
+func (r *RunnerResult) ExpectOutputRegexp(s string) *RunnerResult {
 	r.tb.Helper()
 
-	assert.Regexp(r.tb, s, r.output, "exit code is %d", r.exitCode)
+	assert.Regexp(r.tb, normalizePathInRegex(s), r.output, "exit code is %d", r.exitCode)
 	return r
 }
 
 func (r *RunnerResult) ExpectOutputContains(s string) *RunnerResult {
 	r.tb.Helper()
 
-	assert.Contains(r.tb, r.output, s, "exit code is %d", r.exitCode)
+	assert.Contains(r.tb, r.output, normalizeFilePath(s), "exit code is %d", r.exitCode)
 	return r
 }
 
@@ -317,7 +318,7 @@ func (r *RunnerResult) ExpectOutputNotContains(s string) *RunnerResult {
 func (r *RunnerResult) ExpectOutputEq(s string) *RunnerResult {
 	r.tb.Helper()
 
-	assert.Equal(r.tb, s, r.output, "exit code is %d", r.exitCode)
+	assert.Equal(r.tb, normalizeFilePath(s), r.output, "exit code is %d", r.exitCode)
 	return r
 }
 
@@ -338,10 +339,10 @@ func InstallGolangciLint(tb testing.TB) string {
 			tb.Log(string(output))
 		}
 
-		require.NoError(tb, err, "Can't go install golangci-lint")
+		assert.NoError(tb, err, "Can't go install golangci-lint %s", string(output))
 	}
 
-	abs, err := filepath.Abs(defaultBinPath)
+	abs, err := filepath.Abs(defaultBinaryName())
 	require.NoError(tb, err)
 
 	return abs
diff --git a/test/testshared/runner_test.go b/test/testshared/runner_test.go
index af5fe760..c647e8c1 100644
--- a/test/testshared/runner_test.go
+++ b/test/testshared/runner_test.go
@@ -1,7 +1,7 @@
 package testshared
 
 import (
-	"regexp"
+	"path/filepath"
 	"testing"
 
 	"github.com/stretchr/testify/assert"
@@ -67,7 +67,7 @@ func TestRunnerBuilder_Runner(t *testing.T) {
 					"--internal-cmd-test",
 					"--allow-parallel-runners",
 					"-c",
-					"./testdata/example.yml",
+					filepath.FromSlash("./testdata/example.yml"),
 				},
 			},
 		},
@@ -82,7 +82,7 @@ func TestRunnerBuilder_Runner(t *testing.T) {
 					"--internal-cmd-test",
 					"--allow-parallel-runners",
 					"-c",
-					"testdata/example.yml",
+					filepath.FromSlash("testdata/example.yml"),
 					"-Efoo",
 					"--simple",
 					"--hello=world",
@@ -140,7 +140,7 @@ func TestRunnerBuilder_Runner(t *testing.T) {
 					"--go=1.17",
 					"--internal-cmd-test",
 					"--allow-parallel-runners",
-					"testdata/all.go",
+					filepath.FromSlash("testdata/all.go"),
 				},
 			},
 		},
@@ -149,7 +149,7 @@ func TestRunnerBuilder_Runner(t *testing.T) {
 			builder: NewRunnerBuilder(t).
 				WithRunContext(&RunContext{
 					Args:           []string{"-Efoo", "--simple", "--hello=world"},
-					ConfigPath:     "testdata/example.yml",
+					ConfigPath:     filepath.FromSlash("testdata/example.yml"),
 					ExpectedLinter: "test",
 				}),
 			expected: &Runner{
@@ -160,7 +160,7 @@ func TestRunnerBuilder_Runner(t *testing.T) {
 					"--internal-cmd-test",
 					"--allow-parallel-runners",
 					"-c",
-					"testdata/example.yml",
+					filepath.FromSlash("testdata/example.yml"),
 					"-Efoo",
 					"--simple",
 					"--hello=world",
@@ -219,7 +219,6 @@ func TestRunnerResult_ExpectOutputNotContains(t *testing.T) {
 
 func TestRunnerResult_ExpectOutputRegexp(t *testing.T) {
 	r := &RunnerResult{tb: t, output: "this is an output"}
-	r.ExpectOutputRegexp(regexp.MustCompile(`an.+`))
 	r.ExpectOutputRegexp(`an.+`)
 	r.ExpectOutputRegexp("an")
 }
diff --git a/test/testshared/runner_unix.go b/test/testshared/runner_unix.go
new file mode 100644
index 00000000..c36669b1
--- /dev/null
+++ b/test/testshared/runner_unix.go
@@ -0,0 +1,36 @@
+//go:build !windows
+
+package testshared
+
+import (
+	"path/filepath"
+	"testing"
+)
+
+// SkipOnWindows it's a noop function on Unix.
+func SkipOnWindows(_ testing.TB) {}
+
+// NormalizeFilePathInJSON it's a noop function on Unix.
+func NormalizeFilePathInJSON(in string) string {
+	return in
+}
+
+// NormalizeFileInString it's a noop function on Unix.
+func NormalizeFileInString(in string) string {
+	return in
+}
+
+// defaultBinaryName returns the path to the default binary.
+func defaultBinaryName() string {
+	return filepath.Join("..", "golangci-lint")
+}
+
+// normalizeFilePath it's a noop function on Unix.
+func normalizeFilePath(in string) string {
+	return in
+}
+
+// normalizePathInRegex it's a noop function on Unix.
+func normalizePathInRegex(path string) string {
+	return path
+}
diff --git a/test/testshared/runner_windows.go b/test/testshared/runner_windows.go
new file mode 100644
index 00000000..69afec9c
--- /dev/null
+++ b/test/testshared/runner_windows.go
@@ -0,0 +1,51 @@
+//go:build windows
+
+package testshared
+
+import (
+	"path/filepath"
+	"regexp"
+	"strings"
+	"testing"
+)
+
+// SkipOnWindows skip test on Windows.
+func SkipOnWindows(tb testing.TB) {
+	tb.Skip("not supported on Windows")
+}
+
+// NormalizeFilePathInJSON find Go file path and replace `/` with `\\\\`.
+func NormalizeFilePathInJSON(in string) string {
+	exp := regexp.MustCompile(`(?:^|\b)[\w-/.]+\.go`)
+
+	return exp.ReplaceAllStringFunc(in, func(s string) string {
+		return strings.ReplaceAll(s, "/", "\\\\")
+	})
+}
+
+// NormalizeFileInString normalizes in quoted string, ie. replace `\\` with `\\\\`.
+func NormalizeFileInString(in string) string {
+	return strings.ReplaceAll(filepath.FromSlash(in), "\\", "\\\\")
+}
+
+// defaultBinaryName returns the path to the default binary.
+func defaultBinaryName() string {
+	return filepath.Join("..", "golangci-lint.exe")
+}
+
+// normalizeFilePath find Go file path and replace `/` with `\\`.
+func normalizeFilePath(in string) string {
+	exp := regexp.MustCompile(`(?:^|\b)[\w-/.]+\.go`)
+
+	return exp.ReplaceAllStringFunc(in, func(s string) string {
+		return strings.ReplaceAll(s, "/", "\\")
+	})
+}
+
+// normalizePathInRegex normalizes path in regular expressions.
+// Replace all `/` with `\\`.
+// This replacing should be safe because "/" are disallowed in Windows
+// https://docs.microsoft.com/windows/win32/fileio/naming-a-file
+func normalizePathInRegex(path string) string {
+	return strings.ReplaceAll(path, "/", regexp.QuoteMeta(string(filepath.Separator)))
+}