Skip to content

Releases: transmitworks/tropel

tropel v0.6.0

Choose a tag to compare

@github-actions github-actions released this 10 Sep 21:35
fa4e230

The release that makes tropel installable as a dependency. Every crate is
published to crates.io, so a consumer — KnockPort first — depends on
version = "0.6.0" instead of a git SHA.

The API is unstable pre-1.0. tropel-sdk is deliberately NOT in this
lockstep: it is the contract a third-party adapter author depends on, in its
own repo with its own history, and its version tracks its own API.

  • Added — tropel reads KnockPort collections
  • Changed — the whole crate tree is published
  • Fixed — publish-dry-run.sh cried wolf on a first full-tree publish
Full notes for 0.6.0

The release that makes tropel installable as a dependency. Every crate is
published to crates.io, so a consumer — KnockPort first — depends on
version = "0.6.0" instead of a git SHA.

The API is unstable pre-1.0. tropel-sdk is deliberately NOT in this
lockstep: it is the contract a third-party adapter author depends on, in its
own repo with its own history, and its version tracks its own API.

Added — tropel reads KnockPort collections

tropel-input-knockport. tropel could already import Postman, Bruno,
Insomnia, HAR, OpenAPI, k6 and .http — every competitor's format — and not
the one format both products own, so tropel run my-collection/ failed on it.

The format is a directory, not a file (knockport.yaml +
requests/**/*.yaml), so the adapter uses parse_with_path — the hook that
already existed for k6's module resolution. It follows the client's own rules
rather than convenient ones: the filename is the identity (KP-101, so no
id: keys and a missing name: takes the file stem), order: is
authoritative and lists filenames, files absent from order: still import
rather than being dropped, and disabled rows are not sent. Scripts and
assertions are reported, not silently dropped — tropel's script realm is
the pm/k6 surface, and running a KnockPort script there would not be the same
script.

Changed — the whole crate tree is published

  • 35 crates at 0.6.0, with the binary, tropel-web and the four npm
    packages. version-lockstep.sh reports all seven surfaces agreeing.
  • 28 publish = false lines removed. Six of the seven crates KnockPort
    links were opted out of the registry, which is why it had to pin a git SHA.
  • 14 crates gained a description — cargo refuses to publish without
    one, so this was the hard prerequisite.
  • Every workspace tropel-* declaration carries a version. A path-only
    dependency cannot be published at all: consumers resolve the version.

0.6.0 rather than 0.5.6 because the release is breaking — tropel-auth's
EdgeGrid error type went TropelError → String and its clock/CSPRNG
helpers became feature-gated; Request gained a proxy field. Pre-1.0 that
is a minor bump by this repo's own policy.

Fixed — publish-dry-run.sh cried wolf on a first full-tree publish

It read "no matching package named X found" as a hard failure. Cargo words
"a workspace sibling is not on crates.io yet" two ways and only the other one
was recognised, so the first full-tree run reported "BLOCKED: 12 of 35 …
the gate is NOT met"
for the entirely normal pre-publish state. The guard
against a real failure is unchanged: the named crate must be a workspace
member whose own version is the one being asked for.

It also now prints the topological publish order. Sequencing 35 crates by
hand is how a version number gets burned on a failed publish.

Install

Linux builds are static musl — they run unmodified in Alpine, distroless
and scratch containers.

curl -fsSL https://github.com/transithq/tropel/releases/download/v0.6.0/tropel-v0.6.0-x86_64-unknown-linux-musl.tar.gz | tar xz
sudo install -m755 tropel-v0.6.0-x86_64-unknown-linux-musl/tropel /usr/local/bin/
Other platforms, checksum verification, building from source
# Linux arm64
curl -fsSL https://github.com/transithq/tropel/releases/download/v0.6.0/tropel-v0.6.0-aarch64-unknown-linux-musl.tar.gz | tar xz

# macOS Apple Silicon
curl -fsSL https://github.com/transithq/tropel/releases/download/v0.6.0/tropel-v0.6.0-aarch64-apple-darwin.tar.gz | tar xz

# macOS Intel
curl -fsSL https://github.com/transithq/tropel/releases/download/v0.6.0/tropel-v0.6.0-x86_64-apple-darwin.tar.gz | tar xz

Windows (PowerShell):

Invoke-WebRequest -Uri "https://github.com/transithq/tropel/releases/download/v0.6.0/tropel-v0.6.0-x86_64-pc-windows-msvc.zip" -OutFile tropel.zip
Expand-Archive tropel.zip -DestinationPath .

macOS binaries are unsigned, so Gatekeeper quarantines a downloaded archive.
Clear it with xattr -d com.apple.quarantine ./tropel.

curl -fsSLO https://github.com/transithq/tropel/releases/download/v0.6.0/SHA256SUMS
sha256sum -c SHA256SUMS --ignore-missing      # macOS: shasum -a 256 -c

From source (Rust 1.94+): cargo build --release puts it at
./target/release/tropel.

Each tropel-v0.6.0-<target> archive carries tropel, tropel-controller and
tropel-agent; tropel-wasm-v0.6.0.tar.gz is the browser tier
(core-wasm, input-wasm, runtime-wasm, shims, also on npm at
0.6.0); SHA256SUMS covers every asset.


Full changelog ·
Docs ·
Issues · Apache-2.0

tropel v0.5.5

Choose a tag to compare

@github-actions github-actions released this 10 Sep 13:46
ba79849

Tropel is an open-source load-testing framework in Rust. It runs
Postman collections, HAR files, OpenAPI specs, Bruno and
Insomnia exports, and k6 scripts as load tests — a native Rust hot
path with an embedded QuickJS engine for script execution.

Pre-1.0: the API is unstable. k6 parity is actively expanding and the
tropel-sdk surface still changes between minor versions. Pin an exact
version if you depend on it.

Install

Pick your platform. Linux builds are static musl — they run unmodified in
Alpine, distroless and scratch containers.

# Linux x86_64
curl -fsSL https://github.com/transithq/tropel/releases/download/v0.5.5/tropel-v0.5.5-x86_64-unknown-linux-musl.tar.gz | tar xz
sudo install -m755 tropel-v0.5.5-x86_64-unknown-linux-musl/tropel /usr/local/bin/

# Linux arm64
curl -fsSL https://github.com/transithq/tropel/releases/download/v0.5.5/tropel-v0.5.5-aarch64-unknown-linux-musl.tar.gz | tar xz
sudo install -m755 tropel-v0.5.5-aarch64-unknown-linux-musl/tropel /usr/local/bin/

# macOS Apple Silicon
curl -fsSL https://github.com/transithq/tropel/releases/download/v0.5.5/tropel-v0.5.5-aarch64-apple-darwin.tar.gz | tar xz
sudo install -m755 tropel-v0.5.5-aarch64-apple-darwin/tropel /usr/local/bin/

# macOS Intel
curl -fsSL https://github.com/transithq/tropel/releases/download/v0.5.5/tropel-v0.5.5-x86_64-apple-darwin.tar.gz | tar xz
sudo install -m755 tropel-v0.5.5-x86_64-apple-darwin/tropel /usr/local/bin/

Windows (PowerShell):

Invoke-WebRequest -Uri "https://github.com/transithq/tropel/releases/download/v0.5.5/tropel-v0.5.5-x86_64-pc-windows-msvc.zip" -OutFile tropel.zip
Expand-Archive tropel.zip -DestinationPath .

macOS note: binaries are unsigned, so Gatekeeper will quarantine a downloaded
archive. Clear it with xattr -d com.apple.quarantine ./tropel.

Verify your download

curl -fsSLO https://github.com/transithq/tropel/releases/download/v0.5.5/SHA256SUMS
sha256sum -c SHA256SUMS --ignore-missing      # macOS: shasum -a 256 -c

From source (Rust 1.94+)

cargo build --release      # ./target/release/tropel

Assets

asset contents
tropel-v0.5.5-<target>.tar.gz / .zip tropel, tropel-controller, tropel-agent + LICENSE, README
tropel-wasm-v0.5.5.tar.gz the browser/edge tier — core-wasm, input-wasm, runtime-wasm, shims
SHA256SUMS checksums over every asset above

Targets: x86_64/aarch64 Linux (musl), x86_64/aarch64 macOS,
x86_64 Windows (MSVC).

Quick start

tropel run collection.json --vus 50 --duration 2m   # Postman, HAR, OpenAPI, k6, Bruno, Insomnia
tropel inspect collection.json                      # preview without sending traffic
tropel extensions                                   # list the input formats this binary ships

Full docs: docs/ — CLI
reference, executors, scripting, metrics, extensions, distributed execution.

What's in it

  • Seven executors — constant-vus, ramping-vus, shared/per-vu iterations,
    constant & ramping arrival rate, externally-controlled, with graceful
    stop/ramp-down, think time and pacing
  • Postman pm.* scripting — tests, assertions, variable scopes,
    setNextRequest, sendRequest, custom metrics
  • k6-style scripting — http.*, check(), group(), sleep(),
    Counter/Gauge/Rate/Trend; exported options honoured
  • HDR-histogram metrics — p50/p90/p95/p99, sub-timings, tag-scoped
    aggregation, thresholds with k6-compatible abort semantics
  • Streaming outputs — NDJSON, StatsD, InfluxDB, Prometheus, OTLP
    (protobuf + gzip), plus stdout/JSON/CSV reporters
  • Distributed — k6-style execution segments for multi-node runs
  • Extensible — tropel-sdk + tropel build --with <crate>, plus a
    sandboxed WASM plugin tier for third-party input formats

npm packages

@tropel/core-wasm@0.5.5   @tropel/input-wasm@0.5.5   @tropel/runtime-wasm@0.5.5   @tropel/shims@0.5.5

Changes in 0.5.5

Three tagged versions shipped without a changelog entry — 0.3.0 and 0.5.4 were
tagged, and the file stopped at 0.2.0. This section therefore covers
everything since 0.2.0 rather than 0.5.4 alone, because a reader upgrading
from the last documented release needs the whole span. scripts/release-notes.sh
extracts it, so it is also what the release page shows.

The API is unstable pre-1.0. tropel-sdk is at 0.4.0 and moves between
minor versions; k6 parity is still expanding.

Added — the browser and desktop tiers can now execute what they could only configure

The theme of this release. KnockPort (and any embedder) could create a
digest, Hawk, SigV4, OAuth1, WSSE or EdgeGrid auth config and every send
refused, because the signing lived behind tropel-auth's reqwest
feature and wasm32-unknown-unknown cannot compile reqwest. The rules moved
out from behind that gate and got a JS facade:

  • Pure auth builders (tropel-auth::builders, ungated): digest
    challenge-response, Hawk, AWS SigV4 — including the service-derivation and
    S3 double-encoding rules — and OAuth1's RFC 5849 §3.4.1.2 base-string URI
    and form parsing. signers.rs became a thin adapter that reads fields off a
    reqwest::Request and calls in, so there is one implementation rather than
    two.
  • @tropel/core-wasm exports them: digestSign, hawkSign,
    awsSigV4Sign, oauth1Sign, edgegridSign, plus
    oauth1SignatureMethods() so a picker offers exactly what the signer
    accepts instead of keeping its own list. edgegridSign takes the whole
    request, because the signature covers it — method, url, the named
    headers' values and the body hash — and refuses a missing nonce or
    timestamp rather than fabricating one: this tier has no clock and no
    CSPRNG worth using, and crypto.getRandomValues is a better source anyway.
  • tropel agent's /auth/sign answers for EdgeGrid and WSSE too. It had
    the signers (via build_auth_signer, which /execute already calls) and
    refused them as unknown at the endpoint the desktop tier actually calls —
    one surface signing what the other called unsupported. Its refusal message
    was worse than the gap: a hardcoded "supported: digest, hawk, awsSigV4,
    oauth1" that went stale the moment a scheme was added, so a caller reading
    it would conclude the agent could not sign something it could. Now derived
    from one declaration, with a test asserting every entry appears in the
    refusal.
  • WSSE UsernameToken and Akamai EdgeGrid (EG1-HMAC-SHA256) through the
    signer builder. EdgeGrid uses yyyyMMddTHH:mm:ss+0000, not RFC 3339 — an
    ISO stamp goes into the signing data verbatim and produces a 401 that
    mentions neither the clock nor the format. Bodies over Akamai's 128 KiB
    default are not hashed rather than truncated, because a truncated
    content hash is a signature the server cannot reproduce.
  • OAuth1 PLAINTEXT, over TLS only. RSA-* still refuses by name rather than
    silently downgrading to HMAC-SHA1.

Added — declarative assertions in Rust

  • assert_evaluate(response, assertions) with 28 operators, their arity,
    and target resolution (status, header.*, body.*, JSON paths). Pinned
    by a test that asserts the set and its order, because the editor renders
    from it.
  • @tropel/core-wasm exports assertEvaluate and the operator table. The
    regex matcher is the host's RegExp, passed in: linking a Rust regex
    would put back the 152 KB TR-434 removed, and it would be unfaithful —
    JavaScript has backreferences and lookaround that Rust's regex does not.
  • Failure messages name the target, not the assertion, so a reader learns
    which field was wrong rather than which rule fired.

Added — the loopback agent grew a rules surface

tropel agent went from an execute-only endpoint to the full seam an embedder
needs: /resolve and /resolve/batch, /assert, /operators, /auth/sign,
/auth/oauth2, /variables/dynamic (and its batched twin), /constants, and
/script — which carries all four variable scopes both ways, seeds the
response a test asserts against, returns what the script did to the request,
and has a bidirectional host-callback channel so bru.runRequest works.
--exit-with-parent means a dead supervisor cannot leak a live agent.

Added — inputs and protocols

  • Bruno .bru text format parsed directly, keeping disabled entries,
    vars, settings and assertions — a disabled row is deliberate local state,
    and dropping it makes the checkbox meaningless.
  • OpenAPI declarations reach the Scenario (ScenarioItem::contract): the
    declared parameters with their types and required flags, the declared
    request body, and the declared responses keyed by the spec's own string
    so 4XX and default survive. Plus path_template, because building an
    executable request substitutes /users/{id} into /users/example and the
    substituted segment is indistinguishable from a literal one afterwards.
  • Private CA bundles on the TLS builder (root_cert_paths,
    keep_system_roots). Multi-cert bundles read every certificate —
    Certificate::from_pem reads only the FIRST, so a chain handed to it whole
    drops the intermediate and then fails verification in a very confusing way.
    A config that would trust no CA at all is refused by name.
  • A proxy surface: off / fixed / system / pac, proxy auth, and
    bypass rules with exact hosts, *.suffix (subdomains only), bare *, IP
    and CIDR. A malformed bypass entry is an error, not a fuzzy match, because
    both ways of getting it wrong route traffic silently. PAC directive
    failover ordering with a bounded TTL is implemented; PAC evaluation
    (running FindProxyForURL) still needs a JS engine and is not.
  • Per-request proxy and TLS: Request::proxy alongside Request::certificate,
    ...
Read more

tropel v0.2.0

Choose a tag to compare

@github-actions github-actions released this 31 Aug 11:23
de6c337

Tropel is an open-source load-testing framework in Rust. It runs
Postman collections, HAR files, OpenAPI specs, Bruno and
Insomnia exports, and k6 scripts as load tests — a native Rust hot
path with an embedded QuickJS engine for script execution.

Pre-1.0: the API is unstable. k6 parity is actively expanding and the
tropel-sdk surface still changes between minor versions. Pin an exact
version if you depend on it.

Install

Pick your platform. Linux builds are static musl — they run unmodified in
Alpine, distroless and scratch containers.

# Linux x86_64
curl -fsSL https://github.com/transithq/tropel/releases/download/v0.2.0/tropel-v0.2.0-x86_64-unknown-linux-musl.tar.gz | tar xz
sudo install -m755 tropel-v0.2.0-x86_64-unknown-linux-musl/tropel /usr/local/bin/

# Linux arm64
curl -fsSL https://github.com/transithq/tropel/releases/download/v0.2.0/tropel-v0.2.0-aarch64-unknown-linux-musl.tar.gz | tar xz
sudo install -m755 tropel-v0.2.0-aarch64-unknown-linux-musl/tropel /usr/local/bin/

# macOS Apple Silicon
curl -fsSL https://github.com/transithq/tropel/releases/download/v0.2.0/tropel-v0.2.0-aarch64-apple-darwin.tar.gz | tar xz
sudo install -m755 tropel-v0.2.0-aarch64-apple-darwin/tropel /usr/local/bin/

# macOS Intel
curl -fsSL https://github.com/transithq/tropel/releases/download/v0.2.0/tropel-v0.2.0-x86_64-apple-darwin.tar.gz | tar xz
sudo install -m755 tropel-v0.2.0-x86_64-apple-darwin/tropel /usr/local/bin/

Windows (PowerShell):

Invoke-WebRequest -Uri "https://github.com/transithq/tropel/releases/download/v0.2.0/tropel-v0.2.0-x86_64-pc-windows-msvc.zip" -OutFile tropel.zip
Expand-Archive tropel.zip -DestinationPath .

macOS note: binaries are unsigned, so Gatekeeper will quarantine a downloaded
archive. Clear it with xattr -d com.apple.quarantine ./tropel.

Verify your download

curl -fsSLO https://github.com/transithq/tropel/releases/download/v0.2.0/SHA256SUMS
sha256sum -c SHA256SUMS --ignore-missing      # macOS: shasum -a 256 -c

From source (Rust 1.94+)

cargo build --release      # ./target/release/tropel

Assets

asset contents
tropel-v0.2.0-<target>.tar.gz / .zip tropel, tropel-controller, tropel-agent + LICENSE, README
tropel-wasm-v0.2.0.tar.gz the browser/edge tier — core-wasm, input-wasm, runtime-wasm, shims
SHA256SUMS checksums over every asset above

Targets: x86_64/aarch64 Linux (musl), x86_64/aarch64 macOS,
x86_64 Windows (MSVC).

Quick start

tropel run collection.json --vus 50 --duration 2m   # Postman, HAR, OpenAPI, k6, Bruno, Insomnia
tropel inspect collection.json                      # preview without sending traffic
tropel extensions                                   # list the input formats this binary ships

Full docs: docs/ — CLI
reference, executors, scripting, metrics, extensions, distributed execution.

What's in it

  • Seven executors — constant-vus, ramping-vus, shared/per-vu iterations,
    constant & ramping arrival rate, externally-controlled, with graceful
    stop/ramp-down, think time and pacing
  • Postman pm.* scripting — tests, assertions, variable scopes,
    setNextRequest, sendRequest, custom metrics
  • k6-style scripting — http.*, check(), group(), sleep(),
    Counter/Gauge/Rate/Trend; exported options honoured
  • HDR-histogram metrics — p50/p90/p95/p99, sub-timings, tag-scoped
    aggregation, thresholds with k6-compatible abort semantics
  • Streaming outputs — NDJSON, StatsD, InfluxDB, Prometheus, OTLP
    (protobuf + gzip), plus stdout/JSON/CSV reporters
  • Distributed — k6-style execution segments for multi-node runs
  • Extensible — tropel-sdk + tropel build --with <crate>, plus a
    sandboxed WASM plugin tier for third-party input formats

npm packages

@tropel/core-wasm@0.2.0   @tropel/input-wasm@0.2.0   @tropel/runtime-wasm@0.2.0   @tropel/shims@0.2.0

Changes in 0.2.0

First tagged release. Versioned 0.2.0 rather than 0.1.0 across all seven
surfaces (binary, tropel-engine, tropel-web, and the four @tropel/* npm
packages — tropel-engine is what --version prints) because
@tropel/shims@0.1.0 and @tropel/runtime-wasm@0.1.0 were published on
2026-08-10 and npm versions are immutable: those artifacts still call the
__tropel_pm_* bridges this tree renamed to __tropel_trp_*, so they cannot
be paired with this runtime. 0.2.0 is the first npm set that matches the
binary.

The API is unstable pre-1.0. k6 parity is actively expanding and the
tropel-sdk surface still moves between minor versions.

Fixed — correctness

  • sleep() was non-functional on every declarative format (Postman, HAR,
    OpenAPI, .http, Insomnia, Bruno). __tropel_native_sleep was registered as
    an async host function on an rquickjs Runtime with no spawner, so the first
    call panicked with "tried to use async function in non async runtime";
    rquickjs stashed that panic and re-raised it on whichever VU next threw — a
    different VU on a shared runtime. Now a blocking sleep with an absolute
    deadline. This also fixes bru.sleep(ms).
  • Headline http_req_duration / iteration_duration were computed from one
    metrics shard.
    shard_for_key hashed the metric name and its tags,
    against its own documented contract, so {url:/a} and {url:/b} landed on
    different shards and the merge kept only the largest-count partial. Count,
    avg, min, max and p95 were derived from a fraction of the population.
  • http_req_failed was the maximum of per-shard rates — 10 failures across
    400 requests reported 0.10 instead of 0.025. Now merges numerator and
    denominator.
  • output_samples_dropped / aggregator_samples_dropped were multiplied by
    SHARD_COUNT
    — process-global atomics read once per shard and summed, so
    one output dropping 1,000 samples reported 4,000.
  • absorb_snapshot discarded over-cap series without counting them, so a
    controller merge that lost series still reported "unverified": false.
  • The QuickJS promise-rejection tracker was overwritten by every new VU.
    It is a property of the runtime, not the context, so on a shared runtime one
    VU's unhandled rejection was recorded into another's map — the first VU
    passed silently, the second failed with an error it never raised.
  • bru.setVar / setEnvVar / setCollectionVar were not inverses of their
    getters
    — they wrote with String(value) while the getters used
    JSON.parse, so setVar('id','1234') read back as the number 1234 and an
    object read back as "[object Object]".
  • @tropel/shims shipped without k6-core.js, leaving every
    @tropel/runtime-wasm embedder without check/group/Counter/Gauge/
    Rate/Trend. The npm bundle list is now derived from Shim::ALL in the
    Rust source, so the two cannot diverge silently.
  • ws_* samples carried group:"ws" where k6's root group is "".

Changed

  • Per-VU JS shims are now selected by input format, and the bytecode cache is keyed by bundle (TR-501). The compiled-shim-bytecode cache was a single OnceLock keyed on nothing, so only the full default bundle could use it and every narrowed bundle paid a per-VU source parse+compile — which made shim gating a pessimisation: an http-only script measured 557,824 B/VU against the full bundle's 497,584 B/VU. The cache is now keyed by bundle identity, and ShimBundle::for_format picks the shim set from the resolving InputAdapter::id(). ✅MEAS (Apple M2, release, N=25, contexts sharing one worker-thread Runtime): har/openapi/http/insomnia 203,400 B/VU, content-gated http-only 261,878, postman 369,424, k6/unknown format 385,324. (An earlier revision of this entry quoted 280,480 / 336,848 / 479,952 / 497,584 — those came from a harness that summed a shared runtime's heap once per context and divided by N, reporting the whole 25-context total as a per-VU figure. Withdrawn; do not cite them.) Behaviour change: a context created by create_vu_js_context no longer necessarily has all of pm/chai/_/CryptoJS/bru — bru is dropped for every format except bru, and the assertion/utility libraries are dropped for the four formats whose adapters cannot emit a script. pm.js is still loaded for every format. The k6 Driver's own shim bundle (tropel-input-k6) is unchanged.
  • License: dual MIT OR Apache-2.0 → Apache-2.0 only. LICENSE-MIT removed; LICENSE-APACHE now carries the full canonical text (previously a placeholder stub) and is copied into crates/tropel-sdk/ so published artifacts include it. deny.toml keeps MIT on the dependency allowlist (third-party crates only).

Full changelog: CHANGELOG.md ·
Issues: github.com/transithq/tropel/issues ·
Licence: Apache-2.0