diff --git a/.github/workflows/lint-test.yml b/.github/workflows/lint-test.yml new file mode 100644 index 0000000..04561aa --- /dev/null +++ b/.github/workflows/lint-test.yml @@ -0,0 +1,16 @@ +name: CI +on: + # Enable manually triggering this workflow via the API or web UI + workflow_dispatch: + push: + branches: + - main + tags: + - v* + pull_request: + +jobs: + checks: + uses: grafana/k6-ci/.github/workflows/all.yml@main + with: + skip-extension-testing: true diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml deleted file mode 100644 index e08c21b..0000000 --- a/.github/workflows/lint.yml +++ /dev/null @@ -1,43 +0,0 @@ -name: lint - -on: - push: - branches: - - main - paths-ignore: - - "docs/**" - - README.md - - "releases/**" - pull_request: - branches: - - main - -permissions: - contents: read - -jobs: - lint: - name: lint - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v5 - with: - fetch-depth: 0 - persist-credentials: false - - name: Setup go - uses: actions/setup-go@v6 - with: - go-version-file: go.mod - cache: false - - name: Set golangci-lint version - run: | - LINT_VERSION=$(head -n 1 ".golangci.yml" | tr -d '# ') - echo "LINT_VERSION=${LINT_VERSION}" >> $GITHUB_ENV - - name: Go linter - uses: golangci/golangci-lint-action@55c2c1448f86e01eaae002a5a3a9624417608d84 # v6.5.2 - with: - version: ${{ env.LINT_VERSION }} - args: --timeout=30m - install-mode: binary - verify: false diff --git a/.github/workflows/sync-k6-deps.yaml b/.github/workflows/sync-k6-deps.yaml index 8eee7b6..4b66172 100644 --- a/.github/workflows/sync-k6-deps.yaml +++ b/.github/workflows/sync-k6-deps.yaml @@ -22,7 +22,7 @@ jobs: token: ${{ secrets.GITHUB_TOKEN }} - uses: actions/setup-go@v6 with: - go-version: "1.23.x" + go-version: "1.26.x" cache: false - name: Sync dependencies run: | diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml deleted file mode 100644 index e37a61d..0000000 --- a/.github/workflows/test.yml +++ /dev/null @@ -1,44 +0,0 @@ -name: test - -on: - push: - branches: - - main - paths-ignore: - - "docs/**" - - README.md - - "releases/**" - pull_request: - branches: - - main - -permissions: - contents: read - -jobs: - test: - name: Test - strategy: - matrix: - platform: - - ubuntu-latest - - macos-latest - - windows-latest - runs-on: ${{matrix.platform}} - steps: - - name: Install Go - uses: actions/setup-go@v6 - with: - go-version: "1.25.8" - - - name: Checkout code - uses: actions/checkout@v5 - with: - fetch-depth: 0 - persist-credentials: false - - - name: Test - run: go test -race -count 1 ./... - - - name: Integration Tests - run: go test -tags integration -race -count 1 ./integration/... diff --git a/.golangci.yml b/.golangci.yml deleted file mode 100644 index 2387f77..0000000 --- a/.golangci.yml +++ /dev/null @@ -1,135 +0,0 @@ -# v1.64.8 -# Please don't remove the first line. It uses in CI to determine the golangci version -run: - timeout: 5m - -issues: - # Maximum issues count per one linter. Set to 0 to disable. Default is 50. - max-issues-per-linter: 0 - # Maximum count of issues with the same text. Set to 0 to disable. Default is 3. - max-same-issues: 0 - - # We want to try and improve the comments in the k6 codebase, so individual - # non-golint items from the default exclusion list will gradually be added - # to the exclude-rules below - exclude-use-default: false - - exclude-rules: - # Exclude duplicate code and function length and complexity checking in test - # files (due to common repeats and long functions in test code) - - path: _(test|gen)\.go - linters: - - cyclop - - dupl - - gocognit - - funlen - - lll - - gosec - - noctx - - gochecknoglobals - - revive - - path: js\/modules\/k6\/http\/.*_test\.go - linters: - # k6/http module's tests are quite complex because they often have several nested levels. - # The module is in maintainance mode, so we don't intend to port the tests to a parallel version. - - paralleltest - - tparallel - - linters: - - staticcheck # Tracked in https://github.com/grafana/xk6-grpc/issues/14 - text: "The entire proto file grpc/reflection/v1alpha/reflection.proto is marked as deprecated." - - linters: - - forbidigo - text: 'use of `os\.(SyscallError|Signal|Interrupt)` forbidden' - -linters-settings: - nolintlint: - # Disable to ensure that nolint directives don't have a leading space. Default is true. - allow-leading-space: false - exhaustive: - default-signifies-exhaustive: true - govet: - shadow: true - cyclop: - max-complexity: 25 - maligned: - suggest-new: true - dupl: - threshold: 150 - goconst: - min-len: 10 - min-occurrences: 4 - funlen: - lines: 80 - statements: 60 - forbidigo: - forbid: - - '^(fmt\\.Print(|f|ln)|print|println)$' - # Forbid everything in syscall except the uppercase constants - - '^syscall\.[^A-Z_]+$(# Using anything except constants from the syscall package is forbidden )?' - -linters: - disable-all: true - enable: - - asasalint - - asciicheck - - bidichk - - bodyclose - - contextcheck - - cyclop - - dogsled - - dupl - - durationcheck - - errcheck - - errchkjson - - errname - - errorlint - - exhaustive - - forbidigo - - forcetypeassert - - funlen - - gocheckcompilerdirectives - - gochecknoglobals - - gocognit - - goconst - - gocritic - - gofmt - - gofumpt - - goimports - - gomoddirectives - - goprintffuncname - - gosec - - gosimple - - govet - - importas - - ineffassign - - interfacebloat - - lll - - makezero - - misspell - - nakedret - - nestif - - nilerr - - nilnil - - noctx - - nolintlint - - nosprintfhostport - - paralleltest - - prealloc - - predeclared - - promlinter - - revive - - reassign - - rowserrcheck - - sqlclosecheck - - staticcheck - - stylecheck - - tenv - - tparallel - - typecheck - - unconvert - - unparam - - unused - - usestdlibvars - - wastedassign - - whitespace - fast: false diff --git a/Makefile b/Makefile index 2be76f6..5c2fb73 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ work_dir = $(shell pwd) -golangci_version = $(shell head -n 1 .golangci.yml | tr -d '\# ') +GOLANGCI_CONFIG ?= .golangci.yml all: build @@ -12,11 +12,21 @@ format: gofmt -w -s . gofumpt -w . -# Running with -buildvcs=false works around the issue of `go list all` failing when git, which runs as root inside -# the container, refuses to operate on the disruptor source tree as it is not owned by the same user (root). +## linter-config: Checks if the linter config exists, if not, downloads it from the main k6 repository. +.PHONY: linter-config +linter-config: + test -s "${GOLANGCI_CONFIG}" || (echo "No linter config, downloading from main k6 repository..." && curl --silent --show-error --fail --no-location https://raw.githubusercontent.com/grafana/k6/master/.golangci.yml --output "${GOLANGCI_CONFIG}") + +## check-linter-version: Checks if the linter version is the same as the one specified in the linter config. +.PHONY: check-linter-version +check-linter-version: + (golangci-lint version | grep "version $(shell head -n 1 .golangci.yml | tr -d '\# ')") || echo "Your installation of golangci-lint is different from the one that is specified in k6's linter config (there it's $(shell head -n 1 .golangci.yml | tr -d '\# ')). Results could be different in the CI." + +## lint: Runs the linters. .PHONY: lint -lint: format - docker run --rm -v $(work_dir):/src -w /src -e GOFLAGS=-buildvcs=false golangci/golangci-lint:$(golangci_version) golangci-lint run +lint: linter-config check-linter-version + echo "Running linters..." + golangci-lint run ./... .PHONY: test test: diff --git a/build.go b/build.go index 42edb8a..99fb2eb 100644 --- a/build.go +++ b/build.go @@ -58,15 +58,15 @@ func (a Artifact) PrintSummary() string { func (a Artifact) toString(details bool, sep string) string { buffer := &bytes.Buffer{} if details { - buffer.WriteString(fmt.Sprintf("id: %s%s", a.ID, sep)) + fmt.Fprintf(buffer, "id: %s%s", a.ID, sep) } - buffer.WriteString(fmt.Sprintf("platform: %s%s", a.Platform, sep)) + fmt.Fprintf(buffer, "platform: %s%s", a.Platform, sep) for dep, version := range a.Dependencies { - buffer.WriteString(fmt.Sprintf("%s:%q%s", dep, version, sep)) + fmt.Fprintf(buffer, "%s:%q%s", dep, version, sep) } - buffer.WriteString(fmt.Sprintf("checksum: %s%s", a.Checksum, sep)) + fmt.Fprintf(buffer, "checksum: %s%s", a.Checksum, sep) if details { - buffer.WriteString(fmt.Sprintf("url: %s%s", a.URL, sep)) + fmt.Fprintf(buffer, "url: %s%s", a.URL, sep) } return buffer.String() } diff --git a/cmd/k6build/main.go b/cmd/k6build/main.go index b43c137..f59e5d8 100644 --- a/cmd/k6build/main.go +++ b/cmd/k6build/main.go @@ -14,6 +14,6 @@ func main() { err := root.Execute() if err != nil { fmt.Printf("%s\n", err.Error()) - os.Exit(1) + os.Exit(1) //nolint:forbidigo } } diff --git a/cmd/local/local.go b/cmd/local/local.go index a4610f1..8c7fd0d 100644 --- a/cmd/local/local.go +++ b/cmd/local/local.go @@ -99,7 +99,7 @@ func New() *cobra.Command { //nolint:funlen if err != nil { return fmt.Errorf("malformed URL %w", err) } - artifactBinary, err := os.Open(binaryURL.Path) + artifactBinary, err := os.Open(binaryURL.Path) //nolint:gosec,forbidigo if err != nil { return fmt.Errorf("opening output file %w", err) } @@ -107,7 +107,7 @@ func New() *cobra.Command { //nolint:funlen _ = artifactBinary.Close() }() - binary, err := os.OpenFile(output, os.O_WRONLY|os.O_CREATE, 0o755) //nolint:gosec + binary, err := os.OpenFile(output, os.O_WRONLY|os.O_CREATE, 0o755) //nolint:gosec,forbidigo if err != nil { return fmt.Errorf("opening output file %w", err) } @@ -127,9 +127,9 @@ func New() *cobra.Command { //nolint:funlen _ = cmd.MarkFlagRequired("platform") cmd.Flags().StringVarP(&config.Catalog, "catalog", "c", catalog.DefaultCatalogURL, "dependencies catalog") cmd.Flags().StringVarP(&config.StoreDir, "store-dir", "f", "/tmp/k6build/store", "object store dir") - cmd.Flags().BoolVarP(&config.Opts.Verbose, "verbose", "v", false, "print build process output") + cmd.Flags().BoolVarP(&config.Verbose, "verbose", "v", false, "print build process output") cmd.Flags().BoolVarP(&config.CopyGoEnv, "copy-go-env", "g", true, "copy go environment") - cmd.Flags().StringToStringVarP(&config.Opts.Env, "env", "e", nil, "build environment variables") + cmd.Flags().StringToStringVarP(&config.Env, "env", "e", nil, "build environment variables") cmd.Flags().StringVarP(&output, "output", "o", "k6", "path to put the binary as an executable.") cmd.Flags().BoolVarP(&quiet, "quiet", "q", false, "don't print artifact's details") cmd.Flags().BoolVar( diff --git a/cmd/server/server.go b/cmd/server/server.go index 01fcd7f..ad5764b 100644 --- a/cmd/server/server.go +++ b/cmd/server/server.go @@ -360,7 +360,7 @@ func getLogger(logLevel string) (*slog.Logger, error) { return slog.New( slog.NewTextHandler( - os.Stderr, + os.Stderr, //nolint:forbidigo &slog.HandlerOptions{ Level: ll, }, diff --git a/cmd/store/store.go b/cmd/store/store.go index f23afc9..9360932 100644 --- a/cmd/store/store.go +++ b/cmd/store/store.go @@ -75,7 +75,7 @@ func New() *cobra.Command { log := slog.New( slog.NewTextHandler( - os.Stderr, + os.Stderr, //nolint:forbidigo &slog.HandlerOptions{ Level: ll, }, diff --git a/internal/clireadme/generate.go b/internal/clireadme/generate.go index 4d1ab71..d430184 100644 --- a/internal/clireadme/generate.go +++ b/internal/clireadme/generate.go @@ -71,7 +71,7 @@ func printLong(buff *bytes.Buffer, cmd *cobra.Command, offset int) { func printUseLine(buff *bytes.Buffer, cmd *cobra.Command) { if cmd.Runnable() { - buff.WriteString(fmt.Sprintf("```\n%s\n```\n\n", cmd.UseLine())) + fmt.Fprintf(buff, "```\n%s\n```\n\n", cmd.UseLine()) } } @@ -100,7 +100,7 @@ func printExamples(buff *bytes.Buffer, cmd *cobra.Command, offset int) { } buff.WriteString(heading(offset, 2, "Examples")) - buff.WriteString(fmt.Sprintf("```\n%s\n```\n\n", cmd.Example)) + fmt.Fprintf(buff, "```\n%s\n```\n\n", cmd.Example) } func printAdditionalHelpTopics(buff *bytes.Buffer, cmd *cobra.Command, offset int) { @@ -140,7 +140,7 @@ func printSeeAlso(buf *bytes.Buffer, cmd *cobra.Command, offset int) { parent := cmd.Parent() pname := parent.CommandPath() link := strings.ReplaceAll(pname, " ", "-") - buf.WriteString(fmt.Sprintf("* [%s](#%s)\t - %s\n", pname, link, parent.Short)) + fmt.Fprintf(buf, "* [%s](#%s)\t - %s\n", pname, link, parent.Short) } firstChild := true @@ -156,7 +156,7 @@ func printSeeAlso(buf *bytes.Buffer, cmd *cobra.Command, offset int) { cname := cmd.CommandPath() + " " + child.Name() link := strings.ReplaceAll(cname, " ", "-") - buf.WriteString(fmt.Sprintf("* [%s](#%s)\t - %s\n", cname, link, child.Short)) + fmt.Fprintf(buf, "* [%s](#%s)\t - %s\n", cname, link, child.Short) } buf.WriteString("\n") diff --git a/internal/clireadme/main.go b/internal/clireadme/main.go index a10e011..8f1f07c 100644 --- a/internal/clireadme/main.go +++ b/internal/clireadme/main.go @@ -10,14 +10,14 @@ import ( // Main updates the markdown documentation recursively based on cobra Command. func Main(root *cobra.Command, headingOffset int) { - exe := filepath.Base(os.Args[0]) - if len(os.Args) != 2 { //nolint:gomnd - fmt.Fprintf(os.Stderr, "usage: %s filename", exe) - os.Exit(1) + exe := filepath.Base(os.Args[0]) //nolint:forbidigo + if len(os.Args) != 2 { //nolint:gomnd,forbidigo + fmt.Fprintf(os.Stderr, "usage: %s filename", exe) //nolint:gosec,forbidigo + os.Exit(1) //nolint:forbidigo } - if err := Update(root, os.Args[1], headingOffset); err != nil { - fmt.Fprintf(os.Stderr, "%s: error: %s\n", exe, err) - os.Exit(1) + if err := Update(root, os.Args[1], headingOffset); err != nil { //nolint:forbidigo + fmt.Fprintf(os.Stderr, "%s: error: %s\n", exe, err) //nolint:gosec,forbidigo + os.Exit(1) //nolint:forbidigo } } diff --git a/internal/clireadme/update.go b/internal/clireadme/update.go index c384db2..bf52fd5 100644 --- a/internal/clireadme/update.go +++ b/internal/clireadme/update.go @@ -19,7 +19,7 @@ func Update(root *cobra.Command, filename string, headingOffset int) error { filename = filepath.Clean(filename) - src, err := os.ReadFile(filename) + src, err := os.ReadFile(filename) //nolint:gosec,forbidigo if err != nil { return err } @@ -32,5 +32,5 @@ func Update(root *cobra.Command, filename string, headingOffset int) error { src = res } - return os.WriteFile(filename, src, 0o600) //nolint:gomnd + return os.WriteFile(filename, src, 0o600) //nolint:gomnd,gosec,forbidigo } diff --git a/pkg/api/api.go b/pkg/api/api.go index 77beab0..8965cba 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -37,16 +37,16 @@ type BuildResponse struct { // to know the type of error, and use Unwrap to obtain its cause if available. Error *k6build.WrappedError `json:"error,omitempty"` // Artifact metadata. If an error occurred, content is undefined - Artifact k6build.Artifact `json:"artifact,omitempty"` + Artifact k6build.Artifact `json:"artifact"` } // String returns a text serialization of the BuildRequest func (r BuildRequest) String() string { buffer := &bytes.Buffer{} - buffer.WriteString(fmt.Sprintf("platform: %s", r.Platform)) - buffer.WriteString(fmt.Sprintf("k6: %s", r.K6Constrains)) + fmt.Fprintf(buffer, "platform: %s", r.Platform) + fmt.Fprintf(buffer, "k6: %s", r.K6Constrains) for _, d := range r.Dependencies { - buffer.WriteString(fmt.Sprintf("%s:%q", d.Name, d.Constraints)) + fmt.Fprintf(buffer, "%s:%q", d.Name, d.Constraints) } return buffer.String() } @@ -54,7 +54,7 @@ func (r BuildRequest) String() string { // String returns a text serialization of the BuildResponse func (r BuildResponse) String() string { buffer := &bytes.Buffer{} - buffer.WriteString(fmt.Sprintf("artifact: %s", r.Artifact.String())) + fmt.Fprintf(buffer, "artifact: %s", r.Artifact.String()) return buffer.String() } @@ -78,9 +78,9 @@ type ResolveResponse struct { // String returns a text serialization of the ResolveRequest func (r ResolveRequest) String() string { buffer := &bytes.Buffer{} - buffer.WriteString(fmt.Sprintf("k6: %s", r.K6Constrains)) + fmt.Fprintf(buffer, "k6: %s", r.K6Constrains) for _, d := range r.Dependencies { - buffer.WriteString(fmt.Sprintf("%s:%q", d.Name, d.Constraints)) + fmt.Fprintf(buffer, "%s:%q", d.Name, d.Constraints) } return buffer.String() } @@ -89,10 +89,10 @@ func (r ResolveRequest) String() string { func (r ResolveResponse) String() string { buffer := &bytes.Buffer{} if r.Error != nil { - buffer.WriteString(fmt.Sprintf("error: %s", r.Error.Error())) + fmt.Fprintf(buffer, "error: %s", r.Error.Error()) } for dep, version := range r.Dependencies { - buffer.WriteString(fmt.Sprintf("%s:%q ", dep, version)) + fmt.Fprintf(buffer, "%s:%q ", dep, version) } return buffer.String() } diff --git a/pkg/builder/builder.go b/pkg/builder/builder.go index e60c898..9dd2838 100644 --- a/pkg/builder/builder.go +++ b/pkg/builder/builder.go @@ -322,14 +322,14 @@ func generateArtifactID(platform string, deps map[string]catalog.Module) string hashData.WriteString(platform) // add k6 as the first dependency - hashData.WriteString(fmt.Sprintf(":%s%s", k6DependencyName, deps[k6DependencyName].Version)) + fmt.Fprintf(&hashData, ":%s%s", k6DependencyName, deps[k6DependencyName].Version) // add the other dependencies for _, d := range slices.Sorted(maps.Keys(deps)) { if d == k6DependencyName { continue } - hashData.WriteString(fmt.Sprintf(":%s%s", d, deps[d].Version)) + fmt.Fprintf(&hashData, ":%s%s", d, deps[d].Version) } return fmt.Sprintf("%x", sha1.Sum(hashData.Bytes())) //nolint:gosec @@ -382,8 +382,8 @@ func (b *Builder) buildArtifact( }, } if b.opts.Verbose { - builderOpts.Stdout = os.Stdout - builderOpts.Stderr = os.Stderr + builderOpts.Stdout = os.Stdout //nolint:forbidigo + builderOpts.Stderr = os.Stderr //nolint:forbidigo } builder, err := b.foundry.NewFoundry(ctx, builderOpts) diff --git a/pkg/builder/builder_test.go b/pkg/builder/builder_test.go index 793bef2..08a1f2f 100644 --- a/pkg/builder/builder_test.go +++ b/pkg/builder/builder_test.go @@ -34,11 +34,11 @@ type mockFoundry struct { func (m *mockFoundry) Build( _ context.Context, platform k6foundry.Platform, - k6Version string, + _ string, mods []k6foundry.Module, - reps []k6foundry.Module, - buildOpts []string, - out io.Writer, + _ []k6foundry.Module, + _ []string, + _ io.Writer, ) (*k6foundry.BuildInfo, error) { modVersions := make(map[string]string) for _, mod := range mods { @@ -114,7 +114,6 @@ func TestBuild(t *testing.T) { } for _, tc := range testCases { - tc := tc t.Run(tc.title, func(t *testing.T) { t.Parallel() @@ -197,7 +196,6 @@ func TestResolve(t *testing.T) { } for _, tc := range testCases { - tc := tc t.Run(tc.title, func(t *testing.T) { t.Parallel() @@ -411,10 +409,7 @@ func TestConcurrentBuilds(t *testing.T) { wg := sync.WaitGroup{} for _, b := range builds { - wg.Add(1) - go func() { - defer wg.Done() - + wg.Go(func() { if _, err := buildsrv.Build( context.TODO(), "linux/amd64", @@ -423,7 +418,7 @@ func TestConcurrentBuilds(t *testing.T) { ); err != nil { errch <- err } - }() + }) } wg.Wait() @@ -435,29 +430,29 @@ func TestConcurrentBuilds(t *testing.T) { } } -// templates for producing metric text output -var metricTemplates = map[string]string{ - "k6build_requests_total": ` +func TestMetrics(t *testing.T) { + t.Parallel() + + // templates for producing metric text output + metricTemplates := map[string]string{ + "k6build_requests_total": ` # HELP k6build_requests_total The total number of builds requests # TYPE k6build_requests_total counter k6build_requests_total %s`, - "k6build_builds_total": ` + "k6build_builds_total": ` # HELP k6build_builds_total The total number of builds # HELP k6build_builds_total # TYPE k6build_builds_total counter k6build_builds_total %s`, - "k6build_builds_failed_total": ` + "k6build_builds_failed_total": ` # HELP k6build_builds_failed_total The total number of failed builds # TYPE k6build_builds_failed_total counter k6build_builds_failed_total %s`, - "k6build_builds_invalid_total": ` + "k6build_builds_invalid_total": ` # HELP k6build_builds_invalid_total The total number of builds with invalid parameters # TYPE k6build_builds_invalid_total counter k6build_builds_invalid_total %s`, -} - -func TestMetrics(t *testing.T) { - t.Parallel() + } testCases := []struct { title string @@ -546,7 +541,7 @@ func TestMetrics(t *testing.T) { text := strings.Builder{} for metric, expected := range tc.expected { metrics = append(metrics, metric) - text.Write([]byte(fmt.Sprintf(metricTemplates[metric], expected))) + fmt.Fprintf(&text, metricTemplates[metric], expected) } text.Write([]byte("\n")) diff --git a/pkg/catalog/catalog.go b/pkg/catalog/catalog.go index f0f8d42..696712d 100644 --- a/pkg/catalog/catalog.go +++ b/pkg/catalog/catalog.go @@ -134,7 +134,7 @@ func NewCatalog(ctx context.Context, location string) (Catalog, error) { // NewCatalogFromFile creates a Catalog from a json file func NewCatalogFromFile(catalogFile string) (Catalog, error) { - json, err := os.ReadFile(catalogFile) //nolint:gosec + json, err := os.ReadFile(catalogFile) //nolint:gosec,forbidigo if err != nil { return nil, fmt.Errorf("%w: %w", ErrOpening, err) } @@ -150,7 +150,7 @@ func NewCatalogFromURL(ctx context.Context, catalogURL string) (Catalog, error) return nil, fmt.Errorf("%w %w", ErrDownload, err) } - resp, err := http.DefaultClient.Do(req) + resp, err := http.DefaultClient.Do(req) //nolint:gosec if err != nil { return nil, fmt.Errorf("%w %w", ErrDownload, err) } diff --git a/pkg/catalog/catalog_test.go b/pkg/catalog/catalog_test.go index 3a630fc..9f82d91 100644 --- a/pkg/catalog/catalog_test.go +++ b/pkg/catalog/catalog_test.go @@ -58,8 +58,6 @@ func TestResolve(t *testing.T) { t.Fatalf("test setup %v", err) } for _, tc := range testCases { - tc := tc - t.Run(tc.title, func(t *testing.T) { t.Parallel() @@ -151,7 +149,7 @@ func TestCatalogFromFile(t *testing.T) { t.Parallel() catalogFile := filepath.Join(t.TempDir(), "catalog.json") - err := os.WriteFile(catalogFile, []byte(testCatalog), 0o644) + err := os.WriteFile(catalogFile, []byte(testCatalog), 0o644) //nolint:forbidigo if err != nil { t.Fatalf("test setup: %v", err) } diff --git a/pkg/client/client.go b/pkg/client/client.go index da25049..65c5d04 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -210,7 +210,7 @@ func (r *BuildClient) doRequest(ctx context.Context, path string, request any, r // try at least once for { req.Body = io.NopCloser(bytes.NewReader(body)) // reset the body - resp, err = r.client.Do(req) + resp, err = r.client.Do(req) //nolint:gosec if retries == 0 || !shouldRetry(err, resp) { break diff --git a/pkg/client/client_test.go b/pkg/client/client_test.go index ef16cf8..d504267 100644 --- a/pkg/client/client_test.go +++ b/pkg/client/client_test.go @@ -92,7 +92,7 @@ func response(status int, response any) requestHandler { // fail with the given error up to a number of times func unreliable(status int, failures int) requestHandler { requests := 0 - return func(w http.ResponseWriter, r *http.Request) bool { + return func(w http.ResponseWriter, _ *http.Request) bool { requests++ if requests <= failures { w.WriteHeader(status) @@ -271,7 +271,6 @@ func TestResolve(t *testing.T) { } for _, tc := range testCases { - tc := tc t.Run(tc.title, func(t *testing.T) { t.Parallel() diff --git a/pkg/server/server_test.go b/pkg/server/server_test.go index 8ba2091..3c37781 100644 --- a/pkg/server/server_test.go +++ b/pkg/server/server_test.go @@ -22,10 +22,10 @@ type mockBuilder struct { } func (m mockBuilder) Build( - ctx context.Context, + _ context.Context, platform string, - k6Constrains string, - deps []k6build.Dependency, + _ string, + _ []k6build.Dependency, ) (k6build.Artifact, error) { if m.err != nil { return k6build.Artifact{}, m.err @@ -38,9 +38,9 @@ func (m mockBuilder) Build( } func (m mockBuilder) Resolve( - ctx context.Context, - k6Constrains string, - deps []k6build.Dependency, + _ context.Context, + _ string, + _ []k6build.Dependency, ) (map[string]string, error) { if m.err != nil { return nil, m.err @@ -122,7 +122,6 @@ func TestBuild(t *testing.T) { } for _, tc := range testCases { - tc := tc t.Run(tc.title, func(t *testing.T) { t.Parallel() @@ -138,7 +137,12 @@ func TestBuild(t *testing.T) { } url, _ := url.Parse(apiserver.URL) - resp, err := http.Post(url.JoinPath("build").String(), "application/json", req) + httpReq, err := http.NewRequestWithContext(t.Context(), http.MethodPost, url.JoinPath("build").String(), req) + if err != nil { + t.Fatalf("creating request %v", err) + } + httpReq.Header.Set("Content-Type", "application/json") + resp, err := http.DefaultClient.Do(httpReq) if err != nil { t.Fatalf("making request %v", err) } @@ -203,7 +207,6 @@ func TestBuildGet(t *testing.T) { } for _, tc := range testCases { - tc := tc t.Run(tc.title, func(t *testing.T) { t.Parallel() @@ -219,7 +222,12 @@ func TestBuildGet(t *testing.T) { queryParams.Add(param, value) } - resp, err := http.Get(u.String() + "?" + queryParams.Encode()) + u.RawQuery = queryParams.Encode() + httpReq, err := http.NewRequestWithContext(t.Context(), http.MethodGet, u.String(), nil) + if err != nil { + t.Fatalf("creating request %v", err) + } + resp, err := http.DefaultClient.Do(httpReq) if err != nil { t.Fatalf("making request %v", err) } @@ -291,7 +299,6 @@ func TestResolve(t *testing.T) { } for _, tc := range testCases { - tc := tc t.Run(tc.title, func(t *testing.T) { t.Parallel() @@ -307,7 +314,12 @@ func TestResolve(t *testing.T) { } url, _ := url.Parse(apiserver.URL) - resp, err := http.Post(url.JoinPath("resolve").String(), "application/json", req) + httpReq, err := http.NewRequestWithContext(t.Context(), http.MethodPost, url.JoinPath("resolve").String(), req) + if err != nil { + t.Fatalf("creating request %v", err) + } + httpReq.Header.Set("Content-Type", "application/json") + resp, err := http.DefaultClient.Do(httpReq) if err != nil { t.Fatalf("making request %v", err) } diff --git a/pkg/store/client/client.go b/pkg/store/client/client.go index f4f6ba0..3b39c79 100644 --- a/pkg/store/client/client.go +++ b/pkg/store/client/client.go @@ -55,7 +55,7 @@ func (c *StoreClient) Get(ctx context.Context, id string) (store.Object, error) return store.Object{}, k6build.NewWrappedError(api.ErrInvalidRequest, err) } - resp, err := c.client.Do(req) + resp, err := c.client.Do(req) //nolint:gosec if err != nil { return store.Object{}, k6build.NewWrappedError(api.ErrRequestFailed, err) } @@ -97,7 +97,7 @@ func (c *StoreClient) Put(ctx context.Context, id string, content io.Reader) (st } req.Header.Set("Content-Type", "application/octet-stream") - resp, err := c.client.Do(req) + resp, err := c.client.Do(req) //nolint:gosec if err != nil { return store.Object{}, k6build.NewWrappedError(api.ErrRequestFailed, err) } @@ -128,7 +128,7 @@ func (c *StoreClient) Download(ctx context.Context, object store.Object) (io.Rea return nil, k6build.NewWrappedError(api.ErrInvalidRequest, err) } - resp, err := c.client.Do(req) //nolint:bodyclose + resp, err := c.client.Do(req) //nolint:bodyclose,gosec if err != nil { return nil, k6build.NewWrappedError(api.ErrRequestFailed, err) } diff --git a/pkg/store/client/client_test.go b/pkg/store/client/client_test.go index 03271d9..ffb220f 100644 --- a/pkg/store/client/client_test.go +++ b/pkg/store/client/client_test.go @@ -81,7 +81,6 @@ func TestStoreClientGet(t *testing.T) { } for _, tc := range testCases { - tc := tc t.Run(tc.title, func(t *testing.T) { t.Parallel() @@ -129,7 +128,6 @@ func TestStoreClientPut(t *testing.T) { } for _, tc := range testCases { - tc := tc t.Run(tc.title, func(t *testing.T) { t.Parallel() @@ -170,7 +168,6 @@ func TestStoreClientDownload(t *testing.T) { } for _, tc := range testCases { - tc := tc t.Run(tc.title, func(t *testing.T) { t.Parallel() diff --git a/pkg/store/downloader/downloader.go b/pkg/store/downloader/downloader.go index 18c79ab..ef9cf80 100644 --- a/pkg/store/downloader/downloader.go +++ b/pkg/store/downloader/downloader.go @@ -36,10 +36,10 @@ func Download(ctx context.Context, client *http.Client, object store.Object) (io return nil, err } - objectFile, err := os.Open(objectPath) //nolint:gosec // path is sanitized + objectFile, err := os.Open(objectPath) //nolint:gosec,forbidigo // path is sanitized if err != nil { // FIXME: is the path has invalid characters, still will return ErrNotExists - if errors.Is(err, os.ErrNotExist) { + if errors.Is(err, os.ErrNotExist) { //nolint:forbidigo return nil, store.ErrObjectNotFound } return nil, k6build.NewWrappedError(store.ErrAccessingObject, err) @@ -52,7 +52,7 @@ func Download(ctx context.Context, client *http.Client, object store.Object) (io return nil, k6build.NewWrappedError(store.ErrAccessingObject, err) } - resp, err := client.Do(req) + resp, err := client.Do(req) //nolint:gosec if err != nil { return nil, k6build.NewWrappedError(store.ErrAccessingObject, err) } diff --git a/pkg/store/downloader/downloader_test.go b/pkg/store/downloader/downloader_test.go index e0919a5..d378cc9 100644 --- a/pkg/store/downloader/downloader_test.go +++ b/pkg/store/downloader/downloader_test.go @@ -42,7 +42,7 @@ func TestDownload(t *testing.T) { } for _, o := range objects { - if err := os.WriteFile(filepath.Join(storeDir, o.id), o.content, 0o600); err != nil { + if err := os.WriteFile(filepath.Join(storeDir, o.id), o.content, 0o600); err != nil { //nolint:forbidigo t.Fatalf("test setup %v", err) } } @@ -93,7 +93,6 @@ func TestDownload(t *testing.T) { } for _, tc := range testCases { - tc := tc t.Run(tc.title, func(t *testing.T) { t.Parallel() diff --git a/pkg/store/file/file.go b/pkg/store/file/file.go index 1a8f8ec..fbaf1b1 100644 --- a/pkg/store/file/file.go +++ b/pkg/store/file/file.go @@ -24,12 +24,12 @@ type Store struct { // NewTempFileStore creates a file object store using a temporary file func NewTempFileStore() (store.ObjectStore, error) { - return NewFileStore(filepath.Join(os.TempDir(), "k6build", "objectstore")) + return NewFileStore(filepath.Join(os.TempDir(), "k6build", "objectstore")) //nolint:forbidigo } // NewFileStore creates an object store backed by a directory func NewFileStore(dir string) (store.ObjectStore, error) { - err := os.MkdirAll(dir, 0o750) + err := os.MkdirAll(dir, 0o750) //nolint:forbidigo if err != nil { return nil, k6build.NewWrappedError(store.ErrInitializingStore, err) } @@ -52,12 +52,12 @@ func (f *Store) Put(_ context.Context, id string, content io.Reader) (store.Obje objectDir := filepath.Join(f.dir, id) - if _, err := os.Stat(objectDir); !errors.Is(err, os.ErrNotExist) { + if _, err := os.Stat(objectDir); !errors.Is(err, os.ErrNotExist) { //nolint:forbidigo return store.Object{}, fmt.Errorf("%w: %q", store.ErrDuplicateObject, id) } // TODO: check permissions - err := os.MkdirAll(objectDir, 0o750) + err := os.MkdirAll(objectDir, 0o750) //nolint:forbidigo if err != nil { return store.Object{}, k6build.NewWrappedError(store.ErrCreatingObject, err) } @@ -69,7 +69,7 @@ func (f *Store) Put(_ context.Context, id string, content io.Reader) (store.Obje } defer unlock() - objectFile, err := os.Create(filepath.Join(objectDir, "data")) //nolint:gosec + objectFile, err := os.Create(filepath.Join(objectDir, "data")) //nolint:gosec,forbidigo if err != nil { return store.Object{}, k6build.NewWrappedError(store.ErrCreatingObject, err) } @@ -87,7 +87,7 @@ func (f *Store) Put(_ context.Context, id string, content io.Reader) (store.Obje checksum := fmt.Sprintf("%x", sha256.Sum256(buff.Bytes())) // write metadata - err = os.WriteFile(filepath.Join(objectDir, "checksum"), []byte(checksum), 0o644) //nolint:gosec + err = os.WriteFile(filepath.Join(objectDir, "checksum"), []byte(checksum), 0o644) //nolint:gosec,forbidigo if err != nil { return store.Object{}, k6build.NewWrappedError(store.ErrCreatingObject, err) } @@ -103,9 +103,9 @@ func (f *Store) Put(_ context.Context, id string, content io.Reader) (store.Obje // Get retrieves an objects if exists in the object store or an error otherwise func (f *Store) Get(_ context.Context, id string) (store.Object, error) { objectDir := filepath.Join(f.dir, id) - _, err := os.Stat(objectDir) + _, err := os.Stat(objectDir) //nolint:forbidigo - if errors.Is(err, os.ErrNotExist) { + if errors.Is(err, os.ErrNotExist) { //nolint:forbidigo return store.Object{}, fmt.Errorf("%w (%s)", store.ErrObjectNotFound, id) } @@ -120,7 +120,7 @@ func (f *Store) Get(_ context.Context, id string) (store.Object, error) { } defer unlock() - checksum, err := os.ReadFile(filepath.Join(objectDir, "checksum")) //nolint:gosec + checksum, err := os.ReadFile(filepath.Join(objectDir, "checksum")) //nolint:gosec,forbidigo if err != nil { return store.Object{}, k6build.NewWrappedError(store.ErrAccessingObject, err) } diff --git a/pkg/store/file/file_test.go b/pkg/store/file/file_test.go index 90787fd..d4c63f2 100644 --- a/pkg/store/file/file_test.go +++ b/pkg/store/file/file_test.go @@ -110,7 +110,7 @@ func TestFileStoreStoreObject(t *testing.T) { t.Fatalf("invalid url %v", err) } - content, err := os.ReadFile(filePath) + content, err := os.ReadFile(filePath) //nolint:forbidigo if err != nil { t.Fatalf("reading object url %v", err) } @@ -177,7 +177,7 @@ func TestFileStoreGet(t *testing.T) { t.Fatalf("invalid url %v", err) } - data, err := os.ReadFile(fileUPath) + data, err := os.ReadFile(fileUPath) //nolint:forbidigo if err != nil { t.Fatalf("reading object url %v", err) } diff --git a/pkg/store/file/lock_nix.go b/pkg/store/file/lock_nix.go index 2f96577..2a9f939 100644 --- a/pkg/store/file/lock_nix.go +++ b/pkg/store/file/lock_nix.go @@ -1,5 +1,4 @@ //go:build !windows -// +build !windows package file diff --git a/pkg/store/s3/s3_test.go b/pkg/store/s3/s3_test.go index 54b2339..a03614c 100644 --- a/pkg/store/s3/s3_test.go +++ b/pkg/store/s3/s3_test.go @@ -136,7 +136,11 @@ func TestPutObject(t *testing.T) { t.Fatalf("invalid url %v", err) } - resp, err := http.Get(obj.URL) + req, err := http.NewRequestWithContext(t.Context(), http.MethodGet, obj.URL, nil) + if err != nil { + t.Fatalf("creating request %v", err) + } + resp, err := http.DefaultClient.Do(req) if err != nil { t.Fatalf("reading object url %v", err) } @@ -213,7 +217,11 @@ func TestGetObject(t *testing.T) { t.Fatalf("invalid url %v", err) } - resp, err := http.Get(obj.URL) + req, err := http.NewRequestWithContext(t.Context(), http.MethodGet, obj.URL, nil) + if err != nil { + t.Fatalf("creating request %v", err) + } + resp, err := http.DefaultClient.Do(req) if err != nil { t.Fatalf("reading object url %v", err) } diff --git a/pkg/store/server/server_test.go b/pkg/store/server/server_test.go index e071dc5..c968796 100644 --- a/pkg/store/server/server_test.go +++ b/pkg/store/server/server_test.go @@ -60,12 +60,15 @@ func TestStoreServerGet(t *testing.T) { } for _, tc := range testCases { - tc := tc t.Run(tc.title, func(t *testing.T) { t.Parallel() url := fmt.Sprintf("%s/store/%s", srv.URL, tc.id) - resp, err := http.Get(url) + req, err := http.NewRequestWithContext(t.Context(), http.MethodGet, url, nil) + if err != nil { + t.Fatalf("creating request %v", err) + } + resp, err := http.DefaultClient.Do(req) if err != nil { t.Fatalf("accessing server %v", err) } @@ -130,16 +133,21 @@ func TestStoreServerPut(t *testing.T) { } for _, tc := range testCases { - tc := tc t.Run(tc.title, func(t *testing.T) { t.Parallel() url := fmt.Sprintf("%s/store/%s", srv.URL, tc.id) - resp, err := http.Post( + req, err := http.NewRequestWithContext( + t.Context(), + http.MethodPost, url, - "application/octet-stream", bytes.NewBufferString(tc.content), ) + if err != nil { + t.Fatalf("creating request %v", err) + } + req.Header.Set("Content-Type", "application/octet-stream") + resp, err := http.DefaultClient.Do(req) if err != nil { t.Fatalf("accessing server %v", err) } @@ -220,12 +228,15 @@ func TestStoreServerDownload(t *testing.T) { } for _, tc := range testCases { - tc := tc t.Run(tc.title, func(t *testing.T) { t.Parallel() url := fmt.Sprintf("%s/store/%s/download", srv.URL, tc.id) - resp, err := http.Get(url) + req, err := http.NewRequestWithContext(t.Context(), http.MethodGet, url, nil) + if err != nil { + t.Fatalf("creating request %v", err) + } + resp, err := http.DefaultClient.Do(req) if err != nil { t.Fatalf("accessing server %v", err) } diff --git a/pkg/store/store.go b/pkg/store/store.go index 47bb679..8c42727 100644 --- a/pkg/store/store.go +++ b/pkg/store/store.go @@ -30,9 +30,9 @@ type Object struct { func (o Object) String() string { buffer := &bytes.Buffer{} - buffer.WriteString(fmt.Sprintf("id: %s", o.ID)) - buffer.WriteString(fmt.Sprintf(" checksum: %s", o.Checksum)) - buffer.WriteString(fmt.Sprintf("url: %s", o.URL)) + fmt.Fprintf(buffer, "id: %s", o.ID) + fmt.Fprintf(buffer, " checksum: %s", o.Checksum) + fmt.Fprintf(buffer, "url: %s", o.URL) return buffer.String() } diff --git a/pkg/util/download.go b/pkg/util/download.go index d198659..e92f507 100644 --- a/pkg/util/download.go +++ b/pkg/util/download.go @@ -20,7 +20,7 @@ func Download(ctx context.Context, url string, output string) error { if err != nil { return fmt.Errorf("%w %w", ErrDownloadFailed, err) } - resp, err := http.DefaultClient.Do(req) + resp, err := http.DefaultClient.Do(req) //nolint:gosec if err != nil { return fmt.Errorf("%w %w", ErrDownloadFailed, err) } @@ -32,9 +32,9 @@ func Download(ctx context.Context, url string, output string) error { _ = resp.Body.Close() }() - outFile, err := os.OpenFile( //nolint:gosec + outFile, err := os.OpenFile( //nolint:gosec,forbidigo output, - os.O_TRUNC|os.O_WRONLY|os.O_CREATE, + os.O_TRUNC|os.O_WRONLY|os.O_CREATE, //nolint:forbidigo syscall.S_IRUSR|syscall.S_IXUSR|syscall.S_IWUSR, ) if err != nil { diff --git a/pkg/util/download_test.go b/pkg/util/download_test.go index 95b6e9c..5ddd7f4 100644 --- a/pkg/util/download_test.go +++ b/pkg/util/download_test.go @@ -45,7 +45,6 @@ func TestDownload(t *testing.T) { } for _, tc := range testCases { - tc := tc t.Run(tc.title, func(t *testing.T) { t.Parallel() diff --git a/pkg/util/url_test.go b/pkg/util/url_test.go index 8041597..d8a58af 100644 --- a/pkg/util/url_test.go +++ b/pkg/util/url_test.go @@ -13,7 +13,7 @@ func TestURLToFilePath(t *testing.T) { if tc.url == "" { continue } - tc := tc + t.Run(tc.url, func(t *testing.T) { t.Parallel() @@ -46,7 +46,7 @@ func TestURLFromFilePath(t *testing.T) { if tc.filePath == "" { continue } - tc := tc + t.Run(tc.filePath, func(t *testing.T) { t.Parallel() @@ -76,6 +76,106 @@ func TestURLFromFilePath(t *testing.T) { } func urlTests() []urlTest { + urlTestsOthers := []urlTest{ + // Examples from RFC 8089: + { + url: `file:///path/to/file`, + filePath: `/path/to/file`, + }, + { + url: `file:/path/to/file`, + filePath: `/path/to/file`, + canonicalURL: `file:///path/to/file`, + }, + { + url: `file://localhost/path/to/file`, + filePath: `/path/to/file`, + canonicalURL: `file:///path/to/file`, + }, + // We reject non-local files. + { + url: `file://host.example.com/path/to/file`, + wantErr: "file URL specifies non-local host", + }, + } + + urlTestsWindows := []urlTest{ + // Examples from https://blogs.msdn.microsoft.com/ie/2006/12/06/file-uris-in-windows/: + { + url: `file://laptop/My%20Documents/FileSchemeURIs.doc`, + filePath: `\\laptop\My Documents\FileSchemeURIs.doc`, + }, + { + url: `file:///C:/Documents%20and%20Settings/davris/FileSchemeURIs.doc`, + filePath: `C:\Documents and Settings\davris\FileSchemeURIs.doc`, + }, + { + url: `file:///D:/Program%20Files/Viewer/startup.htm`, + filePath: `D:\Program Files\Viewer\startup.htm`, + }, + { + url: `file:///C:/Program%20Files/Music/Web%20Sys/main.html?REQUEST=RADIO`, + filePath: `C:\Program Files\Music\Web Sys\main.html`, + canonicalURL: `file:///C:/Program%20Files/Music/Web%20Sys/main.html`, + }, + { + url: `file://applib/products/a-b/abc_9/4148.920a/media/start.swf`, + filePath: `\\applib\products\a-b\abc_9\4148.920a\media\start.swf`, + }, + { + url: `file:////applib/products/a%2Db/abc%5F9/4148.920a/media/start.swf`, + wantErr: "file URL missing drive letter", + }, + { + url: `C:\Program Files\Music\Web Sys\main.html?REQUEST=RADIO`, + wantErr: "non-file URL", + }, + // The example "file://D:\Program Files\Viewer\startup.htm" errors out in + // url.Parse, so we substitute a slash-based path for testing instead. + { + url: `file://D:/Program Files/Viewer/startup.htm`, + wantErr: "file URL encodes volume in host field: too few slashes?", + }, + // The blog post discourages the use of non-ASCII characters because they + // depend on the user's current codepage. However, when we are working with Go + // strings we assume UTF-8 encoding, and our url package refuses to encode + // URLs to non-ASCII strings. + { + url: `file:///C:/exampleㄓ.txt`, + filePath: `C:\exampleㄓ.txt`, + canonicalURL: `file:///C:/example%E3%84%93.txt`, + }, + { + url: `file:///C:/example%E3%84%93.txt`, + filePath: `C:\exampleㄓ.txt`, + }, + // Examples from RFC 8089: + // We allow the drive-letter variation from section E.2, because it is + // simpler to support than not to. However, we do not generate the shorter + // form in the reverse direction. + { + url: `file:c:/path/to/file`, + filePath: `c:\path\to\file`, + canonicalURL: `file:///c:/path/to/file`, + }, + // We encode the UNC share name as the authority following section E.3.1, + // because that is what the Microsoft blog post explicitly recommends. + { + url: `file://host.example.com/Share/path/to/file.txt`, + filePath: `\\host.example.com\Share\path\to\file.txt`, + }, + // We decline the four- and five-slash variations from section E.3.2. + // The paths in these URLs would change meaning under path.Clean. + { + url: `file:////host.example.com/path/to/file`, + wantErr: "file URL missing drive letter", + }, + { + url: `file://///host.example.com/path/to/file`, + wantErr: "file URL missing drive letter", + }, + } + if runtime.GOOS == "windows" { return urlTestsWindows } @@ -88,103 +188,3 @@ type urlTest struct { canonicalURL string // If empty, assume equal to url. wantErr string } - -var urlTestsOthers = []urlTest{ - // Examples from RFC 8089: - { - url: `file:///path/to/file`, - filePath: `/path/to/file`, - }, - { - url: `file:/path/to/file`, - filePath: `/path/to/file`, - canonicalURL: `file:///path/to/file`, - }, - { - url: `file://localhost/path/to/file`, - filePath: `/path/to/file`, - canonicalURL: `file:///path/to/file`, - }, - // We reject non-local files. - { - url: `file://host.example.com/path/to/file`, - wantErr: "file URL specifies non-local host", - }, -} - -var urlTestsWindows = []urlTest{ - // Examples from https://blogs.msdn.microsoft.com/ie/2006/12/06/file-uris-in-windows/: - { - url: `file://laptop/My%20Documents/FileSchemeURIs.doc`, - filePath: `\\laptop\My Documents\FileSchemeURIs.doc`, - }, - { - url: `file:///C:/Documents%20and%20Settings/davris/FileSchemeURIs.doc`, - filePath: `C:\Documents and Settings\davris\FileSchemeURIs.doc`, - }, - { - url: `file:///D:/Program%20Files/Viewer/startup.htm`, - filePath: `D:\Program Files\Viewer\startup.htm`, - }, - { - url: `file:///C:/Program%20Files/Music/Web%20Sys/main.html?REQUEST=RADIO`, - filePath: `C:\Program Files\Music\Web Sys\main.html`, - canonicalURL: `file:///C:/Program%20Files/Music/Web%20Sys/main.html`, - }, - { - url: `file://applib/products/a-b/abc_9/4148.920a/media/start.swf`, - filePath: `\\applib\products\a-b\abc_9\4148.920a\media\start.swf`, - }, - { - url: `file:////applib/products/a%2Db/abc%5F9/4148.920a/media/start.swf`, - wantErr: "file URL missing drive letter", - }, - { - url: `C:\Program Files\Music\Web Sys\main.html?REQUEST=RADIO`, - wantErr: "non-file URL", - }, - // The example "file://D:\Program Files\Viewer\startup.htm" errors out in - // url.Parse, so we substitute a slash-based path for testing instead. - { - url: `file://D:/Program Files/Viewer/startup.htm`, - wantErr: "file URL encodes volume in host field: too few slashes?", - }, - // The blog post discourages the use of non-ASCII characters because they - // depend on the user's current codepage. However, when we are working with Go - // strings we assume UTF-8 encoding, and our url package refuses to encode - // URLs to non-ASCII strings. - { - url: `file:///C:/exampleㄓ.txt`, - filePath: `C:\exampleㄓ.txt`, - canonicalURL: `file:///C:/example%E3%84%93.txt`, - }, - { - url: `file:///C:/example%E3%84%93.txt`, - filePath: `C:\exampleㄓ.txt`, - }, - // Examples from RFC 8089: - // We allow the drive-letter variation from section E.2, because it is - // simpler to support than not to. However, we do not generate the shorter - // form in the reverse direction. - { - url: `file:c:/path/to/file`, - filePath: `c:\path\to\file`, - canonicalURL: `file:///c:/path/to/file`, - }, - // We encode the UNC share name as the authority following section E.3.1, - // because that is what the Microsoft blog post explicitly recommends. - { - url: `file://host.example.com/Share/path/to/file.txt`, - filePath: `\\host.example.com\Share\path\to\file.txt`, - }, - // We decline the four- and five-slash variations from section E.3.2. - // The paths in these URLs would change meaning under path.Clean. - { - url: `file:////host.example.com/path/to/file`, - wantErr: "file URL missing drive letter", - }, - { - url: `file://///host.example.com/path/to/file`, - wantErr: "file URL missing drive letter", - }, -}