Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ jobs:
run: |
# The grader's exit status, not tail's, decides the step.
set -o pipefail
for suite in operations vectors; do
for suite in operations management vectors; do
./target/release/object-tests grade "cases/$suite.json" \
--record-unsupported "$RUNNER_TEMP/unsupported.json" \
-- adapters/borink/target/release/borink-adapter | tail -n 1
Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ version = "0.2.0"
edition = "2024"
license = "MIT"
description = "A data-driven object storage conformance grader"
include = ["src/**", "cases/**", "tests/**", "README.md", "Cargo.toml"]
include = ["src/**", "cases/**", "tests/**", "README.md", "REFERENCE.md", "Cargo.toml"]
[dependencies]
serde = { version = "1", features = ["derive"] }
serde_json = "1"
Expand Down
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,13 @@ There are also a few live tests that are just to see if you can actually make a

| Suite | Contents |
|---|---|
| `operations.json` | Operations over recorded HTTP |
| `operations.json` | Object operations over recorded HTTP |
| `management.json` | Bucket and container operations over recorded HTTP |
| `vectors.json` | Digests and signatures |
| `live.json` | Probes against a live service |

[REFERENCE.md](REFERENCE.md) describes the adapter protocol.

## Example test of `borink-object-storage`

`adapters/borink` builds the adapter that borink-org/object-storage keeps in [`hosts/object-tests`](https://github.com/borink-org/object-storage/tree/master/hosts/object-tests), at the commit its `Cargo.toml` pins. That adapter lives beside the crates whose API it calls, and object-storage grades every change against this suite.
Expand Down
43 changes: 43 additions & 0 deletions REFERENCE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Grader reference

```nu
./target/debug/object-tests grade cases/operations.json --profile s3 -- ADAPTER
```

`--case`, `--provider` and `--profile` select cases. `--expected-unsupported FILE` lists cases, patterns with `*`, or `field:/value/...` result fields that may be unsupported; `--record-unsupported FILE` writes that list. Exit code `0` means every case passed.

## Adapter protocol

The grader starts the adapter once per case, writes one JSON input to its stdin and reads one JSON result from its stdout:

```json
{"version":1,"mode":"offline","provider":"azure","endpoint":{"url":"http://127.0.0.1:PORT","account":"fixture","bucket":"fixture"},"call":{"op":"get","key":"a b"}}
```

Use `endpoint` with dummy credentials, and `endpoint.proxy_url` as the HTTP proxy where given. Map `call` to the client and report what it returns. Do not encode keys, repair client behavior or make extra calls.

```json
{"outcome":"ok","value":{"body_base64":"aGVsbG8="}}
{"outcome":"error","kind":"not_found","status":404,"code":"BlobNotFound"}
{"outcome":"refused","kind":"invalid_range","parameter":"range"}
{"outcome":"unsupported","reason":"not mapped","parameter":"range"}
```

- `error`: the service refused the call.
- `refused`: the client refused it before sending.
- `unsupported`: the client cannot make it. Naming a `parameter` declines that parameter, which some cases permit.

An error names a `kind`. `not_found` means the object is absent, `container_not_found` the bucket or container, and `missing` that the answer cannot say which. The others are `precondition`, `not_modified`, `permission_denied` and `other`.

Leave out a result property the client does not expose, and name it in `unsupported_fields`, as `[{"at":"/value/content_md5_base64","reason":"..."}]`.

Keys are unescaped, bodies base64, ranges an offset and an exclusive end. A `checksum` without `value_base64` is for the client to compute. Tags are an object, or a list of `{"key", "value"}` pairs where a key may repeat. `content_range` reports a served range as `bytes FIRST-LAST/SIZE`.

## Cases

[model.rs](src/model.rs) defines the format.

- `origin` says where the responses come from: `observed` (the default), `transient` (sent, but not on demand) or `constructed` (written by hand). `derived_from` names the recorded case such a case edits.
- `refusal` holds the checks a refusal must pass; a case without it accepts none. `decline_permitted` lets an `unsupported` result that names the parameter pass.
- Checks address JSON pointers. `matches` is a whole-string regex, `xml` compares structure, and `optional` permits absence only. `blob_batch` and `body_checksum` apply at the request's root.
- A request header's value holds one character per byte, so `é` is the byte e9 and `é` its UTF-8.
73 changes: 66 additions & 7 deletions adapters/borink/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion adapters/borink/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ publish = false
[dependencies]
# The adapter itself lives in borink-org/object-storage, beside the crates whose
# API it calls. This is the commit whose adapter speaks this revision's protocol.
borink-object-tests-adapter = { git = "https://github.com/borink-org/object-storage.git", rev = "03829e1253bccf62ae55d09b607731a5c4b74952" }
borink-object-tests-adapter = { git = "https://github.com/borink-org/object-storage.git", rev = "deb671c5bd30939bef1bb1144f36c3a43e66d9d0" }

[features]
# HTTPS, which only the live probes need. Offline grading uses plain HTTP on loopback.
Expand Down
Loading
Loading