diff --git a/.github/actions/extract-fixtures/action.yml b/.github/actions/extract-fixtures/action.yml index dd8be0134..c96a54ddb 100644 --- a/.github/actions/extract-fixtures/action.yml +++ b/.github/actions/extract-fixtures/action.yml @@ -3,9 +3,13 @@ name: 'Gateway Conformance Tests: Extract Fixtures' description: 'Extracts the fixtures from the Gateway Conformance Tests' inputs: output: - description: 'The path where the fixtures will be saved' + description: 'The path where the test fixtures should be extracted.' required: true default: 'fixtures' + merged: + description: 'Whether the fixtures should be merged into a single CAR file.' + required: false + default: 'false' runs: using: 'composite' steps: @@ -15,8 +19,9 @@ runs: uses: pl-strflt/docker-container-action@v1 env: OUTPUT: ${{ inputs.output }} + MERGED: ${{ inputs.merged }} with: repository: ${{ steps.github.outputs.action_repository }} ref: ${{ steps.github.outputs.action_ref }} dockerfile: Dockerfile - args: extract-fixtures "$OUTPUT" + args: extract-fixtures --directory="$OUTPUT" --merged="$MERGED" diff --git a/.github/actions/test/action.yml b/.github/actions/test/action.yml index d6dedd237..fe9972128 100644 --- a/.github/actions/test/action.yml +++ b/.github/actions/test/action.yml @@ -1,55 +1,46 @@ -# action.yml name: "Gateway Conformance Tests" description: "Runs the Gateway Conformance Tests" inputs: gateway-url: - description: "The URL of the Gateway to test" + description: "The URL of the IPFS Gateway implementation to be tested." required: true json: - description: "The path where the JSON report will be saved" + description: "The path where the JSON test report should be generated." required: true default: "report.json" xml: - description: "The path where the jUnit XML report will be saved" + description: "The path where the JUnit XML test report should be generated." required: false html: - description: "The path where the HTML report will be saved" + description: "The path where the one-page HTML test report should be generated." required: false markdown: - description: "The path where the Markdown report will be saved" + description: "The path where the summary Markdown test report should be generated." required: false - is-subdomain: - description: "Enable the test suite for subdomain gateways" + specs: + description: "A comma-separated list of specs to be tested. Accepts a spec (test only this spec), a +spec (test also this immature spec), or a -spec (do not test this mature spec)." + required: false + default: "" + args: + description: "[DANGER] The `args` input allows you to pass custom, free-text arguments directly to the Go test command that the tool employs to execute tests." required: false runs: using: "composite" steps: - id: github uses: pl-strflt/docker-container-action/.github/actions/github@v1 - - id: generate-args - name: Generate additional arguments - shell: bash - env: - IS_SUBDOMAIN: ${{ inputs.is-subdomain }} - run: | - ARGS="" - if [ "$IS_SUBDOMAIN" = "true" ]; then - ARGS="$ARGS --is-subdomain" - fi - - echo "args=${ARGS}" >> "$GITHUB_OUTPUT" - name: Run the test uses: pl-strflt/docker-container-action@v1 env: - GATEWAY_URL: ${{ inputs.gateway-url }} + URL: ${{ inputs.gateway-url }} JSON: ${{ inputs.json }} - ADDITIONAL_ARGS: ${{ steps.generate-args.outputs.args }} + SPECS: ${{ inputs.specs }} with: repository: ${{ steps.github.outputs.action_repository }} ref: ${{ steps.github.outputs.action_ref }} dockerfile: Dockerfile opts: --network=host - args: test --gateway-url "$GATEWAY_URL" --json-output "$JSON" ${ADDITIONAL_ARGS} + args: test --url="$URL" --json="$JSON" --specs="$SPECS" -- ${{ inputs.args }} - name: Create the XML if: (inputs.xml || inputs.html || inputs.markdown) && (failure() || success()) uses: pl-strflt/gotest-json-to-junit-xml@v1 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 28111098d..e26e87e8f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -34,7 +34,8 @@ jobs: xml: output.xml html: output.html markdown: output.md - is-subdomain: false + specs: -subdomain-gateway + args: -skip TestGatewayCar - name: Set summary if: (failure() || success()) run: cat ./output.md >> $GITHUB_STEP_SUMMARY diff --git a/.gitignore b/.gitignore index 3eed21d8b..b76d55ad7 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ /fixtures.car /merge-fixtures /entrypoint +/gateway-conformance # Logs logs diff --git a/Dockerfile b/Dockerfile index 919e8bf1e..b272e978b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,13 +1,11 @@ -FROM golang:1.19.1-buster +FROM golang:1.20-alpine WORKDIR /app -ENV TEST_PATH=/app - -RUN go install gotest.tools/gotestsum@v1.9.0 +ENV GATEWAY_CONFORMANCE_HOME=/app COPY ./go.mod ./go.sum ./ RUN go mod download COPY . . -RUN go build -o /entrypoint ./entrypoint.go +RUN go build -o ./gateway-conformance ./cmd/gateway-conformance -ENTRYPOINT ["/entrypoint"] +ENTRYPOINT ["/app/gateway-conformance"] diff --git a/Makefile b/Makefile index 744b035a5..5de08e5e9 100644 --- a/Makefile +++ b/Makefile @@ -11,22 +11,19 @@ provision-kubo: test-kubo: provision-kubo GATEWAY_URL=http://127.0.0.1:8080 make _test -merge-fixtures: - go build -o merge-fixtures ./tooling/cmd/merge_fixtures.go - # tools -fixtures.car: entrypoint - ./entrypoint merge-fixtures ./fixtures.car +fixtures.car: gateway-conformance + ./gateway-conformance extract-fixtures --merged=true --dir=. -entrypoint: - go build -o ./entrypoint ./entrypoint.go +gateway-conformance: + go build -o ./gateway-conformance ./cmd/gateway-conformance -_test: fixtures.car entrypoint - ./entrypoint test --json-output output.json --gateway-url ${GATEWAY_URL} --is-subdomain +_test: fixtures.car gateway-conformance + ./gateway-conformance test --json output.json --gateway-url ${GATEWAY_URL} -test-docker: fixtures.car entrypoint - docker build -t gway-test . - docker run --rm -v "${PWD}:/workspace" -w "/workspace" --network=host gway-test test +test-docker: fixtures.car gateway-conformance + docker build -t gateway-conformance . + docker run --rm -v "${PWD}:/workspace" -w "/workspace" --network=host gateway-conformance test output.xml: test-kubo docker run --rm -v "${PWD}:/workspace" -w "/workspace" --entrypoint "/bin/bash" ghcr.io/pl-strflt/saxon:v1 -c """ @@ -37,4 +34,4 @@ output.html: output.xml docker run --rm -v "${PWD}:/workspace" -w "/workspace" ghcr.io/pl-strflt/saxon:v1 -s:output.xml -xsl:/etc/junit-noframes-saxon.xsl -o:output.html open ./output.html -.PHONY: entrypoint +.PHONY: gateway-conformance diff --git a/README.md b/README.md index 0a559db33..3cad992da 100644 --- a/README.md +++ b/README.md @@ -1,46 +1,151 @@ -# Gateway Conformance Testing Approach Proposal +# gateway-conformance -## Key Concepts +`gateway-conformance` is a tool designed to test if an IPFS Gateway implementation complies with the IPFS Gateway Specification correctly. The tool is distributed as a Docker image, as well as a GitHub Action(s). -- Built with Go for stability & ease of building -- TBD +## Table of Contents -## Issues / Open Questions +- [Commands](#commands) + - [test](#test) + - [Inputs](#inputs) + - [Usage](#usage) + - [extract-fixtures](##extract-fixtures) + - [Inputs](#inputs-1) + - [Usage](#usage-1) +- [Examples](#examples) +- [FAQ](#faq) +- [In Development](#in-development) -- How to deal with subdomains & configuration (t0114 for example)? - - Some test relies on querying URLs like `http://$CIDv1.ipfs.example.com/`. While `http://$CIDv1.ipfs.localhost/` works by default, do we need / want to test with `.example.com`? +## Commands + +### test + +The `test` command is the main command of the tool. It is used to test a given IPFS Gateway implementation against the IPFS Gateway Specification. + +#### Inputs + +| Input | Availability | Description | Default | +|---|---|---|---| +| gateway-url | Both | The URL of the IPFS Gateway implementation to be tested. | http://localhost:8080 | +| json | Both | The path where the JSON test report should be generated. | `./report.json` | +| xml | GitHub Action | The path where the JUnit XML test report should be generated. | `./report.xml` | +| html | GitHub Action | The path where the one-page HTML test report should be generated. | `./report.html` | +| markdown | GitHub Action | The path where the summary Markdown test report should be generated. | `./report.md` | +| specs | Both | A comma-separated list of specs to be tested. Accepts a spec (test only this spec), a +spec (test also this immature spec), or a -spec (do not test this mature spec). | Mature specs only | +| args | Both | [DANGER] The `args` input allows you to pass custom, free-text arguments directly to the Go test command that the tool employs to execute tests. | N/A | + +##### Specs + +By default, only mature specs (reliable, stable, or permanent) will be tested if this input is not provided. You can specify particular specs without any prefixes (e.g., subdomain-gateway, trustless-gateway, path-gateway) to test exclusively those, irrespective of their maturity status. + +To selectively enable or disable specs based on their maturity, use the "+" and "-" prefixes. Adding a "+" prefix (e.g., +subdomain-gateway) means that the spec should be included in the test, in addition to the mature specs. Conversely, using a "-" prefix (e.g., -subdomain-gateway) means that the spec should be excluded from the test, even if it is mature. + +If you provide a list containing both prefixed and unprefixed specs, the prefixed specs will be ignored. It is advisable to use either prefixed or unprefixed specs, but not both. However, you can include specs with both "+" and "-" prefixes in the same list. + +##### Args + +This input should be used sparingly and with caution, as it involves interacting with the underlying internal processes, which may be subject to changes. It is recommended to use the `args` input only when you have a deep understanding of the tool's inner workings and need to fine-tune the testing process. Users should be mindful of the potential risks associated with using this input. + +#### Usage + +##### GitHub Action + +```yaml +- name: Run gateway-conformance tests + uses: ipfs/gateway-conformance/.github/actions/test@v1 + with: + gateway-url: http://localhost:8080 + specs: +subdomain-gateway,-path-gateway + json: report.json + xml: report.xml + markdown: report.md + html: report.html + args: -timeout 30m +``` + +##### Docker + +```bash +docker run --network host -v "${PWD}:/workspace" -w "/workspace" ghcr.io/ipfs/gateway-conformance test --gateway-url http://localhost:8080 --json report.json --specs +subdomain-gateway,-path-gateway -- -timeout 30m +``` + +### extract-fixtures + +The `extract-fixtures` command is used to extract the test fixtures from the `gateway-conformance` tool. -## Usage +#### Inputs -### Retrieve fixtures +| Input | Availability | Description | Default | +|---|---|---|---| +| output | Both | The path where the test fixtures should be extracted. | `./fixtures` | +| merged | Both | Whether the fixtures should be merged into a single CAR file. | `false` | + +#### Usage + +##### GitHub Action + +```yaml +- name: Extract gateway-conformance fixtures + uses: ipfs/gateway-conformance/.github/actions/extract-fixtures@v1 + with: + output: fixtures + merged: false +``` + +##### Docker ```bash -docker run -v "${PWD}:/workspace" -w "/workspace" ghcr.io/ipfs/gateway-conformance extract-fixtures [OUTPUT_DIR] +docker run -v "${PWD}:/workspace" -w "/workspace" ghcr.io/ipfs/gateway-conformance extract-fixtures --output fixtures --merged false ``` -### Run tests +## Examples + +The examples are going to use `gateway-conformance` as a wrapper over `docker run -v "${PWD}:/workspace" -w "/workspace" ghcr.io/ipfs/gateway-conformance` for simplicity. + +### Testing only mature specs ```bash -docker run --network host -v "${PWD}:/workspace" -w "/workspace" ghcr.io/ipfs/gateway-conformance test [gateway-url] [OUTPUT_JSON] +gateway-conformance test ``` -### Generate a XML report +### Testing specific specs, regardless of their maturity level ```bash -docker run --rm -v "${PWD}:/workspace" -w "/workspace" --entrypoint "/bin/bash" ghcr.io/pl-strflt/saxon:v1 -c """ - java -jar /opt/SaxonHE11-5J/saxon-he-11.5.jar -s:<(jq -s '.' [OUTPUT_JSON]) -xsl:/etc/gotest.xsl -o:[OUTPUT_XML] -""" +gateway-conformance test --specs subdomain-gateway,path-gateway +``` + +### Testing mature specs and additionally enabling specific specs +```bash +gateway-conformance test --specs +subdomain-gateway ``` -### Generate a HTML report +### Testing mature specs, while disabling specific specs ```bash - docker run --rm -v "${PWD}:/workspace" -w "/workspace" ghcr.io/pl-strflt/saxon:v1 -s:[OUTPUT_XML] -xsl:/etc/junit-noframes-saxon.xsl -o:[OUTPUT_HTML] +gateway-conformance test --specs -subdomain-gateway ``` -### Generate a single car file for testing +### Extracting the test fixtures ```bash -docker run --network host -w "/workspace" -v "${PWD}:/workspace" ghcr.io/ipfs/gateway-conformance merge-fixtures /workspace/[output-car] +gateway-conformance extract-fixtures ``` + +### Extracting the test fixtures into a single CAR file + +```bash +gateway-conformance extract-fixtures --merged true +``` + +## FAQ + +### How to generate XML, HTML and Markdown reports when using the tool as a Docker container? + +The tool can generate XML, HTML and Markdown reports when used as a GitHub Action. However, when using the tool as a Docker container, you can generate these reports by using the [`saxon` Docker image](https://github.com/pl-strflt/saxon). You can draw inspiration from the [gotest-json-to-junit-xml](https://github.com/pl-strflt/gotest-json-to-junit-xml) and the [junit-xml-to-html](https://github.com/pl-strflt/junit-xml-to-html) GitHub Actions. + +Please let us know if you would like to see this feature implemented directly in the Docker image distribution. + +## In Development + +- How to deal with subdomains & configuration (t0114 for example)? + - Some test relies on querying URLs like `http://$CIDv1.ipfs.example.com/`. While `http://$CIDv1.ipfs.localhost/` works by default, do we need / want to test with `.example.com`? diff --git a/cmd/gateway-conformance/main.go b/cmd/gateway-conformance/main.go new file mode 100644 index 000000000..c7fe22194 --- /dev/null +++ b/cmd/gateway-conformance/main.go @@ -0,0 +1,191 @@ +package main + +import ( + "bytes" + "fmt" + "io" + "log" + "os" + "os/exec" + "path/filepath" + "strings" + + "github.com/ipfs/gateway-conformance/tooling" + "github.com/ipfs/gateway-conformance/tooling/car" + "github.com/ipfs/gateway-conformance/tooling/fixtures" + "github.com/urfave/cli/v2" +) + +type event struct { + Action string + Test string `json:",omitempty"` +} + +type out struct { + Writer io.Writer +} + +func (o out) Write(p []byte) (n int, err error) { + os.Stdout.Write(p) + return o.Writer.Write(p) +} + +func copyFiles(inputPaths []string, outputDirectoryPath string) error { + err := os.MkdirAll(outputDirectoryPath, 0755) + if err != nil { + return err + } + for _, inputPath := range inputPaths { + outputPath := filepath.Join(outputDirectoryPath, filepath.Base(inputPath)) + src, err := os.Open(inputPath) + if err != nil { + return err + } + defer src.Close() + dst, err := os.Create(outputPath) + if err != nil { + return err + } + defer dst.Close() + _, err = io.Copy(dst, src) + if err != nil { + return err + } + } + return nil +} + +func main() { + var gatewayURL string + var jsonOutput string + var specs string + var directory string + var merged bool + + app := &cli.App{ + Name: "gateway-conformance", + Usage: "Tooling for the gateway test suite", + Commands: []*cli.Command{ + { + Name: "test", + Aliases: []string{"t"}, + Usage: "Run the conformance test suite against your gateway", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "gateway-url", + Aliases: []string{"url", "g"}, + Usage: "The URL of the IPFS Gateway implementation to be tested.", + Value: "http://localhost:8080", + Destination: &gatewayURL, + }, + &cli.StringFlag{ + Name: "json-output", + Aliases: []string{"json", "j"}, + Usage: "The path where the JSON test report should be generated.", + Value: "", + Destination: &jsonOutput, + }, + &cli.StringFlag{ + Name: "specs", + Usage: "Accepts a spec (test only this spec), a +spec (test also this immature spec), or a -spec (do not test this mature spec).", + Value: "", + Destination: &specs, + }, + }, + Action: func(cCtx *cli.Context) error { + args := []string{"test", "./tests", "-test.v=test2json"} + + if specs != "" { + args = append(args, fmt.Sprintf("-specs=%s", specs)) + } + + args = append(args, cCtx.Args().Slice()...) + + fmt.Println("go " + strings.Join(args, " ")) + + output := &bytes.Buffer{} + cmd := exec.Command("go", args...) + cmd.Dir = tooling.Home() + cmd.Env = append(os.Environ(), fmt.Sprintf("GATEWAY_URL=%s", gatewayURL)) + cmd.Stdout = out{output} + cmd.Stderr = os.Stderr + testErr := cmd.Run() + + if jsonOutput != "" { + json := &bytes.Buffer{} + cmd = exec.Command("go", "tool", "test2json", "-p", "Gateway Tests", "-t") + cmd.Stdin = output + cmd.Stdout = json + cmd.Stderr = os.Stderr + err := cmd.Run() + if err != nil { + return err + } + // write jsonOutput to json file + f, err := os.Create(jsonOutput) + if err != nil { + return err + } + defer f.Close() + _, err = f.Write(json.Bytes()) + if err != nil { + return err + } + } + + return testErr + }, + }, + { + Name: "extract-fixtures", + Aliases: []string{"e"}, + Usage: "Extract gateway testing fixtures that are used by the conformance test suite", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "directory", + Aliases: []string{"dir"}, + Usage: "The directory to extract the fixtures to", + Required: true, + Destination: &directory, + }, + &cli.BoolFlag{ + Name: "merged", + Usage: "Merge the fixtures into a single CAR file", + Value: false, + Destination: &merged, + }, + }, + Action: func(cCtx *cli.Context) error { + err := os.MkdirAll(directory, 0755) + if err != nil { + return err + } + + files, err := fixtures.List() + if err != nil { + return err + } + + merged := cCtx.Bool("merged") + if merged { + err = car.Merge(files, filepath.Join(directory, "fixtures.car")) + if err != nil { + return err + } + } else { + err = copyFiles(files, directory) + if err != nil { + return err + } + } + + return nil + }, + }, + }, + } + + if err := app.Run(os.Args); err != nil { + log.Fatal(err) + } +} diff --git a/entrypoint.go b/entrypoint.go deleted file mode 100644 index eb44c8167..000000000 --- a/entrypoint.go +++ /dev/null @@ -1,125 +0,0 @@ -package main - -import ( - "fmt" - "log" - "os" - "os/exec" - "path/filepath" - "strings" - - "github.com/ipfs/gateway-conformance/tooling/cmd" - - "github.com/urfave/cli/v2" -) - -func main() { - var subdomain bool - var gatewayURL string - var jsonOutput string - - app := &cli.App{ - Name: "entrypoint", - Usage: "Tooling for the gateway test suite", - Commands: []*cli.Command{ - { - Name: "test", - Aliases: []string{"t"}, - Usage: "Run the conformance test suite against your gateway", - Flags: []cli.Flag{ - &cli.BoolFlag{ - Name: "is-subdomain", - Aliases: []string{"s"}, - Usage: "Run the testsuite for subdomain gateways", - Value: false, - Destination: &subdomain, - }, - &cli.StringFlag{ - Name: "gateway-url", - Aliases: []string{"g"}, - Usage: "The URL of the gateway to test", - Value: "http://localhost:8080", - Destination: &gatewayURL, - }, - &cli.StringFlag{ - Name: "json-output", - Aliases: []string{"j"}, - Usage: "The path to the JSON output file", - Value: "results.json", - Destination: &jsonOutput, - }, - }, - Action: func(cCtx *cli.Context) error { - // Capture the output path, we run the tests in a different folder. - jsonOutputAbs, err := filepath.Abs(jsonOutput) - if err != nil { - panic(err) - } - - testTagsList := []string{} - if subdomain { - testTagsList = append(testTagsList, "test_subdomains") - } - testTags := strings.Join(testTagsList, ",") - - // run gotestsum --jsonfile ${...} ./tests -tags="${testTags}" - cmd := exec.Command("gotestsum", "--jsonfile", jsonOutputAbs, "./tests", "-tags="+testTags) - cmd.Env = append(os.Environ(), "GATEWAY_URL="+gatewayURL) - - // if environ containts "TEST_PATH" then use its value in cmd.Dir - if testPath, ok := os.LookupEnv("TEST_PATH"); ok { - cmd.Dir = testPath - } - cmd.Stdout = os.Stdout - - fmt.Printf("running: %s\n", cmd.String()) - err = cmd.Run() - return err - }, - }, - { - Name: "extract-fixtures", - Aliases: []string{"e"}, - Usage: "Extract gateway testing fixture that is used by the conformance test suite", - Action: func(cCtx *cli.Context) error { - output := cCtx.Args().First() - if output == "" { - return fmt.Errorf("output path is required") - } - - // mkdir -p output: - err := os.MkdirAll(output, 0755) - if err != nil { - return err - } - - // run shell command: `find /app/fixtures -name '*.car' -exec cp {} "${2}/" \;` - cmd := exec.Command("find", "/app/fixtures", "-name", "*.car", "-exec", "cp", "{}", output+"/", ";") - err = cmd.Run() - - cmd.Stderr = os.Stderr - cmd.Stdout = os.Stdout - - return err - }, - }, - { - Name: "merge-fixtures", - Aliases: []string{"m"}, - Usage: "Merge all the fixtures into a single CAR file", - Action: func(cCtx *cli.Context) error { - output := cCtx.Args().First() - if output == "" { - return fmt.Errorf("output path is required") - } - - return cmd.MergeFixtures(output) - }, - }, - }, - } - - if err := app.Run(os.Args); err != nil { - log.Fatal(err) - } -} diff --git a/fixtures/dir/ascii.txt b/fixtures/dir/ascii.txt deleted file mode 100644 index 8b7f203ca..000000000 --- a/fixtures/dir/ascii.txt +++ /dev/null @@ -1 +0,0 @@ -goodbye application/vnd.ipld.raw diff --git a/tests/init.go b/tests/init.go new file mode 100644 index 000000000..e5b0b2325 --- /dev/null +++ b/tests/init.go @@ -0,0 +1,66 @@ +package tests + +import ( + "flag" + "regexp" + "strings" + + "github.com/ipfs/gateway-conformance/tooling/specs" +) + +type specsFlag string + +func (s *specsFlag) String() string { + return string(*s) +} + +func (s *specsFlag) Set(value string) error { + names := strings.Split(value, ",") + var only, enable, disable = []specs.Spec{}, []specs.Spec{}, []specs.Spec{} + for _, name := range names { + spec, err := specs.FromString(regexp.MustCompile(`^[-+]`).ReplaceAllString(name, "")) + if err != nil { + return err + } + if strings.HasPrefix(name, "+") { + // If a spec from the input is prefixed with a +, + // it will be explicitly enabled. + enable = append(enable, spec) + } else if strings.HasPrefix(name, "-") { + // If a spec from the input is prefixed with a -, + // it will be explicitly disabled. + disable = append(disable, spec) + } else { + // If a spec from the input is not prefixed with a + or -, + // only the specified specs will be enabled. + only = append(only, spec) + } + } + if len(only) > 0 { + // If any specs from the input are unprefixed, + // disable all specs and then enable only the specified specs. + for _, spec := range specs.All() { + spec.Disable() + } + for _, spec := range only { + spec.Enable() + } + } else { + // If all specs from the input are prefixed with a + or -, + // enable the specs prefixed with + and then disable the specs prefixed with -. + for _, spec := range enable { + spec.Enable() + } + for _, spec := range disable { + spec.Disable() + } + } + *s = specsFlag(value) + return nil +} + +var specsFlagValue specsFlag + +func init() { + flag.Var(&specsFlagValue, "specs", "A comma-separated list of specs to be tested. Accepts a spec (test only this spec), a +spec (test also this immature spec), or a -spec (do not test this mature spec). Defaults to all mature specs.") +} diff --git a/tests/t0113_gateway_symlink_test.go b/tests/t0113_gateway_symlink_test.go index d8abde270..54ffb51f2 100644 --- a/tests/t0113_gateway_symlink_test.go +++ b/tests/t0113_gateway_symlink_test.go @@ -1,4 +1,4 @@ -package main +package tests import ( "fmt" diff --git a/tests/t0114_gateway_subdomains_test.go b/tests/t0114_gateway_subdomains_test.go index 4d899c32e..86ac705b0 100644 --- a/tests/t0114_gateway_subdomains_test.go +++ b/tests/t0114_gateway_subdomains_test.go @@ -1,13 +1,11 @@ -//go:build test_subdomains -// +build test_subdomains - -package main +package tests import ( "fmt" "testing" . "github.com/ipfs/gateway-conformance/tooling/check" + . "github.com/ipfs/gateway-conformance/tooling/specs" . "github.com/ipfs/gateway-conformance/tooling/test" ) @@ -58,5 +56,7 @@ func TestGatewaySubdomains(t *testing.T) { }, } - Run(t, tests) + if SubdomainGateway.IsEnabled() { + Run(t, tests) + } } diff --git a/tests/t0116_gateway_cache_test.go b/tests/t0116_gateway_cache_test.go index 1fbc0755c..caf1d9f37 100644 --- a/tests/t0116_gateway_cache_test.go +++ b/tests/t0116_gateway_cache_test.go @@ -1,4 +1,4 @@ -package main +package tests import ( "fmt" diff --git a/tests/t0117_gateway_block_test.go b/tests/t0117_gateway_block_test.go index b58ebe12e..34299496e 100644 --- a/tests/t0117_gateway_block_test.go +++ b/tests/t0117_gateway_block_test.go @@ -1,4 +1,4 @@ -package main +package tests import ( "fmt" diff --git a/tests/t0118_gateway_car_test.go b/tests/t0118_gateway_car_test.go index 5b22ce1de..ef780548c 100644 --- a/tests/t0118_gateway_car_test.go +++ b/tests/t0118_gateway_car_test.go @@ -1,6 +1,5 @@ -package main +package tests -/* Skip until https://github.com/ipfs/kubo/issues/9651 is resolved import ( "fmt" "testing" @@ -78,4 +77,3 @@ func TestGatewayCar(t *testing.T) { Run(t, tests) } -*/ diff --git a/tests/t0122_gateway_tar_test.go b/tests/t0122_gateway_tar_test.go index 6944d99f4..e5369b45a 100644 --- a/tests/t0122_gateway_tar_test.go +++ b/tests/t0122_gateway_tar_test.go @@ -1,4 +1,4 @@ -package main +package tests import ( "testing" diff --git a/tests/t0123_gateway_json_cbor_test.go b/tests/t0123_gateway_json_cbor_test.go index 22803b468..01fff9e51 100644 --- a/tests/t0123_gateway_json_cbor_test.go +++ b/tests/t0123_gateway_json_cbor_test.go @@ -1,4 +1,4 @@ -package main +package tests import ( "testing" diff --git a/tests/t0400_api_no_gateway_test.go b/tests/t0400_api_no_gateway_test.go index 81df4e340..f4c9cb2a5 100644 --- a/tests/t0400_api_no_gateway_test.go +++ b/tests/t0400_api_no_gateway_test.go @@ -1,4 +1,4 @@ -package main +package tests import ( "testing" diff --git a/tooling/car/merge.go b/tooling/car/merge.go new file mode 100644 index 000000000..6c00f4595 --- /dev/null +++ b/tooling/car/merge.go @@ -0,0 +1,68 @@ +package car + +import ( + "context" + "fmt" + + "github.com/ipfs/go-cid" + "github.com/ipld/go-car/v2/blockstore" +) + +func Merge(inputPaths []string, outputPath string) error { + // First list all the roots in our fixtures + roots := make([]cid.Cid, 0) + + for _, path := range inputPaths { + fmt.Printf("processing %s\n", path) + robs, err := blockstore.OpenReadOnly(path, + blockstore.UseWholeCIDs(true), + ) + if err != nil { + return err + } + + r, err := robs.Roots() + if err != nil { + return err + } + + roots = append(roots, r...) + } + + // Now prepare our new CAR file + fmt.Printf("Opening the %s file, with roots: %v\n", outputPath, roots) + rout, err := blockstore.OpenReadWrite(outputPath, roots) + if err != nil { + return err + } + + // Then aggregate all our blocks. + for _, path := range inputPaths { + fmt.Printf("processing %s\n", path) + robs, err := blockstore.OpenReadOnly(path, + blockstore.UseWholeCIDs(true), + ) + if err != nil { + return err + } + + cids, err := robs.AllKeysChan(context.Background()) + if err != nil { + return err + } + + for c := range cids { + fmt.Printf("Adding %s\n", c.String()) + block, err := robs.Get(context.Background(), c) + if err != nil { + return err + } + + rout.Put(context.Background(), block) + } + } + + fmt.Printf("Finalizing...\n") + err = rout.Finalize() + return err +} diff --git a/tooling/car/unixfs.go b/tooling/car/unixfs.go index 1621a0358..7a3496a4c 100644 --- a/tooling/car/unixfs.go +++ b/tooling/car/unixfs.go @@ -5,10 +5,9 @@ import ( "fmt" "os" "path" - "path/filepath" - "runtime" "strings" + "github.com/ipfs/gateway-conformance/tooling/fixtures" "github.com/ipfs/go-blockservice" "github.com/ipfs/go-cid" format "github.com/ipfs/go-ipld-format" @@ -94,9 +93,7 @@ func (d *UnixfsDag) MustGetRawData(names ...string) []byte { } func MustOpenUnixfsCar(file string) *UnixfsDag { - _, filename, _, _ := runtime.Caller(0) - basePath := filepath.Dir(filename) - fixturePath := path.Join(basePath, "..", "..", "fixtures", file) + fixturePath := path.Join(fixtures.Dir(), file) dag, err := newUnixfsDagFromCar(fixturePath) if err != nil { diff --git a/tooling/cmd/merge_fixtures.go b/tooling/cmd/merge_fixtures.go deleted file mode 100644 index a0c4d3cb8..000000000 --- a/tooling/cmd/merge_fixtures.go +++ /dev/null @@ -1,104 +0,0 @@ -package cmd - -import ( - "context" - "fmt" - "os" - "path/filepath" - "time" - - "github.com/ipfs/go-cid" - "github.com/ipld/go-car/v2/blockstore" -) - -/** - * list all `*.car` file in the basePath directory, recursively - */ -func listAllCarFile(basePath string) []string { - var carFiles []string - - filepath.WalkDir(basePath, func(path string, d os.DirEntry, err error) error { - if err != nil { - return err - } - if d.IsDir() { - return nil - } - if filepath.Ext(path) == ".car" { - path, err := filepath.Abs(path) - if err != nil { - return err - } - - carFiles = append(carFiles, path) - } - - return nil - }) - - return carFiles -} - -func MergeFixtures(outputPath string) error { - ctx, cancel := context.WithTimeout(context.Background(), time.Second) - defer cancel() - - carFiles := listAllCarFile("./fixtures") - - // First list all the roots in our fixtures - roots := make([]cid.Cid, 0) - - for _, f := range carFiles { - fmt.Printf("processing %s\n", f) - robs, err := blockstore.OpenReadOnly(f, - blockstore.UseWholeCIDs(true), - ) - if err != nil { - return err - } - - r, err := robs.Roots() - if err != nil { - return err - } - - roots = append(roots, r...) - } - - // Now prepare our new CAR file - fmt.Printf("Opening the %s file, with roots: %v\n", outputPath, roots) - rout, err := blockstore.OpenReadWrite(outputPath, roots) - if err != nil { - return err - } - - // Then aggregate all our blocks. - for _, f := range carFiles { - fmt.Printf("processing %s\n", f) - robs, err := blockstore.OpenReadOnly(f, - blockstore.UseWholeCIDs(true), - ) - if err != nil { - return err - } - - cids, err := robs.AllKeysChan(ctx) - if err != nil { - return err - } - - for c := range cids { - fmt.Printf("Adding %s\n", c.String()) - block, err := robs.Get(ctx, c) - if err != nil { - return err - } - - rout.Put(ctx, block) - } - } - - fmt.Printf("Finalizing...\n") - err = rout.Finalize() - return err -} diff --git a/tooling/env.go b/tooling/env.go new file mode 100644 index 000000000..cb0765cd7 --- /dev/null +++ b/tooling/env.go @@ -0,0 +1,17 @@ +package tooling + +import ( + "os" + "path/filepath" + "runtime" +) + +func Home() string { + home := os.Getenv("GATEWAY_CONFORMANCE_HOME") + if home == "" { + _, filename, _, _ := runtime.Caller(0) + basePath := filepath.Dir(filename) + return filepath.Join(basePath, "..") + } + return home +} diff --git a/tooling/fixtures/list.go b/tooling/fixtures/list.go new file mode 100644 index 000000000..d58cbec83 --- /dev/null +++ b/tooling/fixtures/list.go @@ -0,0 +1,43 @@ +package fixtures + +import ( + "os" + "path" + "path/filepath" + + "github.com/ipfs/gateway-conformance/tooling" +) + +func Dir() string { + home := tooling.Home() + return path.Join(home, "fixtures") +} + +func List() ([]string, error) { + var carFiles []string + + err := filepath.WalkDir(Dir(), func(path string, d os.DirEntry, err error) error { + if err != nil { + return err + } + if d.IsDir() { + return nil + } + if filepath.Ext(path) == ".car" { + path, err := filepath.Abs(path) + if err != nil { + return err + } + + carFiles = append(carFiles, path) + } + + return nil + }) + + if err != nil { + return nil, err + } + + return carFiles, nil +} diff --git a/tooling/specs/specs.go b/tooling/specs/specs.go new file mode 100644 index 000000000..a44ff42b5 --- /dev/null +++ b/tooling/specs/specs.go @@ -0,0 +1,75 @@ +package specs + +import "fmt" + +type maturity string + +const ( + wip maturity = "wip" + draft maturity = "draft" + reliable maturity = "reliable" + stable maturity = "stable" + permanent maturity = "permanent" + deprecated maturity = "deprecated" +) + +func (m maturity) isMature() bool { + switch m { + case reliable, stable, permanent: + return true + default: + return false + } +} + +type Spec string + +const ( + SubdomainGateway Spec = "subdomain-gateway" +) + +// All specs should be listed here. +var specMaturity = map[Spec]maturity{ + SubdomainGateway: stable, +} + +func (s Spec) IsMature() bool { + return specMaturity[s].isMature() +} + +var specEnabled = map[Spec]bool{} + +func (s Spec) IsEnabled() bool { + // If the spec was explicitly enabled or disabled, use that. + // Otherwise, use the maturity level. + if enabled, ok := specEnabled[s]; ok { + return enabled + } else { + return s.IsMature() + } +} + +func (s Spec) Enable() { + specEnabled[s] = true +} + +func (s Spec) Disable() { + specEnabled[s] = false +} + +func All() []Spec { + specs := []Spec{} + for spec := range specMaturity { + specs = append(specs, spec) + } + return specs +} + +func FromString(name string) (Spec, error) { + for _, spec := range All() { + if string(spec) == name { + return spec, nil + } + } + return "", fmt.Errorf("unknown spec: %s", name) +} diff --git a/tooling/test/sugar.go b/tooling/test/sugar.go index 728ecfaac..2b47100dd 100644 --- a/tooling/test/sugar.go +++ b/tooling/test/sugar.go @@ -126,4 +126,4 @@ func (h HeaderBuilder) Equals(value string, args ...any) HeaderBuilder { func (h HeaderBuilder) IsEmpty() HeaderBuilder { h.Check = check.CheckIsEmpty{} return h -} \ No newline at end of file +}