Skip to content

test-runner: http-service URL concat produces double-slash when adapter prints trailing slash (Expected 200, got 404) #228

@cataggar

Description

@cataggar

Summary

http-service (the only wasm32-wasip3 fixture that uses world wasi:http/service) fails when the runtime-adapter prints the listener URL with a trailing slash — the URL the runner sends ends up with // and the in-fixture path-router falls through to 404. This makes the upstream tests/wasi-testsuite/adapters/wasmtime.py adapter fail the very first request of http-service.json, even though both the wasmtime side and the fixture itself are otherwise behaving correctly.

The bug is in test_suite_runner.py's URL construction, not in wasmtime or in the fixture.

Reproduction

# In a clean checkout of wasi-testsuite, on a host with `wasmtime` 44.0.1
# (the first release with `-Sp3` support) on PATH:
cd tests/rust/testsuite
make build
cd ../../..
WASMTIME=wasmtime python3 -m wasi_test_runner \
    --test-suite tests/rust/testsuite/wasm32-wasip3 \
    --runtime-adapter adapters/wasmtime.py

Output:

Test http-service failed
  [Unexpected] Request(method='GET', path='/', response=Response(status=200, headers={'content-type': 'text/plain'}, body='hey\n')): Expected status 200, got 404

Root cause

wasi_test_runner/test_suite_runner.py:95-105 builds the server URL by reading the first stderr line and slicing from http:// to the next whitespace:

def get_http_server(self) -> str | None:
    if self._http_server:
        return self._http_server
    line = self.get_pipe('stderr').readline().strip()
    start = line.find('http://')
    ...
    self._http_server = line[start:].split()[0]
    return self._http_server

do_request then concatenates the server URL with the request path:

url = http_server + req.path

wasmtime serve prints Serving HTTP on http://127.0.0.1:<port>/ (note the trailing slash on the URL — wasmtime treats the listen URL as a base), so http_server == 'http://127.0.0.1:<port>/'. http-service.json's requests have "path": "/", so the final URL becomes http://127.0.0.1:<port>//. The fixture's path-router (src/bin/http-service.rs) matches the request path literally:

let (status, payload) = match (request.get_method(), request.get_path_with_query()) {
    (Method::Get, Some(s)) if s == "/" => handle_root(&headers),
    (Method::Get, _) => handle_not_found(&headers),   // ← this branch wins for "//"
    ...
};

so the request returns 404 instead of 200.

Confirmed with the test runner's exact request via requests:

GET http://127.0.0.1:<port>/  → 200 OK  body='hey\n'
GET http://127.0.0.1:<port>// → 404 Not Found  body=''

The wamr runtime adapter (used by the WAMR-Zig project's parity gate) happens to print http://127.0.0.1:<port> without a trailing slash, so the concatenation produces a single / and the fixture passes. The trailing-slash form is at least as legal as the slash-less form — it's the runner's naïve concat that's wrong.

Suggested fix

Normalize the URL in get_http_server (or in do_request) so the path join can't produce //:

self._http_server = line[start:].split()[0].rstrip('/')

Alternatively, use urllib.parse.urljoin or a similar URL-aware join in do_request. Either fix gets the upstream adapters/wasmtime.py over the http-service line and lets multiple runtime adapters coexist regardless of whether they print the trailing slash.

Why this matters

This is the only wasm32-wasip3 fixture that uses world wasi:http/service, so the wasmtime adapter's http-service baseline silently fails because of a wsai-testsuite runner bug. Other runtimes (e.g. WAMR-Zig) only escape because they don't print the trailing slash — they'd hit the same bug if they ever did. Filed as part of cataggar/wamr#583 C1 — the wamr-side parity gate against wasmtime — where it surfaces as one of four documented "wasmtime side" failures.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions