Skip to main content

max / makenotwork

sando: run cargo_test over every crate, not just server/ The gate hardcoded worktree/server, so everything else in the repo shipped ungated. mnw-cli was the sharp edge: built as a companion and installed onto prod-1 in the same promote as the server, with no test ever run against it. multithreaded, pom, wam and all of shared/* were never touched by any gate either. Targets are now a configured [[test_target]] list: dir, optional features or all_features, and opt-in scratch_db. Omitting the key keeps the historical single-server behavior, so the sando code stays project-agnostic. scratch_db is per-target because exporting DATABASE_URL takes sqlx OUT of offline mode; a crate shipping .sqlx data would type-check against a database holding none of its tables. Only server and multithreaded ask for it. The set shares one gate_runs row and one log file, sectioned by per-target banners, and stops at the first red target with the crate named in the failure summary. gate_timeout_secs bounds the whole gate rather than each target, so N targets cannot multiply the ceiling by N. A target absent from the worktree is skipped so older shas still build; zero targets found fails the gate rather than passing over nothing. All fifteen crates verified green before being listed.
Author: Max Johnson <me@maxj.phd> · 2026-07-21 19:34 UTC
Commit: 2e450945ba9025712997f5cad75eeaa4102e0d72
Parent: 57d872e
7 files changed, +557 insertions, -376 deletions
M sando/README.md +41 -1
@@ -106,7 +106,7 @@ Promote-time gates run against a tier's deployed nodes or its operator.
106 106 | Kind | When | What it proves |
107 107 |------|------|----------------|
108 108 | `code_smoke` | build | Boots the fresh binary on a throwaway DB it migrates from scratch and seeds, then probes `/health`. Runs first: green here isolates a later red as an environment problem, not a code one. |
109 - | `cargo_test` | build | The server suite under `--features fast-tests`. |
109 + | `cargo_test` | build | Every configured `[[test_target]]` crate's suite, in order (see below). |
110 110 | `hardening_test` | build | What `cargo_test` structurally cannot reach (see below). |
111 111 | `migration_dry_run` | build | Migrations apply cleanly to a restored production dump. |
112 112 | `boot_smoke` | build | The staged artifact boots in minimal no-DB mode on the build host. |
@@ -114,6 +114,46 @@ Promote-time gates run against a tier's deployed nodes or its operator.
114 114 | `burn_in` | promote | The tier has held its current version for N hours. Evaluated live against the clock. |
115 115 | `manual_confirm` | promote | An operator signed off, at or after this version landed on the tier. |
116 116
117 + ### What `cargo_test` runs
118 +
119 + The gate used to be hardcoded to `worktree/server`, so every other crate in the
120 + repo shipped ungated. `mnw-cli` was the sharp edge: it is built as a companion
121 + and installed onto prod-1 in the same promote that ships the server, with no
122 + test ever run against it.
123 +
124 + Targets are now configured in the daemon config:
125 +
126 + ```toml
127 + [[test_target]]
128 + dir = "server"
129 + features = ["fast-tests"]
130 + scratch_db = true # export DATABASE_URL / TEST_DATABASE_URL
131 +
132 + [[test_target]]
133 + dir = "shared/tagtree" # no features, no DB
134 +
135 + [[test_target]]
136 + dir = "shared/ops-exec"
137 + all_features = true # mutually exclusive with `features`
138 + ```
139 +
140 + Omitting the key entirely keeps the historical behavior: one `server` target
141 + with `fast-tests`, against the scratch DB.
142 +
143 + Notes on the semantics:
144 +
145 + - `scratch_db` is opt-in per target. Setting `DATABASE_URL` takes sqlx **out**
146 + of offline mode, so a crate that ships `.sqlx` query data would try to
147 + type-check against a database holding none of its tables.
148 + - All targets share one `gate_runs` row and one log file, sectioned by
149 + `==== test_target: <dir> ====` banners. The gate stops at the first red
150 + target, and the failure summary names the crate.
151 + - `gate_timeout_secs` bounds the whole gate, not each target.
152 + - A target whose directory is absent from the worktree is skipped with a
153 + warning, so sando can still build older shas from a config describing the
154 + tip. If *no* target exists, the gate fails rather than reporting a pass over
155 + zero suites.
156 +
117 157 ### Why `hardening_test` exists
118 158
119 159 `cargo_test` builds with `--features fast-tests`, which relaxes
@@ -34,3 +34,68 @@ scratch_owner_role = "makenotwork"
34 34 name = "mnw-cli"
35 35 manifest_dir = "mnw-cli"
36 36 bin = "mnw-cli"
37 +
38 + # Crates the cargo_test gate runs, in order. This list used to be hardcoded to
39 + # `server`, so every other crate in the repo shipped ungated — including
40 + # mnw-cli, which is BUILT as a companion and installed onto prod-1 in the same
41 + # promote it ships. All of these were verified green on 2026-07-21 before being
42 + # added; the gate stops at the first red one.
43 + #
44 + # scratch_db exports DATABASE_URL/TEST_DATABASE_URL. Only the server needs it
45 + # (its sqlx macros type-check against a live DB). Leaving it off elsewhere keeps
46 + # crates that ship offline `.sqlx` data in offline mode.
47 + [[test_target]]
48 + dir = "server"
49 + features = ["fast-tests"]
50 + scratch_db = true
51 +
52 + # Companion: ships to prod-1, so it is gated first among the rest.
53 + [[test_target]]
54 + dir = "mnw-cli"
55 +
56 + [[test_target]]
57 + dir = "multithreaded"
58 + scratch_db = true
59 +
60 + [[test_target]]
61 + dir = "pom"
62 +
63 + [[test_target]]
64 + dir = "wam"
65 +
66 + # shared/* — every app and service in the repo links these.
67 + [[test_target]]
68 + dir = "shared/docengine"
69 + all_features = true
70 +
71 + # No tests of its own yet; listed so a compile break still fails the gate.
72 + [[test_target]]
73 + dir = "shared/egui-updater"
74 +
75 + [[test_target]]
76 + dir = "shared/kberg"
77 + all_features = true
78 +
79 + [[test_target]]
80 + dir = "shared/livechat"
81 + all_features = true
82 +
83 + [[test_target]]
84 + dir = "shared/ops-core"
85 +
86 + [[test_target]]
87 + dir = "shared/ops-exec"
88 + all_features = true
89 +
90 + [[test_target]]
91 + dir = "shared/pom-contract"
92 +
93 + [[test_target]]
94 + dir = "shared/s3-storage"
95 +
96 + [[test_target]]
97 + dir = "shared/synckit-client"
98 + all_features = true
99 +
100 + [[test_target]]
101 + dir = "shared/tagtree"
@@ -90,6 +90,44 @@ pub struct Config {
90 90 /// empty, so the sando code stays project-agnostic.
91 91 #[serde(default, rename = "companion")]
92 92 pub companions: Vec<Companion>,
93 + /// Crates the `cargo_test` gate runs, in order. The gate used to hardcode
94 + /// `worktree/server`, so everything else in the repo shipped ungated —
95 + /// including `mnw-cli`, which is built as a companion and installed onto
96 + /// prod-1. Default is the historical single `server` entry, so a project
97 + /// that configures nothing keeps today's behavior.
98 + #[serde(default = "default_test_targets", rename = "test_target")]
99 + pub test_targets: Vec<TestTarget>,
100 + }
101 +
102 + /// One crate's test suite, as run by the `cargo_test` gate.
103 + #[derive(Debug, Clone, Deserialize)]
104 + pub struct TestTarget {
105 + /// Directory under the worktree holding the crate's `Cargo.toml`
106 + /// (e.g. `server`, `shared/tagtree`).
107 + pub dir: PathBuf,
108 + /// Cargo features to enable. MNW's server needs `fast-tests`; most crates
109 + /// need none.
110 + #[serde(default)]
111 + pub features: Vec<String>,
112 + /// Pass `--all-features` instead of naming features. Mutually exclusive
113 + /// with `features` (rejected at load).
114 + #[serde(default)]
115 + pub all_features: bool,
116 + /// Export `DATABASE_URL` / `TEST_DATABASE_URL` (pointing at
117 + /// `scratch_db_url`) for this crate's tests. Off by default: a crate using
118 + /// sqlx's offline query data goes *online* when `DATABASE_URL` is set and
119 + /// will fail to compile against the wrong database.
120 + #[serde(default)]
121 + pub scratch_db: bool,
122 + }
123 +
124 + fn default_test_targets() -> Vec<TestTarget> {
125 + vec![TestTarget {
126 + dir: PathBuf::from("server"),
127 + features: vec!["fast-tests".into()],
128 + all_features: false,
129 + scratch_db: true,
130 + }]
93 131 }
94 132
95 133 /// A crate built alongside the server and staged into the release bundle under
@@ -157,6 +195,18 @@ impl Config {
157 195 interpolated into DDL as a bare identifier",
158 196 self.scratch_owner_role,
159 197 );
198 + anyhow::ensure!(
199 + !self.test_targets.is_empty(),
200 + "test_target list is empty; cargo_test would run nothing and pass. Omit the \
201 + key entirely to get the default `server` target.",
202 + );
203 + for t in &self.test_targets {
204 + anyhow::ensure!(
205 + !t.all_features || t.features.is_empty(),
206 + "test_target {:?} sets both all_features and features; pick one",
207 + t.dir,
208 + );
209 + }
160 210 Ok(())
161 211 }
162 212
@@ -179,6 +229,7 @@ impl Config {
179 229 cargo_target_dir: None,
180 230 gate_timeout_secs: default_gate_timeout_secs(),
181 231 companions: Vec::new(),
232 + test_targets: default_test_targets(),
182 233 }
183 234 }
184 235 }
@@ -197,6 +248,81 @@ mod tests {
197 248 "#;
198 249
199 250 #[test]
251 + fn test_targets_default_to_the_historical_server_entry() {
252 + // A project that configures nothing must keep the pre-config behavior:
253 + // the server crate, with fast-tests, against the scratch DB.
254 + let cfg: Config = toml::from_str(MINIMAL).unwrap();
255 + assert_eq!(cfg.test_targets.len(), 1);
256 + let t = &cfg.test_targets[0];
257 + assert_eq!(t.dir, PathBuf::from("server"));
258 + assert_eq!(t.features, ["fast-tests"]);
259 + assert!(t.scratch_db);
260 + assert!(!t.all_features);
261 + }
262 +
263 + #[test]
264 + fn test_targets_parse_as_a_list() {
265 + let raw = format!(
266 + "{MINIMAL}\nscratch_db_url = \"postgres:///x\"\n\
267 + [[test_target]]\ndir = \"server\"\nfeatures = [\"fast-tests\"]\nscratch_db = true\n\
268 + [[test_target]]\ndir = \"shared/tagtree\"\n\
269 + [[test_target]]\ndir = \"shared/ops-exec\"\nall_features = true\n"
270 + );
271 + let cfg: Config = toml::from_str(&raw).unwrap();
272 + cfg.validate().unwrap();
273 + let dirs: Vec<_> = cfg.test_targets.iter().map(|t| t.dir.display().to_string()).collect();
274 + assert_eq!(dirs, ["server", "shared/tagtree", "shared/ops-exec"]);
275 + // Defaults for an entry that names only a dir: no features, no DB.
276 + assert!(cfg.test_targets[1].features.is_empty());
277 + assert!(!cfg.test_targets[1].scratch_db);
278 + assert!(cfg.test_targets[2].all_features);
279 + }
280 +
281 + #[test]
282 + fn validate_rejects_an_empty_test_target_list() {
283 + // An explicit empty list would make cargo_test green having run nothing.
284 + let raw = format!("{MINIMAL}\ntest_target = []\n");
285 + let cfg: Config = toml::from_str(&raw).unwrap();
286 + let err = cfg.validate().unwrap_err().to_string();
287 + assert!(err.contains("test_target list is empty"), "got: {err}");
288 + }
289 +
290 + #[test]
291 + fn validate_rejects_all_features_together_with_features() {
292 + let raw = format!(
293 + "{MINIMAL}\n[[test_target]]\ndir = \"x\"\nall_features = true\nfeatures = [\"y\"]\n"
294 + );
295 + let err = toml::from_str::<Config>(&raw).unwrap().validate().unwrap_err().to_string();
296 + assert!(err.contains("pick one"), "got: {err}");
297 + }
298 +
299 + #[test]
300 + fn scratch_db_without_a_url_is_not_a_config_error() {
301 + // `scratch_db` means "export the scratch URL if there is one", not
302 + // "require one" — the pre-config gate simply skipped the env when
303 + // scratch_db_url was unset, and a project with no postgres at all must
304 + // still boot.
305 + let raw = format!("{MINIMAL}\n[[test_target]]\ndir = \"server\"\nscratch_db = true\n");
306 + toml::from_str::<Config>(&raw).unwrap().validate().unwrap();
307 + }
308 +
309 + #[test]
310 + fn shipped_daemon_config_parses_and_validates() {
311 + // The real sando-daemon.toml next to this crate: catches a typo in the
312 + // test_target list before it wedges a build on the host.
313 + let raw = std::fs::read_to_string(
314 + std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("sando-daemon.toml"),
315 + )
316 + .expect("sando-daemon.toml ships with the crate");
317 + let cfg: Config = toml::from_str(&raw).expect("shipped config parses");
318 + cfg.validate().expect("shipped config validates");
319 + assert!(
320 + cfg.test_targets.iter().any(|t| t.dir == std::path::Path::new("mnw-cli")),
321 + "the companion that installs onto prod-1 must be gated",
322 + );
323 + }
324 +
325 + #[test]
200 326 fn cargo_target_dir_parses_when_present() {
201 327 let raw = format!("{MINIMAL}\ncargo_target_dir = \"/srv/sando/cargo-target\"\n");
202 328 let cfg: Config = toml::from_str(&raw).unwrap();
@@ -173,8 +173,21 @@ pub async fn run_all(ctx: &GateCtx, gates: &[Gate]) -> Result<Vec<GateKind>> {
173 173
174 174 // ---- individual gate runners ----
175 175
176 + /// Run every configured `test_target`'s suite, in order, under one gate.
177 + ///
178 + /// This used to be hardcoded to `worktree/server`, which meant every other
179 + /// crate in the repo shipped ungated — including `mnw-cli`, which is built as a
180 + /// companion and installed onto prod-1 in the same promote. The targets are now
181 + /// configured (`[[test_target]]` in the daemon config), defaulting to the
182 + /// historical single `server` entry.
183 + ///
184 + /// The whole set shares one `gate_runs` row and one log file: from the
185 + /// pipeline's point of view "the tests" either pass or don't. The first failing
186 + /// target ends the gate, since a red suite blocks the promote regardless of what
187 + /// the remaining crates would have said, and running them would only delay the
188 + /// operator's answer. Its name is carried in the failure so the summary points
189 + /// at the crate, not just the test.
176 190 async fn cargo_test(ctx: &GateCtx, run_id: GateRunId) -> Result<GateOutcome> {
177 - let server_dir = ctx.worktree.join("server");
178 191 let log_path = gate_log_path(ctx, GateKind::CargoTest);
179 192 let log_ref = LogRef::new(&ctx.version, GateKind::CargoTest);
180 193
@@ -188,63 +201,156 @@ async fn cargo_test(ctx: &GateCtx, run_id: GateRunId) -> Result<GateOutcome> {
188 201 }
189 202
190 203 let started = std::time::Instant::now();
204 + // One ceiling for the whole gate, not per target: the point is to bound how
205 + // long a hung suite can block the pipeline, and N targets each allowed the
206 + // full timeout would multiply that by N.
207 + let deadline = started + std::time::Duration::from_secs(ctx.cfg.gate_timeout_secs);
208 + let mut ran = 0usize;
209 +
210 + for target in &ctx.cfg.test_targets {
211 + let dir = ctx.worktree.join(&target.dir);
212 + // A target absent from this sha is skipped, not fatal: sando has to be
213 + // able to build older shas (bisect, rollback rebuild) from a config that
214 + // describes the tip. The zero-targets-ran check below is what stops this
215 + // from quietly turning the gate into a no-op.
216 + if !dir.join("Cargo.toml").is_file() {
217 + tracing::warn!(
218 + target = %target.dir.display(), version = %ctx.version,
219 + "test_target has no Cargo.toml in this worktree; skipping",
220 + );
221 + continue;
222 + }
223 + let features: Vec<&str> = target.features.iter().map(String::as_str).collect();
224 +
225 + // Fast pre-gate: compile the test targets WITHOUT running them. This
226 + // builds the exact artifacts the full run needs (so the subsequent run
227 + // reuses the cache — no wasted work), but fails in ~minutes with the
228 + // real `error[Ennnn]: ...` on a test-only-target compile break. That
229 + // class (a field missing in a `#[cfg(test)]`-only binary like `load`)
230 + // otherwise compiles fine under the build step and only blows up here,
231 + // after a full build, as an opaque mass test failure.
232 + let banner = format!("\n==== test_target: {} ====\n", target.dir.display());
233 + append_to_log(&log_path, banner.as_bytes()).await;
234 + let mut pre = match cargo_test_command(ctx, &dir, target, &features, &["--no-run"]).spawn() {
235 + Ok(c) => c,
236 + Err(e) => {
237 + return Ok(GateOutcome::failed(GateFailure::SpawnFailed {
238 + message: format!("{}: {e}", target.dir.display()),
239 + }).with_log_ref(log_ref));
240 + }
241 + };
242 + let (pre_out, pre_err, pre_status) =
243 + match run_to_deadline(&mut pre, ctx, run_id, log_path.clone(), deadline, started).await? {
244 + Ok(v) => v,
245 + Err(timeout) => return Ok(timeout.with_log_ref(log_ref)),
246 + };
247 + if !pre_status.success() {
248 + let failure = classify::classify_compile_error(&pre_out, &pre_err);
249 + return Ok(GateOutcome::failed(name_target(failure, &target.dir)).with_log_ref(log_ref));
250 + }
191 251
192 - // Fast pre-gate: compile the test targets WITHOUT running them. This
193 - // builds the exact `--release --features fast-tests` artifacts the full
194 - // run needs (so the subsequent run reuses the cache — no wasted work),
195 - // but fails in ~minutes with the real `error[Ennnn]: ...` on a
196 - // test-only-target compile break. That class (a field missing in a
197 - // `#[cfg(test)]`-only binary like `load`) otherwise compiles fine under
198 - // the build step + `--test integration` and only blows up here, after a
199 - // full build, as an opaque mass test failure.
200 - let mut pre = match cargo_test_command(ctx, &server_dir, &["fast-tests"], &["--no-run"]).spawn() {
201 - Ok(c) => c,
202 - Err(e) => {
203 - return Ok(GateOutcome::failed(GateFailure::SpawnFailed {
204 - message: e.to_string(),
205 - }).with_log_ref(log_ref));
252 + // Full run: the test binaries are already built above, so cargo's
253 + // up-to-date check skips compilation and this just runs the tests.
254 + let mut child = match cargo_test_command(ctx, &dir, target, &features, &[]).spawn() {
255 + Ok(c) => c,
256 + Err(e) => {
257 + return Ok(GateOutcome::failed(GateFailure::SpawnFailed {
258 + message: format!("{}: {e}", target.dir.display()),
259 + }).with_log_ref(log_ref));
260 + }
261 + };
262 + let (stdout_buf, stderr_buf, status) =
263 + match run_to_deadline(&mut child, ctx, run_id, log_path.clone(), deadline, started).await? {
264 + Ok(v) => v,
265 + Err(timeout) => return Ok(timeout.with_log_ref(log_ref)),
266 + };
267 + if !status.success() {
268 + let failure = classify::classify_cargo_test(&stdout_buf, &stderr_buf);
269 + return Ok(GateOutcome::failed(name_target(failure, &target.dir)).with_log_ref(log_ref));
206 270 }
207 - };
208 - let (pre_out, pre_err, pre_status) =
209 - stream_child_to_live_log(&mut pre, ctx.events.clone(), run_id, log_path.clone()).await?;
210 - if !pre_status.success() {
211 - let failure = classify::classify_compile_error(&pre_out, &pre_err);
212 - return Ok(GateOutcome::failed(failure).with_log_ref(log_ref));
271 + ran += 1;
272 + }
273 +
274 + // Every configured target was missing from the worktree. Exiting green here
275 + // would report "tests passed" having run none of them.
276 + if ran == 0 {
277 + return Ok(GateOutcome::failed(GateFailure::Unclassified {
278 + legacy_detail: Some(format!(
279 + "cargo_test ran no targets: none of the {} configured test_target dir(s) \
280 + exist in this worktree",
281 + ctx.cfg.test_targets.len(),
282 + )),
283 + })
284 + .with_log_ref(log_ref));
213 285 }
214 286
215 - // Full run: the test binaries are already built above, so cargo's
216 - // up-to-date check skips compilation and this just runs the tests.
217 - let mut child = match cargo_test_command(ctx, &server_dir, &["fast-tests"], &[]).spawn() {
218 - Ok(c) => c,
219 - Err(e) => {
220 - return Ok(GateOutcome::failed(GateFailure::SpawnFailed {
221 - message: e.to_string(),
222 - }).with_log_ref(log_ref));
223 - }
224 - };
225 - // Wall-clock ceiling: a hung suite would otherwise block the pipeline until a
226 - // new /rebuild aborts it. Past the ceiling, kill the child and fail Timeout.
227 - let ceiling = std::time::Duration::from_secs(ctx.cfg.gate_timeout_secs);
228 - let stream = stream_child_to_live_log(&mut child, ctx.events.clone(), run_id, log_path);
229 - let (stdout_buf, stderr_buf, status) = match tokio::time::timeout(ceiling, stream).await {
230 - Ok(res) => res?,
287 + let duration_s = started.elapsed().as_secs() as u32;
288 + Ok(GateOutcome::passed(PassNote::TestsPassed { duration_s }).with_log_ref(log_ref))
289 + }
290 +
291 + /// Stream a child to the live log, bounded by the gate-wide `deadline`. `Ok(Err(_))`
292 + /// is the timeout outcome (child killed); `Err(_)` is an IO error on the stream.
293 + #[allow(clippy::type_complexity)]
294 + async fn run_to_deadline(
295 + child: &mut tokio::process::Child,
296 + ctx: &GateCtx,
297 + run_id: GateRunId,
298 + log_path: PathBuf,
299 + deadline: std::time::Instant,
300 + started: std::time::Instant,
301 + ) -> Result<std::result::Result<(Vec<u8>, Vec<u8>, std::process::ExitStatus), GateOutcome>> {
302 + let remaining = deadline.saturating_duration_since(std::time::Instant::now());
303 + let stream = stream_child_to_live_log(child, ctx.events.clone(), run_id, log_path);
304 + match tokio::time::timeout(remaining, stream).await {
305 + Ok(res) => Ok(Ok(res?)),
231 306 Err(_elapsed) => {
232 307 child.start_kill().ok();
233 308 let _ = child.wait().await;
234 - let after_s = started.elapsed().as_secs() as u32;
235 - return Ok(GateOutcome::failed(GateFailure::Timeout {
309 + Ok(Err(GateOutcome::failed(GateFailure::Timeout {
236 310 gate: GateKind::CargoTest,
237 - after_s,
238 - })
239 - .with_log_ref(log_ref));
311 + after_s: started.elapsed().as_secs() as u32,
312 + })))
240 313 }
241 - };
242 - let duration_s = started.elapsed().as_secs() as u32;
243 - if status.success() {
244 - Ok(GateOutcome::passed(PassNote::TestsPassed { duration_s }).with_log_ref(log_ref))
245 - } else {
246 - let failure = classify::classify_cargo_test(&stdout_buf, &stderr_buf);
247 - Ok(GateOutcome::failed(failure).with_log_ref(log_ref))
314 + }
315 + }
316 +
317 + /// Prefix a test/compile failure's headline with the crate it came from, so a
318 + /// red gate across many targets says *which* crate broke. Other failure kinds
319 + /// are single-target by construction and pass through untouched.
320 + fn name_target(failure: GateFailure, dir: &std::path::Path) -> GateFailure {
321 + let at = dir.display();
322 + match failure {
323 + GateFailure::CargoTest { failed_count, first_failed, first_panic } => GateFailure::CargoTest {
324 + failed_count,
325 + first_failed: Some(match first_failed {
326 + Some(name) => format!("{at}: {name}"),
327 + None => at.to_string(),
328 + }),
329 + first_panic,
330 + },
331 + GateFailure::CompileError { error_count, first_error } => GateFailure::CompileError {
332 + error_count,
333 + first_error: Some(match first_error {
334 + Some(e) => format!("{at}: {e}"),
335 + None => at.to_string(),
336 + }),
337 + },
338 + other => other,
339 + }
340 + }
341 +
342 + /// Append raw bytes to a gate log outside the child-streaming path (target
343 + /// banners). Best-effort, same as `LiveLog`: a broken log dir never turns a
344 + /// passing gate red.
345 + async fn append_to_log(path: &std::path::Path, bytes: &[u8]) {
346 + use tokio::io::AsyncWriteExt;
347 + if let Some(parent) = path.parent()
348 + && tokio::fs::create_dir_all(parent).await.is_err()
349 + {
350 + return;
351 + }
352 + if let Ok(mut f) = tokio::fs::OpenOptions::new().create(true).append(true).open(path).await {
353 + let _ = f.write_all(bytes).await;
248 354 }
249 355 }
250 356
@@ -269,6 +375,16 @@ async fn cargo_test(ctx: &GateCtx, run_id: GateRunId) -> Result<GateOutcome> {
269 375 /// That is the price of the coverage; the filter keeps the *run* to seconds.
270 376 async fn hardening_test(ctx: &GateCtx, run_id: GateRunId) -> Result<GateOutcome> {
271 377 let server_dir = ctx.worktree.join("server");
378 + // No features is the whole point; the scratch DB is needed because the
379 + // server's sqlx macros type-check against it. Unlike cargo_test, this gate
380 + // is deliberately not driven by `test_targets`: it targets one specific
381 + // suite in one specific crate, not "the repo's tests".
382 + let target = crate::config::TestTarget {
383 + dir: std::path::PathBuf::from("server"),
384 + features: Vec::new(),
385 + all_features: false,
386 + scratch_db: true,
387 + };
272 388 let log_path = gate_log_path(ctx, GateKind::HardeningTest);
273 389 let log_ref = LogRef::new(&ctx.version, GateKind::HardeningTest);
274 390
@@ -280,7 +396,7 @@ async fn hardening_test(ctx: &GateCtx, run_id: GateRunId) -> Result<GateOutcome>
280 396
281 397 // Same two-step shape as cargo_test: compile first so a test-target break
282 398 // reports as a compile error rather than an opaque mass test failure.
283 - let mut pre = match cargo_test_command(ctx, &server_dir, &[], &["--no-run", "--test", "integration"]).spawn() {
399 + let mut pre = match cargo_test_command(ctx, &server_dir, &target, &[], &["--no-run", "--test", "integration"]).spawn() {
284 400 Ok(c) => c,
285 401 Err(e) => {
286 402 return Ok(GateOutcome::failed(GateFailure::SpawnFailed {
@@ -298,6 +414,7 @@ async fn hardening_test(ctx: &GateCtx, run_id: GateRunId) -> Result<GateOutcome>
298 414 let mut child = match cargo_test_command(
299 415 ctx,
300 416 &server_dir,
417 + &target,
301 418 &[],
302 419 &["--test", "integration", "--", "--test-threads=1", "rate_limit"],
303 420 )
@@ -371,12 +488,15 @@ fn tests_run(stdout: &[u8]) -> u32 {
371 488 fn cargo_test_command(
372 489 ctx: &GateCtx,
373 490 dir: &std::path::Path,
491 + target: &crate::config::TestTarget,
374 492 features: &[&str],
375 493 extra: &[&str],
376 494 ) -> Command {
377 495 let mut cmd = Command::new("cargo");
378 496 cmd.args(["test", "--release"]);
379 - if !features.is_empty() {
497 + if target.all_features {
498 + cmd.arg("--all-features");
499 + } else if !features.is_empty() {
380 500 cmd.args(["--features", &features.join(",")]);
381 501 }
382 502 cmd.args(extra)
@@ -393,7 +513,11 @@ fn cargo_test_command(
393 513 // Same online-mode rationale as the build step: sqlx query macros need a
394 514 // live DB to type-check against. The scratch DB is left in migrated state
395 515 // by the preceding build, so we can reuse it here.
396 - if let Some(scratch_url) = ctx.cfg.scratch_db_url.as_deref() {
516 + //
517 + // Opt-in per target: setting DATABASE_URL switches sqlx OUT of offline mode,
518 + // so a crate that ships `.sqlx` query data would stop using it and try to
519 + // type-check against a database that has none of its tables.
520 + if let Some(scratch_url) = ctx.cfg.scratch_db_url.as_deref().filter(|_| target.scratch_db) {
397 521 cmd.env("DATABASE_URL", scratch_url);
398 522 // The server test harness (tests/harness/db.rs) parses TEST_DATABASE_URL
399 523 // with rfind('/'), which mangles URLs whose query string contains '/'
@@ -1452,6 +1576,132 @@ mod tests {
1452 1576 use crate::events;
1453 1577 use sqlx::sqlite::SqlitePoolOptions;
1454 1578
1579 + fn target(dir: &str) -> crate::config::TestTarget {
1580 + crate::config::TestTarget {
1581 + dir: std::path::PathBuf::from(dir),
1582 + features: Vec::new(),
1583 + all_features: false,
1584 + scratch_db: false,
1585 + }
1586 + }
1587 +
1588 + #[test]
1589 + fn name_target_points_a_test_failure_at_its_crate() {
1590 + // With one target the crate was implicit; with fifteen the operator
1591 + // needs the summary to say which one broke.
1592 + let f = name_target(
1593 + GateFailure::CargoTest {
1594 + failed_count: 3,
1595 + first_failed: Some("workflows::sync::round_trip".into()),
1596 + first_panic: None,
1597 + },
1598 + std::path::Path::new("shared/synckit-client"),
1599 + );
1600 + assert_eq!(
1601 + f.summary(),
1602 + "3 test(s) failed; first: shared/synckit-client: workflows::sync::round_trip",
1603 + );
1604 + }
1605 +
1606 + #[test]
1607 + fn name_target_points_a_compile_failure_at_its_crate() {
1608 + let f = name_target(
1609 + GateFailure::CompileError { error_count: 1, first_error: Some("error[E0063]".into()) },
1610 + std::path::Path::new("mnw-cli"),
1611 + );
1612 + assert_eq!(f.summary(), "compile failed (1 error(s)); first: mnw-cli: error[E0063]");
1613 + }
1614 +
1615 + #[test]
1616 + fn name_target_names_the_crate_even_without_a_test_name() {
1617 + let f = name_target(
1618 + GateFailure::CargoTest { failed_count: 2, first_failed: None, first_panic: None },
1619 + std::path::Path::new("pom"),
1620 + );
1621 + assert_eq!(f.summary(), "2 test(s) failed; first: pom");
1622 + }
1623 +
1624 + #[test]
1625 + fn name_target_leaves_unrelated_failures_alone() {
1626 + let f = name_target(
1627 + GateFailure::SpawnFailed { message: "no cargo".into() },
1628 + std::path::Path::new("pom"),
1629 + );
1630 + assert!(matches!(f, GateFailure::SpawnFailed { .. }));
1631 + }
1632 +
1633 + #[tokio::test]
1634 + async fn cargo_test_fails_closed_when_no_target_exists_in_the_worktree() {
1635 + // A worktree missing every configured crate must not report "tests
1636 + // passed" having run none. Uses an empty tempdir as the worktree, so
1637 + // no cargo process is ever spawned.
1638 + let tmp = tempfile::tempdir().unwrap();
1639 + let mut cfg = crate::config::Config::for_tests();
1640 + cfg.test_targets = vec![target("server"), target("mnw-cli")];
1641 + cfg.logs_root = tmp.path().join("logs");
1642 + let pool = SqlitePoolOptions::new().max_connections(1).connect("sqlite::memory:").await.unwrap();
1643 + let ctx = GateCtx {
1644 + pool,
1645 + cfg: std::sync::Arc::new(cfg),
1646 + tier: TierId::new("host"),
1647 + version: "0.1.0".parse().unwrap(),
1648 + worktree: tmp.path().to_path_buf(),
1649 + events: events::channel(),
1650 + nodes: Vec::new(),
1651 + };
1652 + let out = cargo_test(&ctx, GateRunId(1)).await.unwrap();
1653 + assert_eq!(out.status_str(), "failed", "green here would be a silent no-op gate");
1654 + let crate::outcome::GateStatus::Failed { failure } = &out.status else { panic!("expected a failure") };
1655 + assert!(failure.summary().contains("ran no targets"), "got: {}", failure.summary());
1656 + }
1657 +
1658 + #[tokio::test]
1659 + async fn scratch_db_env_is_opt_in_per_target() {
1660 + // Exporting DATABASE_URL knocks sqlx out of offline mode, so a crate
1661 + // shipping .sqlx data must not see it.
1662 + let mut cfg = crate::config::Config::for_tests();
1663 + cfg.scratch_db_url = Some("postgres://sando@127.0.0.1/sando_scratch".into());
1664 + let ctx = GateCtx {
1665 + pool: SqlitePoolOptions::new().max_connections(1).connect_lazy("sqlite::memory:").unwrap(),
1666 + cfg: std::sync::Arc::new(cfg),
1667 + tier: TierId::new("host"),
1668 + version: "0.1.0".parse().unwrap(),
1669 + worktree: std::path::PathBuf::from("/tmp/wt"),
1670 + events: events::channel(),
1671 + nodes: Vec::new(),
1672 + };
1673 + let dir = std::path::Path::new("/tmp/wt/x");
1674 +
1675 + let off = cargo_test_command(&ctx, dir, &target("x"), &[], &[]);
1676 + let has_db = |c: &Command| {
1677 + c.as_std().get_envs().any(|(k, v)| k == "DATABASE_URL" && v.is_some())
1678 + };
1679 + assert!(!has_db(&off), "scratch_db defaults off");
1680 +
1681 + let mut on_target = target("x");
1682 + on_target.scratch_db = true;
1683 + assert!(has_db(&cargo_test_command(&ctx, dir, &on_target, &[], &[])), "opt-in exports it");
1684 + }
1685 +
1686 + #[tokio::test]
1687 + async fn all_features_replaces_the_feature_list() {
1688 + let ctx = GateCtx {
1689 + pool: SqlitePoolOptions::new().max_connections(1).connect_lazy("sqlite::memory:").unwrap(),
1690 + cfg: std::sync::Arc::new(crate::config::Config::for_tests()),
1691 + tier: TierId::new("host"),
1692 + version: "0.1.0".parse().unwrap(),
1693 + worktree: std::path::PathBuf::from("/tmp/wt"),
1694 + events: events::channel(),
1695 + nodes: Vec::new(),
1696 + };
1697 + let mut t = target("shared/ops-exec");
1698 + t.all_features = true;
1699 + let cmd = cargo_test_command(&ctx, std::path::Path::new("/tmp/wt"), &t, &[], &[]);
1700 + let args: Vec<_> = cmd.as_std().get_args().map(|a| a.to_string_lossy().into_owned()).collect();
1701 + assert!(args.iter().any(|a| a == "--all-features"), "got: {args:?}");
1702 + assert!(!args.iter().any(|a| a == "--features"), "got: {args:?}");
1703 + }
1704 +
1455 1705 #[test]
1456 1706 fn tests_run_reads_the_libtest_summary() {
1457 1707 let out = b"running 6 tests\ntest auth_rate_limit_triggers_on_burst ... ok\n\n\
@@ -1492,12 +1742,20 @@ mod tests {
1492 1742 events: events::channel(),
1493 1743 nodes: Vec::new(),
1494 1744 };
1495 - let cmd = cargo_test_command(&ctx, std::path::Path::new("/tmp/wt/server"), &[], &["--test", "integration"]);
1745 + let plain = crate::config::TestTarget {
1746 + dir: std::path::PathBuf::from("server"), features: Vec::new(),
1747 + all_features: false, scratch_db: true,
1748 + };
1749 + let cmd = cargo_test_command(&ctx, std::path::Path::new("/tmp/wt/server"), &plain, &[], &["--test", "integration"]);
1496 1750 let args: Vec<_> = cmd.as_std().get_args().map(|a| a.to_string_lossy().into_owned()).collect();
1497 1751 assert!(!args.iter().any(|a| a == "--features"), "hardening_test must pass no features: {args:?}");
1498 1752 assert!(!args.iter().any(|a| a.contains("fast-tests")), "got: {args:?}");
1499 1753
1500 - let fast = cargo_test_command(&ctx, std::path::Path::new("/tmp/wt/server"), &["fast-tests"], &[]);
1754 + let fast_target = crate::config::TestTarget {
1755 + dir: std::path::PathBuf::from("server"), features: vec!["fast-tests".into()],
1756 + all_features: false, scratch_db: true,
1757 + };
1758 + let fast = cargo_test_command(&ctx, std::path::Path::new("/tmp/wt/server"), &fast_target, &["fast-tests"], &[]);
1501 1759 let fast_args: Vec<_> = fast.as_std().get_args().map(|a| a.to_string_lossy().into_owned()).collect();
1502 1760 assert!(fast_args.windows(2).any(|w| w == ["--features", "fast-tests"]), "got: {fast_args:?}");
1503 1761 }
@@ -807,6 +807,12 @@ mod tests {
807 807 cargo_target_dir: None,
808 808 gate_timeout_secs: 2400,
809 809 companions: Vec::new(),
810 + test_targets: vec![crate::config::TestTarget {
811 + dir: PathBuf::from("server"),
812 + features: vec!["fast-tests".into()],
813 + all_features: false,
814 + scratch_db: true,
815 + }],
810 816 }
811 817 }
812 818
@@ -0,0 +1,7 @@
1 + # This file is automatically @generated by Cargo.
2 + # It is not intended for manual editing.
3 + version = 4
4 +
5 + [[package]]
6 + name = "egui-updater"
7 + version = "0.0.0"
@@ -224,29 +224,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
224 224 checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
225 225
226 226 [[package]]
227 - name = "aws-lc-rs"
228 - version = "1.17.3"
229 - source = "registry+https://github.com/rust-lang/crates.io-index"
230 - checksum = "00bdb5da18dac48ca2cc7cd4a98e533e8635a58e2361d13a1a4ee3888e0d72f1"
231 - dependencies = [
232 - "aws-lc-sys",
233 - "zeroize",
234 - ]
235 -
236 - [[package]]
237 - name = "aws-lc-sys"
238 - version = "0.43.0"
239 - source = "registry+https://github.com/rust-lang/crates.io-index"
240 - checksum = "43103168cc76fe62678a375e722fc9cb3a0146159ac5828bc4f0dfd755c2224c"
241 - dependencies = [
242 - "cc",
243 - "cmake",
244 - "dunce",
245 - "fs_extra",
246 - "pkg-config",
247 - ]
248 -
249 - [[package]]
250 227 name = "base64"
251 228 version = "0.22.1"
252 229 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -338,8 +315,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
338 315 checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2"
339 316 dependencies = [
340 317 "find-msvc-tools",
341 - "jobserver",
342 - "libc",
343 318 "shlex",
344 319 ]
345 320
@@ -350,12 +325,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
350 325 checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
351 326
352 327 [[package]]
353 - name = "cfg_aliases"
354 - version = "0.2.2"
355 - source = "registry+https://github.com/rust-lang/crates.io-index"
356 - checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527"
357 -
358 - [[package]]
359 328 name = "chacha20"
360 329 version = "0.9.1"
361 330 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -416,25 +385,6 @@ dependencies = [
416 385 ]
417 386
418 387 [[package]]
419 - name = "cmake"
420 - version = "0.1.58"
421 - source = "registry+https://github.com/rust-lang/crates.io-index"
422 - checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678"
423 - dependencies = [
424 - "cc",
425 - ]
426 -
427 - [[package]]
428 - name = "combine"
429 - version = "4.6.7"
430 - source = "registry+https://github.com/rust-lang/crates.io-index"
431 - checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd"
432 - dependencies = [
433 - "bytes",
434 - "memchr",
435 - ]
436 -
437 - [[package]]
438 388 name = "concurrent-queue"
439 389 version = "2.5.0"
440 390 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -545,12 +495,6 @@ dependencies = [
545 495 ]
546 496
547 497 [[package]]
548 - name = "dunce"
549 - version = "1.0.5"
550 - source = "registry+https://github.com/rust-lang/crates.io-index"
551 - checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813"
552 -
553 - [[package]]
554 498 name = "encoding_rs"
555 499 version = "0.8.35"
556 500 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -690,12 +634,6 @@ dependencies = [
690 634 ]
691 635
692 636 [[package]]
693 - name = "fs_extra"
694 - version = "1.3.0"
695 - source = "registry+https://github.com/rust-lang/crates.io-index"
696 - checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"
697 -
698 - [[package]]
699 637 name = "futures"
700 638 version = "0.3.32"
701 639 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -813,10 +751,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
813 751 checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
814 752 dependencies = [
815 753 "cfg-if",
816 - "js-sys",
817 754 "libc",
818 755 "wasi",
819 - "wasm-bindgen",
820 756 ]
821 757
822 758 [[package]]
@@ -826,13 +762,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
826 762 checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec"
827 763 dependencies = [
828 764 "cfg-if",
829 - "js-sys",
830 765 "libc",
831 766 "r-efi",
832 767 "rand_core 0.10.1",
833 768 "wasip2",
834 769 "wasip3",
835 - "wasm-bindgen",
836 770 ]
837 771
838 772 [[package]]
@@ -1219,65 +1153,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
1219 1153 checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
1220 1154
1221 1155 [[package]]
1222 - name = "jni"
1223 - version = "0.22.4"
1224 - source = "registry+https://github.com/rust-lang/crates.io-index"
1225 - checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498"
1226 - dependencies = [
1227 - "cfg-if",
1228 - "combine",
1229 - "jni-macros",
1230 - "jni-sys",
1231 - "log",
1232 - "simd_cesu8",
1233 - "thiserror",
1234 - "walkdir",
1235 - "windows-link",
1236 - ]
1237 -
1238 - [[package]]
1239 - name = "jni-macros"
1240 - version = "0.22.4"
1241 - source = "registry+https://github.com/rust-lang/crates.io-index"
1242 - checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3"
1243 - dependencies = [
1244 - "proc-macro2",
1245 - "quote",
1246 - "rustc_version",
1247 - "simd_cesu8",
1248 - "syn 2.0.117",
1249 - ]
1250 -
1251 - [[package]]
1252 - name = "jni-sys"
1253 - version = "0.4.1"
1254 - source = "registry+https://github.com/rust-lang/crates.io-index"
1255 - checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2"
1256 - dependencies = [
1257 - "jni-sys-macros",
1258 - ]
1259 -
1260 - [[package]]
1261 - name = "jni-sys-macros"
1262 - version = "0.4.1"
1263 - source = "registry+https://github.com/rust-lang/crates.io-index"
1264 - checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264"
1265 - dependencies = [
1266 - "quote",
1267 - "syn 2.0.117",
1268 - ]
1269 -
1270 - [[package]]
1271 - name = "jobserver"
1272 - version = "0.1.35"
1273 - source = "registry+https://github.com/rust-lang/crates.io-index"
1274 - checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3"
1275 - dependencies = [
1276 - "getrandom 0.4.1",
1277 - "libc",
1278 - ]
1279 -
1280 - [[package]]
1281 1156 name = "js-sys"
1282 1157 version = "0.3.90"
1283 1158 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1365,12 +1240,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
1365 1240 checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
1366 1241
1367 1242 [[package]]
1368 - name = "lru-slab"
1369 - version = "0.1.2"
1370 - source = "registry+https://github.com/rust-lang/crates.io-index"
1371 - checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154"
1372 -
1373 - [[package]]
1374 1243 name = "memchr"
1375 1244 version = "2.8.0"
1376 1245 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1705,63 +1574,6 @@ dependencies = [
1705 1574 ]
1706 1575
1707 1576 [[package]]
1708 - name = "quinn"
1709 - version = "0.11.11"
1710 - source = "registry+https://github.com/rust-lang/crates.io-index"
1711 - checksum = "0c1a41e437b6bbd489372cd4971de128e85c855f56c57f283d20ff016cf7c0a8"
1712 - dependencies = [
1713 - "bytes",
1714 - "cfg_aliases",
1715 - "pin-project-lite",
1716 - "quinn-proto",
1717 - "quinn-udp",
1718 - "rustc-hash",
1719 - "rustls",
1720 - "socket2",
1721 - "thiserror",
1722 - "tokio",
1723 - "tracing",
1724 - "web-time",
1725 - ]
1726 -
1727 - [[package]]
1728 - name = "quinn-proto"
1729 - version = "0.11.16"
1730 - source = "registry+https://github.com/rust-lang/crates.io-index"
1731 - checksum = "2f4bfc015262b9df63c8845072ce59068853ff5872180c2ce2f13038b970e560"
1732 - dependencies = [
1733 - "aws-lc-rs",
1734 - "bytes",
1735 - "getrandom 0.4.1",
1736 - "lru-slab",
1737 - "rand",
1738 - "rand_pcg",
1739 - "ring",
1740 - "rustc-hash",
1741 - "rustls",
1742 - "rustls-pki-types",
1743 - "slab",
1744 - "thiserror",
1745 - "tinyvec",
1746 - "tracing",
1747 - "web-time",
1748 - ]
1749 -
1750 - [[package]]
1751 - name = "quinn-udp"
1752 - version = "0.5.15"
1753 - source = "registry+https://github.com/rust-lang/crates.io-index"
1754 - checksum = "35a133f956daabe89a61a685c2649f13d82d5aa4bd5d12d1277e1072a21c0694"
1755 - dependencies = [
1756 - "cfg_aliases",
1757 - "libc",
1758 - "once_cell",
1759 - "socket2",
1760 - "tracing",
1761 - "windows-sys 0.61.2",
1762 - ]
1763 -
1764 - [[package]]
1765 1577 name = "quote"
1766 1578 version = "1.0.44"
1767 1579 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1803,15 +1615,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
1803 1615 checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69"
1804 1616
1805 1617 [[package]]
1806 - name = "rand_pcg"
1807 - version = "0.10.2"
1808 - source = "registry+https://github.com/rust-lang/crates.io-index"
1809 - checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a"
1810 - dependencies = [
1811 - "rand_core 0.10.1",
1812 - ]
1813 -
1814 - [[package]]
1815 1618 name = "redox_syscall"
1816 1619 version = "0.5.18"
1817 1620 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1874,17 +1677,13 @@ dependencies = [
1874 1677 "native-tls",
1875 1678 "percent-encoding",
1876 1679 "pin-project-lite",
1877 - "quinn",
1878 - "rustls",
1879 1680 "rustls-pki-types",
1880 - "rustls-platform-verifier",
1881 1681 "serde",
1882 1682 "serde_json",
1883 1683 "serde_urlencoded",
1884 1684 "sync_wrapper",
1885 1685 "tokio",
1886 1686 "tokio-native-tls",
1887 - "tokio-rustls",
1888 1687 "tokio-util",
1889 1688 "tower",
1890 1689 "tower-http",
@@ -1936,21 +1735,6 @@ dependencies = [
1936 1735 ]
1937 1736
1938 1737 [[package]]
1939 - name = "rustc-hash"
1940 - version = "2.1.3"
1941 - source = "registry+https://github.com/rust-lang/crates.io-index"
1942 - checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d"
1943 -
1944 - [[package]]
1945 - name = "rustc_version"
1946 - version = "0.4.1"
1947 - source = "registry+https://github.com/rust-lang/crates.io-index"
1948 - checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
1949 - dependencies = [
1950 - "semver",
1951 - ]
1952 -
1953 - [[package]]
1954 1738 name = "rustix"
1955 1739 version = "1.1.4"
1956 1740 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1969,7 +1753,6 @@ version = "0.23.37"
1969 1753 source = "registry+https://github.com/rust-lang/crates.io-index"
1970 1754 checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4"
1971 1755 dependencies = [
1972 - "aws-lc-rs",
1973 1756 "once_cell",
1974 1757 "rustls-pki-types",
1975 1758 "rustls-webpki",
@@ -1978,61 +1761,20 @@ dependencies = [
1978 1761 ]
1979 1762
1980 1763 [[package]]
1981 - name = "rustls-native-certs"
1982 - version = "0.8.4"
1983 - source = "registry+https://github.com/rust-lang/crates.io-index"
1984 - checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d"
1985 - dependencies = [
1986 - "openssl-probe",
1987 - "rustls-pki-types",
1988 - "schannel",
1989 - "security-framework",
1990 - ]
1991 -
1992 - [[package]]
1993 1764 name = "rustls-pki-types"
1994 1765 version = "1.14.0"
1995 1766 source = "registry+https://github.com/rust-lang/crates.io-index"
1996 1767 checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd"
1997 1768 dependencies = [
1998 - "web-time",
1999 1769 "zeroize",
2000 1770 ]
2001 1771
2002 1772 [[package]]
2003 - name = "rustls-platform-verifier"
2004 - version = "0.7.0"
2005 - source = "registry+https://github.com/rust-lang/crates.io-index"
2006 - checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0"
2007 - dependencies = [
2008 - "core-foundation 0.10.1",
2009 - "core-foundation-sys",
2010 - "jni",
2011 - "log",
2012 - "once_cell",
2013 - "rustls",
2014 - "rustls-native-certs",
2015 - "rustls-platform-verifier-android",
2016 - "rustls-webpki",
2017 - "security-framework",
2018 - "security-framework-sys",
2019 - "webpki-root-certs",
2020 - "windows-sys 0.61.2",
2021 - ]
2022 -
2023 - [[package]]
2024 - name = "rustls-platform-verifier-android"
2025 - version = "0.1.1"
2026 - source = "registry+https://github.com/rust-lang/crates.io-index"
2027 - checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f"
2028 -
2029 - [[package]]
2030 1773 name = "rustls-webpki"
2031 1774 version = "0.103.13"
2032 1775 source = "registry+https://github.com/rust-lang/crates.io-index"
2033 1776 checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e"
2034 1777 dependencies = [
2035 - "aws-lc-rs",
2036 1778 "ring",
2037 1779 "rustls-pki-types",
2038 1780 "untrusted",
@@ -2051,15 +1793,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
2051 1793 checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
2052 1794
2053 1795 [[package]]
2054 - name = "same-file"
2055 - version = "1.0.6"
2056 - source = "registry+https://github.com/rust-lang/crates.io-index"
2057 - checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
2058 - dependencies = [
2059 - "winapi-util",
2060 - ]
2061 -
2062 - [[package]]
2063 1796 name = "schannel"
2064 1797 version = "0.1.28"
2065 1798 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2216,22 +1949,6 @@ dependencies = [
2216 1949 ]
2217 1950
2218 1951 [[package]]
2219 - name = "simd_cesu8"
2220 - version = "1.2.0"
2221 - source = "registry+https://github.com/rust-lang/crates.io-index"
2222 - checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520"
2223 - dependencies = [
2224 - "rustc_version",
2225 - "simdutf8",
2226 - ]
2227 -
2228 - [[package]]
2229 - name = "simdutf8"
2230 - version = "0.1.5"
2231 - source = "registry+https://github.com/rust-lang/crates.io-index"
2232 - checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
2233 -
2234 - [[package]]
2235 1952 name = "slab"
2236 1953 version = "0.4.12"
2237 1954 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2712,16 +2429,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
2712 2429 checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
2713 2430
2714 2431 [[package]]
2715 - name = "walkdir"
2716 - version = "2.5.0"
2717 - source = "registry+https://github.com/rust-lang/crates.io-index"
2718 - checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
2719 - dependencies = [
2720 - "same-file",
2721 - "winapi-util",
2722 - ]
2723 -
2724 - [[package]]
2725 2432 name = "want"
2726 2433 version = "0.3.1"
2727 2434 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2871,34 +2578,6 @@ dependencies = [
2871 2578 ]
2872 2579
2873 2580 [[package]]
2874 - name = "web-time"
2875 - version = "1.1.0"
2876 - source = "registry+https://github.com/rust-lang/crates.io-index"
2877 - checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
2878 - dependencies = [
2879 - "js-sys",
2880 - "wasm-bindgen",
2881 - ]
2882 -
2883 - [[package]]
2884 - name = "webpki-root-certs"
2885 - version = "1.0.9"
2886 - source = "registry+https://github.com/rust-lang/crates.io-index"
2887 - checksum = "b96554aa2acc8ccdb7e1c9a58a7a68dd5d13bccc69cd124cb09406db612a1c9b"
2888 - dependencies = [
2889 - "rustls-pki-types",
2890 - ]
2891 -
2892 - [[package]]
2893 - name = "winapi-util"
2894 - version = "0.1.11"
2895 - source = "registry+https://github.com/rust-lang/crates.io-index"
2896 - checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
2897 - dependencies = [
2898 - "windows-sys 0.61.2",
2899 - ]
2900 -
2901 - [[package]]
2902 2581 name = "windows-core"
2903 2582 version = "0.62.2"
2904 2583 source = "registry+https://github.com/rust-lang/crates.io-index"