Test the downstream impact of Rust crate changes before publishing. Locate regressions and API breakages.
Test any versions of your crate and/or a local WIP version X any versions of any specified dependents (defaults to top 10 most popular dependents).
Let natural version resolution take place --test-versions "0.8.50 0.8.51" (to simulate publishing to crates.io)
OR use --force-versions "0.8.52 0.8.53" to simulate them upgrading to a new version of your crate with an edit of their cargo.toml.
Why did you name it cargo-copter? To make it absolutely impossible to find via google.
cargo binstall cargo-copter
# Or install from source
cargo install cargo-coptercd my-crate
cargo copter --top-dependents 2Testing 2 reverse dependencies of rgb
Dependents: ansi_colours, resvg
rgb versions: baseline, 0.8.91-alpha.3 [!], 0.8.52
2 Γ 3 = 6 tests
this = 0.8.52 bd35c97* (your work-in-progress version)
βββββββββββββββββββββββββββββββ¬βββββββββββββ¬βββββββββββββββββββ¬βββββββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββ
β Offered β Spec β Resolved β Dependent β Result Time β
βββββββββββββββββββββββββββββββΌβββββββββββββΌβββββββββββββββββββΌβββββββββββββββββββββββββββββββΌββββββββββββββββββββββββββ€
β - baseline β 0.8 β 0.8.52 π¦ β ansi_colours 1.2.3 β passed βββ 8.3s β
β β β 0.8.91-alpha.3 [β β!] β β =0.8.... β 0.8.91-alpha.... β ansi_colours 1.2.3 β test failed βββ 1.7s β
β ββββββββββββββββββββββββββ΄βββββββββββββ ββββββββββββββββββββββββββββββββ β
β β cargo test failed on ansi_colours β
β β error[E0277]: the trait bound `Gray<u8>: ToLab` is not satisfied β
β β --> src/test.rs:45:47 β
β ββββββββββββββββββββββββββ¬βββββββββββββ¬βββββββββββββββββββ¬βββββββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββ€
β β =0.8.52 β 0.8 β 0.8.52 π¦ β ansi_colours 1.2.3 β passed βββ 3.1s β
βββββββββββββββββββββββββββββββΌβββββββββββββΌβββββββββββββββββββΌβββββββββββββββββββββββββββββββΌββββββββββββββββββββββββββ€
β - baseline β 0.8 β 0.8.52 π¦ β resvg 0.45.1 β passed βββ 8.4s β
β β β 0.8.91-alpha.3 [β β!] β β =0.8.... β 0.8.91-alpha.... β resvg 0.45.1 β passed βββ 4.9s β
β β =0.8.52 β 0.8 β 0.8.52 π¦ β resvg 0.45.1 β passed βββ 2.1s β
βββββββββββββββββββββββββββββββ΄βββββββββββββ΄βββββββββββββββββββ΄βββββββββββββββββββββββββββββββ΄ββββββββββββββββββββββββββ
Version Comparison:
Default 0.8.52 0.8.91-alpha.3
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Total tested 2 2 2
Already broken 0 - -
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Passed fetch 2 2 2
Passed check 2 2 2
Passed test 2 2 -1 β 1
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Fully passing 2 2 -1 β 1
Markdown report: copter-report.md
Detailed failure logs: copter-failures.log
π‘ To analyze API changes that may have caused regressions:
cargo install cargo-public-api
cargo public-api diff rgb@0.8.50 rgb@0.8.91 # compare two crates.io versions
cd .copter/staging/rgb-0.8.91 && cargo public-api diff 0.8.50 # compare local against crates.io
# From source
git clone https://github.com/imazen/cargo-copter
cd cargo-copter && cargo install# Run in Docker (for security isolation)
cargo-copter --docker --top-dependents 5
# Or directly with Docker
docker run --rm -v $(pwd):/workspace ghcr.io/imazen/cargo-copter:latest \
--path /workspace --top-dependents 5# Test top dependents
cargo-copter --top-dependents 10
# Test specific dependents
cargo-copter --dependents image serde tokio
# Test specific dependent versions
cargo-copter --dependents image:0.25.8 serde:1.0.200
# Test multiple versions (includes baseline automatically)
cargo-copter --test-versions "0.8.50 0.8.51"
# Force incompatible versions (bypasses semver)
cargo-copter --force-versions "0.9.0 1.0.0-rc.1"
# Test published crate without local source
cargo-copter --crate rgb --force-versions "0.8.51"
# Clean cache and retest
cargo-copter --clean --top-dependents 5
# Test local unpublished dependents (works without crates.io)
cargo-copter --path . --dependent-paths ~/work/my-dep1 ~/work/my-dep2
# Auto-discover local dependents in directories
cargo-copter --path . --dependent-dir ~/work/ ~/work/zen/
# Auto-discover via glob (filters to crates that depend on yours)
cargo-copter --path . --dependent-glob "~/work/*/Cargo.toml"
# Breadth + depth: test top dependents, then add popular older versions
cargo-copter --top-dependents 5 --top-versions 50-p, --path <PATH> Path to crate (directory or Cargo.toml)
-c, --crate <NAME> Test published crate by name
--top-dependents <N> Test top N by downloads [default: 5]
--top-versions <Q> Budget for extra version slots across dependents
--dependents <CRATE[:VER]> Test specific crates (space-separated)
--dependent-paths <PATH> Test local crate paths (works with unpublished crates)
--dependent-glob <GLOB> Discover local dependents via glob patterns
--dependent-dir <DIR> Discover local dependents in directories (1 level deep)
--test-versions <VER>... Test multiple versions
--force-versions <VER>... Force versions (bypass semver)
--staging-dir <PATH> Cache directory [default: ~/.cache/cargo-copter/staging]
--only-fetch Only fetch dependencies (skip check and test)
--only-check Only fetch and check (skip tests)
--clean Clean cache before testing
--error-lines <N> Error lines to show [default: 10]
--simple Verbal output format (good for AI parsing)
--skip-normal-testing Skip auto-patch mode for forced versions
--json JSON output
- Baseline test: Tests each dependent with currently published version
- Offered version tests: Tests with specified versions (--test-versions, --force-versions, or local WIP)
- Three-step ICT: Install (fetch) β Check β Test (stops early on failure)
- Classification:
- β passed: Baseline and offered both passed
- β regressed: Baseline passed, offered failed
- β broken: Baseline check/fetch failed (not your problem)
- β skipped: Version offered but not used by cargo
- End-of-run report separates "your fault" from "not your problem", with baseline failures categorized by root cause (yanked deps, system libs, build.rs, nightly, version conflicts, platform-specific)
- Uses
[patch.crates-io]in Cargo.toml - Respects semver requirements
- Cargo can ignore if version doesn't satisfy spec
- Directly modifies dependency in Cargo.toml
- Bypasses semver requirements
- Always tests the exact version specified
- Auto-adds patch mode test unless --skip-normal-testing
- Adds
[patch.crates-io]to dependent's Cargo.toml - Unifies ALL versions of your crate across the entire dependency tree
- Resolves "multiple versions of crate X" errors
- Use when dependents have transitive deps that also use your crate
Example: Testing rgb against image which depends on ravif which also uses rgb:
cargo-copter --dependents image --patch-transitiveWithout --patch-transitive, image might fail with "the trait AsPixels is not implemented"
because image uses one version of rgb while ravif uses another.
Cache location (platform-specific):
- Linux:
~/.cache/cargo-copter/staging/{crate}-{version}/ - macOS:
~/Library/Caches/cargo-copter/staging/{crate}-{version}/ - Windows:
%LOCALAPPDATA%/cargo-copter/staging/{crate}-{version}/
Contains:
- Unpacked sources
- Build artifacts (target/)
- 10x speedup on subsequent runs
Downloaded .crate files: ~/.cache/cargo-copter/crate-cache/ (or platform equivalent)
All reports are saved to ./copter-report/:
- Markdown:
report.md- optimized for LLM analysis - JSON:
report.json- structured data for CI/automation - Failure logs:
{dependent}-{version}_{base-version}.txt- full compiler output for each failure
Failure logs include the full path to the staged source code for easy navigation:
# Failure Log: image 0.25.9 with base crate version 0.8.52
# Generated: 2025-12-15 10:30:00
# Source: /home/user/.cache/cargo-copter/staging/image-0.25.9
=== CHECK (cargo check) ===
Status: FAILED (1.7s)
error[E0277]: the trait bound `Rgb<u8>: From<...>` is not satisfied
--> src/lib.rs:42:15
...
The copter-report/ directory is automatically added to .gitignore if one exists.
Offered column:
-= Baseline rowβ= Test passedβ= Test failedβ= Version skipped== Exact version matchβ= Upgraded to newer versionβ= Version mismatch[β β!]= Forced version
Resolved column:
π¦= Published from crates.ioπ= Local path
Result column:
βββ= Install + Check + Test passedβββ= Install + Check passed, Test failedββ-= Install passed, Check failed, Test skipped
# Build and test
cargo build --release
cargo test
# Run with debug logging
RUST_LOG=debug ./target/release/cargo-copter --top-dependents 1| State of the art codecs* | zenjpeg Β· zenpng Β· zenwebp Β· zengif Β· zenavif (rav1d-safe Β· zenrav1e Β· zenavif-parse Β· zenavif-serialize) Β· zenjxl (jxl-encoder Β· zenjxl-decoder) Β· zentiff Β· zenbitmaps Β· heic Β· zenraw Β· zenpdf Β· ultrahdr Β· mozjpeg-rs Β· webpx |
| Compression | zenflate Β· zenzop |
| Processing | zenresize Β· zenfilters Β· zenquant Β· zenblend |
| Metrics | zensim Β· fast-ssim2 Β· butteraugli Β· resamplescope-rs Β· codec-eval Β· codec-corpus |
| Pixel types & color | zenpixels Β· zenpixels-convert Β· linear-srgb Β· garb |
| Pipeline | zenpipe Β· zencodec Β· zencodecs Β· zenlayout Β· zennode |
| ImageResizer | ImageResizer (C#) β 24M+ NuGet downloads across all packages |
| Imageflow | Image optimization engine (Rust) β .NET Β· node Β· go β 9M+ NuGet downloads across all packages |
| Imageflow Server | The fast, safe image server (Rust+C#) β 552K+ NuGet downloads, deployed by Fortune 500s and major brands |
* as of 2026
archmage Β· magetypes Β· enough Β· whereat Β· zenbench Β· cargo-copter
And other projects Β· GitHub @imazen Β· GitHub @lilith Β· lib.rs/~lilith Β· NuGet (over 30 million downloads / 87 packages)
MIT/Apache-2.0 (standard Rust dual-license)
- GitHub: https://github.com/imazen/cargo-copter
- Rust API Evolution RFC: https://github.com/rust-lang/rfcs/blob/master/text/1105-api-evolution.md
- Inspired by: cargo-crusader
This was made with Claude Code and around 300 prompts to keep it on track. It wasn't a net savings in time vs. writing it myself, but at least I could do it from my phone. You can probably tell that it needs a lot of refactoring and improved test coverage. However, this is the kind of tool that doesn't need to be perfect, it just needs to be good enough to be useful. It's not a library.