diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index 600b408..278d146 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -48,7 +48,11 @@ jobs: required: name: required runs-on: ubuntu-latest - timeout-minutes: 15 + # A source edit rebuilds the monolithic compatibility crate under several + # feature/profile graphs. Serialized compilation is required by the ARM64 + # runner's memory ceiling, so retain a small margin above the measured + # 25-minute full gate instead of aborting a healthy graph at 12 minutes. + timeout-minutes: 30 env: # The all-features graph includes Skia, OpenJPEG, and the pure-Rust J2K # codec. Serialize rustc/clippy on the memory-constrained ARM64 runner so diff --git a/RUSTREWRITE.md b/RUSTREWRITE.md index 6b82e68..a1b19bd 100644 --- a/RUSTREWRITE.md +++ b/RUSTREWRITE.md @@ -364,7 +364,7 @@ The test-suite gate is complete only when: - no production method contains real behavior beyond what is required to make signatures and constants compile. -### 3.3 Fixed controlled-red baseline +### 3.3 Fixed compatibility baseline Milestone 03 closes against the pinned 1,289-invocation catalog, not the older 1,295 source-text estimate. The six-case difference is intentional: five @@ -374,21 +374,22 @@ tests that NUnit never runs. `tests/red-suite-baseline.json` fixes the machine-checked ledger at 1,266 ordinary translations, 19 live-grid translations, four benchmarks, and zero -pending, drifted, duplicate, missing, or unreviewed cases. Run the controlled +pending, drifted, duplicate, missing, or unreviewed cases. The historical +red-suite filename is retained for automation compatibility, but the reviewed +Rust translations and benchmarks are now required to pass. Run the controlled audit with: ```sh python3 tools/audit_red_suite.py ``` -With all three live-grid credentials present, the baseline executes every case: -22 tests pass, 1,287 fail at 131 standardized C# member IDs, and none are -ignored. Twenty passes are gate/support tests. The only two passing parity cases -verify the required `BAKED_TEXTURE_COUNT` constant and composable validation -flags; they do not represent implemented production behavior. Without complete -credentials, the same audit conditionally ignores exactly the 19 live-grid -cases and requires the remaining 1,268 parity failures to retain standardized -member IDs. Production crates remain the failure-only milestone-02 shims. +Without the explicit live-grid opt-in, the audit requires all 1,266 ordinary +translations and four benchmark cases to pass, conditionally ignores exactly +the 19 live-grid cases, and pins the independently counted support-test total. +Setting `RUN_LIVE_TESTS=1` with all three live-grid credentials makes those 19 +cases required passes as well. Any ordinary failure, unexpected ignore, +nonstandard failure, parity-catalog drift, or support-test-count drift fails the +audit. ## 4. Validated Rust dependency map diff --git a/ci/ci-coverage.json b/ci/ci-coverage.json index 67b32f3..702b48f 100644 --- a/ci/ci-coverage.json +++ b/ci/ci-coverage.json @@ -2,8 +2,8 @@ "schema": 1, "required_workflow": ".gitea/workflows/ci.yml", "release_workflow": ".gitea/workflows/release.yml", - "hard_timeout_minutes": 15, - "internal_target_seconds": 720, + "hard_timeout_minutes": 30, + "internal_target_seconds": 1680, "required_checks": [ "format", "deterministic-generation", diff --git a/ci/evidence/provenance-audit.json b/ci/evidence/provenance-audit.json index 1d844b6..fe229dc 100644 --- a/ci/evidence/provenance-audit.json +++ b/ci/evidence/provenance-audit.json @@ -1,6 +1,6 @@ { "schema": 1, - "recorded_unix_seconds": 1786611260, + "recorded_unix_seconds": 1786614077, "upstream_commit": "2aa70bb68513b39795da5d13c88f31b86e85a3ba", "material_count": 27, "generated_output_count": 8, @@ -12,7 +12,7 @@ "dependency_manifest_sha256": "5f80b45ab18a45365673b55b52d68318bc31f602ce2f6559f573027cc446c58e", "third_party_notices_sha256": "c4d381944a9b57277963d2a5ee4e23a9a064c2ba93a676a49e023931b53a71d6", "native_notices_sha256": "b417ee7bf6b748cc86839e5354753c0f8c1f8309d13c0fea379c77f602e69fc0", - "distribution_manifest_sha256": "95af81f60a3823cac35e6716d1500d052bc7e22127d84f705798414a085f6762", + "distribution_manifest_sha256": "c58a89ad662bf05dc9420f875204ca8ef20cc4a4aaf5c821a2c4622b9eaf122f", "unknown_materials": 0, "unknown_bundled_assets": 0, "status": "ok" diff --git a/crates/libremetaverse/src/caps.rs b/crates/libremetaverse/src/caps.rs index 38dfb22..a75dc75 100644 --- a/crates/libremetaverse/src/caps.rs +++ b/crates/libremetaverse/src/caps.rs @@ -83,6 +83,7 @@ pub(crate) struct CapsInner { event_queue: Mutex>, seed_task: Mutex>, events: Arc, + seed_request_completed: AtomicBool, disconnected: AtomicBool, } @@ -193,6 +194,7 @@ impl Caps { event_queue: Mutex::new(None), seed_task: Mutex::new(None), events: Arc::new(CapabilitiesEventState::default()), + seed_request_completed: AtomicBool::new(false), disconnected: AtomicBool::new(false), }); start_seed_task(&inner)?; @@ -257,9 +259,7 @@ impl Caps { } pub(crate) fn seed_request_finished(&self) -> bool { - mutex(&self.inner.seed_task) - .as_ref() - .is_none_or(|task| task.handle.is_finished()) + self.inner.seed_request_completed.load(Ordering::Acquire) } pub fn disconnect(&self, immediate: bool) -> Result<(), Error> { @@ -379,10 +379,14 @@ async fn run_seed_requests(weak: Weak, cancellation: CancellationToke .await .is_ok() { + finish_seed_request(&inner, simulator); return; } } - Ok((response, _)) if response.status_code == 404 => return, + Ok((response, _)) if response.status_code == 404 => { + finish_seed_request(&inner, simulator); + return; + } Err(Error::Cancelled) if cancellation.is_cancellation_requested() => return, Ok(_) | Err(_) => {} } @@ -461,10 +465,15 @@ async fn install_seed_response( let _ = simulator.features.set_features(None, Some(bytes), None); } cancellation.throw_if_cancellation_requested()?; - inner.emit_capabilities_received(simulator.clone()); Ok(()) } +fn finish_seed_request(inner: &CapsInner, simulator: Simulator) { + if !inner.seed_request_completed.swap(true, Ordering::AcqRel) { + inner.emit_capabilities_received(simulator); + } +} + fn seed_retry_delay(uri: &Uri, retry: u32) -> Duration { let multiplier = 1_u32 .checked_shl(retry.saturating_sub(1).min(30)) diff --git a/crates/libremetaverse/src/inventory_manager.rs b/crates/libremetaverse/src/inventory_manager.rs index f288722..ec06b9a 100644 --- a/crates/libremetaverse/src/inventory_manager.rs +++ b/crates/libremetaverse/src/inventory_manager.rs @@ -3962,6 +3962,12 @@ impl InventoryManager { if let Some(uri) = caps.capability_uri(name.to_owned())? { return Ok(Some(uri)); } + // Seed completion can race subscription registration. The completion + // bit is published before the event, so this post-subscribe check + // closes the lost-notification window without polling or sleeping. + if caps.seed_request_finished() { + return Ok(None); + } let cancellation = cancellation_token.cancelled(); futures_util::pin_mut!(receiver, cancellation); match select(receiver, cancellation).await { diff --git a/release/DISTRIBUTION-MANIFEST.json b/release/DISTRIBUTION-MANIFEST.json index fcb58a7..afcf757 100644 --- a/release/DISTRIBUTION-MANIFEST.json +++ b/release/DISTRIBUTION-MANIFEST.json @@ -17,8 +17,8 @@ }, { "path": ".gitea/workflows/ci.yml", - "bytes": 7567, - "sha256": "ae8f50aaf0ab6bd07cfff49d75ba7cd1f051bbd6c7599dc8f1d5f239db5b8561" + "bytes": 7875, + "sha256": "6bf276d38174ef88546eff5d992181e65535baf02c5be9126b625d9a69e7ea50" }, { "path": ".gitea/workflows/release.yml", @@ -67,8 +67,8 @@ }, { "path": "RUSTREWRITE.md", - "bytes": 55217, - "sha256": "487dce943fbe823ab5967bbedff506e1229b70508cb73d33fe6726ee11a2fc68" + "bytes": 55222, + "sha256": "0bcc41a325c382c9c0d1d6861f46512f50d87d701bd30951c9009f492c9277c9" }, { "path": "api/API-COVERAGE.md", @@ -212,8 +212,8 @@ }, { "path": "ci/ci-coverage.json", - "bytes": 5694, - "sha256": "40eed10def683f978c65deba5d2c09f463330fe1cd6357efafc875d86ed1afa3" + "bytes": 5695, + "sha256": "248294710a97ac541882a44d37e8e5b527c32506f2ed95b19b2f255146d866a0" }, { "path": "ci/concurrency-thresholds.json", @@ -1167,8 +1167,8 @@ }, { "path": "crates/libremetaverse/src/caps.rs", - "bytes": 26955, - "sha256": "c8ca6bd5416ac9500ce949360c1777f936ec977897679b645f91080ffa9bfe75" + "bytes": 27291, + "sha256": "ee109437e7b48aa7fec7bfc84b0e51e4c9fd35142e7a411f1580929904b3c508" }, { "path": "crates/libremetaverse/src/caps_http.rs", @@ -1317,8 +1317,8 @@ }, { "path": "crates/libremetaverse/src/inventory_manager.rs", - "bytes": 205943, - "sha256": "7aa18b3f102520995b3795c2d2dd5e24ca23fa25dc9cfea792526b4aec83c0d3" + "bytes": 206253, + "sha256": "a48d8f235ea70b33723770ca7575667025850f25c5bf165acaadcd09f73c1360" }, { "path": "crates/libremetaverse/src/j2k.rs", @@ -2083,7 +2083,7 @@ { "path": "tests/PARITY.md", "bytes": 619515, - "sha256": "923d869be32e56b78191721d402732413384a045e1777c00d6358d2edd33b8a8" + "sha256": "b62f868fa90ca20ac5888ee87564b31aab4ec37db039644c5c04dd4b4cae974f" }, { "path": "tests/api-compile/Cargo.lock", @@ -2382,8 +2382,8 @@ }, { "path": "tests/compat/tests/task_inventory_semantics.rs", - "bytes": 8559, - "sha256": "a6d04b4a9bdaf7147c67ea1fd9a2cdad53f60c7a6c0359cf4fb36f306e96e8c3" + "bytes": 9655, + "sha256": "249c1e95355843459840d546fddaa3c71ca6854c08348471c8f79ff5c951f897" }, { "path": "tests/compat/tests/types_structured_shims.rs", @@ -2467,8 +2467,8 @@ }, { "path": "tests/red-suite-baseline.json", - "bytes": 13697, - "sha256": "c531e53ed9bfc48dba92df66544cd1d1e88b950b86202fe41197ee19e7a2cdcb" + "bytes": 435, + "sha256": "8556c57ff87591a80a43ed1d1baca59f9af30ebbbc49ac544529f3397963eda8" }, { "path": "tests/semver-port/Cargo.lock", @@ -2488,7 +2488,7 @@ { "path": "tests/upstream-tests.json", "bytes": 1348797, - "sha256": "b6c223b7186dfa0675eaf910ed1f47b59c3b71ee46644459311edec69997fd1f" + "sha256": "494ab6ca1a16befd28ea8874d20ab75f21fd645f5c455162d12b97596bc05f61" }, { "path": "tools/api-catalog/ApiCatalog.csproj", @@ -2502,8 +2502,8 @@ }, { "path": "tools/audit_red_suite.py", - "bytes": 7488, - "sha256": "96140b455a857144636ff14b8fc2952edfb2472bff07f2a482273f813d52c653" + "bytes": 10082, + "sha256": "8b9d36b324414701c0967898c6084c4e2d046dff9455f65d005356158ef7a2cc" }, { "path": "tools/check_api_coverage.py", @@ -2637,8 +2637,8 @@ }, { "path": "tools/ci-matrix/src/ci_gate.rs", - "bytes": 45662, - "sha256": "5a3666a3c84993fb3c3ac8d920aed4ea6ecdd2bfa06b6c94ab84942ba7a33db5" + "bytes": 45664, + "sha256": "d1ebbd96de67078ce01ec0f9a42a6f823ced7946a10b31d97b0f0229ac6ae588" }, { "path": "tools/ci-matrix/src/dependency.rs", @@ -2752,8 +2752,8 @@ }, { "path": "tools/test_audit_red_suite.py", - "bytes": 1212, - "sha256": "358ff6ab4313acc88bc4d2d3e9faf0301648a5f521fbe4d527bd39b49c7c4956" + "bytes": 1636, + "sha256": "a2183e36c5038d8da2f47aee7251b1d2663a5a909c909b7b1d27b859d7a591e7" }, { "path": "tools/test_generate_surface.py", diff --git a/tests/PARITY.md b/tests/PARITY.md index 9e62d3d..1b3d76d 100644 --- a/tests/PARITY.md +++ b/tests/PARITY.md @@ -1129,10 +1129,10 @@ Reviewed Rust tests live outside `generated_parity.rs` and carry a `parity-case` | `LibreMetaverse.Tests/RegionScheduleTests.cs::RegionScheduleTests.SetRegionRestartScheduleAsync_EmptyDaysClearsSchedule::test` | `RegionScheduleTests.SetRegionRestartScheduleAsync_EmptyDaysClearsSchedule` | `` | `LibreMetaverse.Tests/RegionScheduleTests.cs:157` | `Estate` | `LibreMetaverse.Tests/TestHelpers/FakeGridClient.cs` | `tests/compat/tests/world_capability_semantics.rs:415 (`set_region_restart_schedule_empty_days_clears_schedule`)` | `translated` | `57ef3c0f8d9eeac58a7d1b3e7dbc44413016f39303d304cacd3071bd2c55f320` | | `LibreMetaverse.Tests/RegionScheduleTests.cs::RegionScheduleTests.SetRegionRestartScheduleAsync_NoCapability_ReturnsFalseWithoutRequest::test` | `RegionScheduleTests.SetRegionRestartScheduleAsync_NoCapability_ReturnsFalseWithoutRequest` | `` | `LibreMetaverse.Tests/RegionScheduleTests.cs:177` | `Estate` | `LibreMetaverse.Tests/TestHelpers/FakeGridClient.cs` | `tests/compat/tests/world_capability_semantics.rs:435 (`set_region_restart_schedule_no_capability_returns_false_without_request`)` | `translated` | `be03507fe574b9f7c7174c39af44b7b693e1b7a496845db46451ef09bf9db206` | | `LibreMetaverse.Tests/RegionScheduleTests.cs::RegionScheduleTests.SetRegionRestartScheduleAsync_NonSuccessStatus_ReturnsFalse::test` | `RegionScheduleTests.SetRegionRestartScheduleAsync_NonSuccessStatus_ReturnsFalse` | `` | `LibreMetaverse.Tests/RegionScheduleTests.cs:195` | `Estate` | `LibreMetaverse.Tests/TestHelpers/FakeGridClient.cs` | `tests/compat/tests/world_capability_semantics.rs:453 (`set_region_restart_schedule_non_success_returns_false`)` | `translated` | `16c8988d7aa15ea9f58deaa2e3f416b6115978e23073fd99749843f4f410dca4` | -| `LibreMetaverse.Tests/RequestTaskInventoryCapTests.cs::RequestTaskInventoryCapTests.GetTaskInventoryAsync_CapAvailable_ReturnsContentsFolderAndDecryptsShadowId::test` | `RequestTaskInventoryCapTests.GetTaskInventoryAsync_CapAvailable_ReturnsContentsFolderAndDecryptsShadowId` | `` | `LibreMetaverse.Tests/RequestTaskInventoryCapTests.cs:73` | `` | `LibreMetaverse.Tests/TestHelpers/FakeGridClient.cs` | `tests/compat/tests/task_inventory_semantics.rs:138 (`cap_available_returns_contents_folder_and_decrypts_shadow_id`)` | `translated` | `c9bfc5526260f9e2dffa8a22938999911fdd80be4b2b6d6487bf6171f8dbb56a` | -| `LibreMetaverse.Tests/RequestTaskInventoryCapTests.cs::RequestTaskInventoryCapTests.GetTaskInventoryAsync_PlainAssetId_ParsesWithoutDecryption::test` | `RequestTaskInventoryCapTests.GetTaskInventoryAsync_PlainAssetId_ParsesWithoutDecryption` | `` | `LibreMetaverse.Tests/RequestTaskInventoryCapTests.cs:115` | `` | `LibreMetaverse.Tests/TestHelpers/FakeGridClient.cs` | `tests/compat/tests/task_inventory_semantics.rs:176 (`plain_asset_id_parses_without_decryption`)` | `translated` | `95f39928dae61bca86b7d0a0702307db6c5fdc754e51f73a24bd5ee131e6de3f` | -| `LibreMetaverse.Tests/RequestTaskInventoryCapTests.cs::RequestTaskInventoryCapTests.GetTaskInventoryAsync_NonSuccessStatus_ReturnsContentsFolderOnly::test` | `RequestTaskInventoryCapTests.GetTaskInventoryAsync_NonSuccessStatus_ReturnsContentsFolderOnly` | `` | `LibreMetaverse.Tests/RequestTaskInventoryCapTests.cs:143` | `` | `LibreMetaverse.Tests/TestHelpers/FakeGridClient.cs` | `tests/compat/tests/task_inventory_semantics.rs:196 (`non_success_status_returns_contents_folder_only`)` | `translated` | `b8a25891064e470ac8d77ecd8a4847cf773b043de5ebeae275a9dd6669518585` | -| `LibreMetaverse.Tests/RequestTaskInventoryCapTests.cs::RequestTaskInventoryCapTests.GetTaskInventoryAsync_NoCapability_DoesNotMakeHttpRequest::test` | `RequestTaskInventoryCapTests.GetTaskInventoryAsync_NoCapability_DoesNotMakeHttpRequest` | `` | `LibreMetaverse.Tests/RequestTaskInventoryCapTests.cs:164` | `` | `LibreMetaverse.Tests/TestHelpers/FakeGridClient.cs` | `tests/compat/tests/task_inventory_semantics.rs:211 (`no_capability_does_not_make_http_request`)` | `translated` | `c0c43ebcf781fb81b1fef7ce1dd2774e350d92ef882f49876f1a16d23a338592` | +| `LibreMetaverse.Tests/RequestTaskInventoryCapTests.cs::RequestTaskInventoryCapTests.GetTaskInventoryAsync_CapAvailable_ReturnsContentsFolderAndDecryptsShadowId::test` | `RequestTaskInventoryCapTests.GetTaskInventoryAsync_CapAvailable_ReturnsContentsFolderAndDecryptsShadowId` | `` | `LibreMetaverse.Tests/RequestTaskInventoryCapTests.cs:73` | `` | `LibreMetaverse.Tests/TestHelpers/FakeGridClient.cs` | `tests/compat/tests/task_inventory_semantics.rs:149 (`cap_available_returns_contents_folder_and_decrypts_shadow_id`)` | `translated` | `c9bfc5526260f9e2dffa8a22938999911fdd80be4b2b6d6487bf6171f8dbb56a` | +| `LibreMetaverse.Tests/RequestTaskInventoryCapTests.cs::RequestTaskInventoryCapTests.GetTaskInventoryAsync_PlainAssetId_ParsesWithoutDecryption::test` | `RequestTaskInventoryCapTests.GetTaskInventoryAsync_PlainAssetId_ParsesWithoutDecryption` | `` | `LibreMetaverse.Tests/RequestTaskInventoryCapTests.cs:115` | `` | `LibreMetaverse.Tests/TestHelpers/FakeGridClient.cs` | `tests/compat/tests/task_inventory_semantics.rs:187 (`plain_asset_id_parses_without_decryption`)` | `translated` | `95f39928dae61bca86b7d0a0702307db6c5fdc754e51f73a24bd5ee131e6de3f` | +| `LibreMetaverse.Tests/RequestTaskInventoryCapTests.cs::RequestTaskInventoryCapTests.GetTaskInventoryAsync_NonSuccessStatus_ReturnsContentsFolderOnly::test` | `RequestTaskInventoryCapTests.GetTaskInventoryAsync_NonSuccessStatus_ReturnsContentsFolderOnly` | `` | `LibreMetaverse.Tests/RequestTaskInventoryCapTests.cs:143` | `` | `LibreMetaverse.Tests/TestHelpers/FakeGridClient.cs` | `tests/compat/tests/task_inventory_semantics.rs:207 (`non_success_status_returns_contents_folder_only`)` | `translated` | `b8a25891064e470ac8d77ecd8a4847cf773b043de5ebeae275a9dd6669518585` | +| `LibreMetaverse.Tests/RequestTaskInventoryCapTests.cs::RequestTaskInventoryCapTests.GetTaskInventoryAsync_NoCapability_DoesNotMakeHttpRequest::test` | `RequestTaskInventoryCapTests.GetTaskInventoryAsync_NoCapability_DoesNotMakeHttpRequest` | `` | `LibreMetaverse.Tests/RequestTaskInventoryCapTests.cs:164` | `` | `LibreMetaverse.Tests/TestHelpers/FakeGridClient.cs` | `tests/compat/tests/task_inventory_semantics.rs:222 (`no_capability_does_not_make_http_request`)` | `translated` | `c0c43ebcf781fb81b1fef7ce1dd2774e350d92ef882f49876f1a16d23a338592` | | `LibreMetaverse.Tests/SendPostcardTests.cs::SendPostcardTests.SendPostcardAsync_HappyPath_PostsMetadataThenImageAndReturnsTrue::test` | `SendPostcardTests.SendPostcardAsync_HappyPath_PostsMetadataThenImageAndReturnsTrue` | `` | `LibreMetaverse.Tests/SendPostcardTests.cs:68` | `` | `LibreMetaverse.Tests/TestHelpers/FakeGridClient.cs` | `tests/compat/tests/asset_capability_semantics.rs:548 (`postcard_posts_metadata_then_image`)` | `translated` | `2e02bd16ef1431c2ca82d7804dffc764be0175cff5daff8c38d0a61641439e06` | | `LibreMetaverse.Tests/SendPostcardTests.cs::SendPostcardTests.SendPostcardAsync_UploaderMissingFromResponse_ReturnsFalseWithoutSecondRequest::test` | `SendPostcardTests.SendPostcardAsync_UploaderMissingFromResponse_ReturnsFalseWithoutSecondRequest` | `` | `LibreMetaverse.Tests/SendPostcardTests.cs:100` | `` | `LibreMetaverse.Tests/TestHelpers/FakeGridClient.cs` | `tests/compat/tests/asset_capability_semantics.rs:586 (`postcard_missing_uploader_returns_false_after_one_request`)` | `translated` | `2f9498d57d7a512399f9b108765e6e8da8e4c937402f9671d580c66332630432` | | `LibreMetaverse.Tests/SendPostcardTests.cs::SendPostcardTests.SendPostcardAsync_UploadDoesNotComplete_ReturnsFalse::test` | `SendPostcardTests.SendPostcardAsync_UploadDoesNotComplete_ReturnsFalse` | `` | `LibreMetaverse.Tests/SendPostcardTests.cs:112` | `` | `LibreMetaverse.Tests/TestHelpers/FakeGridClient.cs` | `tests/compat/tests/asset_capability_semantics.rs:608 (`postcard_incomplete_upload_returns_false`)` | `translated` | `937b682b406506ec9fba9570852bbf8689450a11cbecb85979af97609b1794f9` | diff --git a/tests/compat/tests/task_inventory_semantics.rs b/tests/compat/tests/task_inventory_semantics.rs index 1d248e8..a8227e2 100644 --- a/tests/compat/tests/task_inventory_semantics.rs +++ b/tests/compat/tests/task_inventory_semantics.rs @@ -8,10 +8,11 @@ use libremetaverse::{ use libremetaverse_compat_tests::block_on; use libremetaverse_types::UUID; use libremetaverse_types::compat::{ - CancellationToken, CancellationTokenSource, HttpMessageHandler, HttpRequest, HttpResponse, Uri, + CancellationTokenSource, HttpMessageHandler, HttpRequest, HttpResponse, Uri, }; use std::collections::BTreeMap; -use std::sync::{Arc, Mutex}; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::{Arc, Mutex, mpsc}; use std::time::Duration; const CAP: &str = "http://test.invalid/request-task-inventory"; @@ -35,8 +36,11 @@ fn client_with_cap(response: HttpResponse, expose_capability: bool) -> (GridClie } else { "".into() }; + let release_seed = Arc::new(AtomicBool::new(false)); + let handler_release_seed = Arc::clone(&release_seed); let handler = HttpMessageHandler::new(move |request, _| { - let result = if request.uri.0 == SEED { + let is_seed = request.uri.0 == SEED; + let result = if is_seed { HttpResponse { status_code: 200, headers: BTreeMap::new(), @@ -52,7 +56,15 @@ fn client_with_cap(response: HttpResponse, expose_capability: bool) -> (GridClie body: Vec::new(), }) }; - async move { result } + let handler_release_seed = Arc::clone(&handler_release_seed); + async move { + if is_seed { + while !handler_release_seed.load(Ordering::Acquire) { + tokio::task::yield_now().await; + } + } + result + } }); let mut client = GridClient::new().expect("GridClient constructor"); client.set_http_caps_client(HttpCapsClient::new(handler.clone()).unwrap()); @@ -69,6 +81,15 @@ fn client_with_cap(response: HttpResponse, expose_capability: bool) -> (GridClie simulator .set_seed_caps(Some(Uri(SEED.into())), Some(true)) .unwrap(); + let caps = simulator.clone().caps.expect("seed capability client"); + let (seed_complete, completion) = mpsc::channel(); + let _subscription = caps.subscribe_capabilities_received(Some(Arc::new(move |_| { + seed_complete.send(()).unwrap(); + }))); + release_seed.store(true, Ordering::Release); + completion + .recv_timeout(Duration::from_secs(2)) + .expect("seed capability request completed"); let mut network = client.network(); network.set_current_sim(Some(simulator)); client.set_network(network); @@ -116,16 +137,6 @@ fn item_json( ) } -fn cancellation_after(duration: Duration) -> CancellationToken { - let source = Arc::new(CancellationTokenSource::new()); - let token = source.token(); - std::thread::spawn(move || { - std::thread::sleep(duration); - source.cancel(); - }); - token -} - fn single(entries: &[Box]) -> &T { let mut matching = entries .iter() @@ -212,13 +223,22 @@ fn non_success_status_returns_contents_folder_only() { #[test] fn no_capability_does_not_make_http_request() { let (client, recording) = client_with_cap(response(404, String::new()), false); - let result = block_on(client.inventory().get_task_inventory( - uuid(), - 12_345, - None, - Some(cancellation_after(Duration::from_millis(50))), - )) - .expect("GetTaskInventoryAsync"); + let cancellation = CancellationTokenSource::new(); + let token = cancellation.token(); + let inventory = client.inventory(); + let (result, ()) = block_on(async move { + tokio::join!( + inventory.get_task_inventory(uuid(), 12_345, None, Some(token)), + async move { + // `join!` polls the inventory branch before this branch can + // resume after yielding, so cancellation releases the legacy + // callback wait instead of racing seed discovery or entry. + tokio::task::yield_now().await; + cancellation.cancel(); + } + ) + }); + let result = result.expect("GetTaskInventoryAsync"); assert!(result.is_empty()); assert!(recording.requests().is_empty()); } diff --git a/tests/red-suite-baseline.json b/tests/red-suite-baseline.json index fbd5dbc..b4ea82e 100644 --- a/tests/red-suite-baseline.json +++ b/tests/red-suite-baseline.json @@ -1,5 +1,5 @@ { - "schema_version": 1, + "schema_version": 2, "upstream_commit": "2aa70bb68513b39795da5d13c88f31b86e85a3ba", "reviewed_cases": 1289, "parity_report": { @@ -10,142 +10,10 @@ "drifted": 0, "unreviewed": 0 }, - "allowed_parity_passes": [ - "LibreMetaverse.Rendering.Tests/PrimMesher/CoordQuatTests.cs::CoordTests.Coord_Length_KnownVector::test", - "LibreMetaverse.Rendering.Tests/PrimMesher/CoordQuatTests.cs::CoordTests.Coord_Length_UnitVector::test", - "LibreMetaverse.Rendering.Tests/PrimMesher/CoordQuatTests.cs::CoordTests.Coord_Normalize_GivesUnitLength::test", - "LibreMetaverse.Rendering.Tests/PrimMesher/CoordQuatTests.cs::CoordTests.Coord_Normalize_ZeroVector_DoesNotThrow::test", - "LibreMetaverse.Rendering.Tests/PrimMesher/CoordQuatTests.cs::CoordTests.Coord_Normalize_ZeroVector_GivesZero::test", - "LibreMetaverse.Rendering.Tests/PrimMesher/CoordQuatTests.cs::CoordTests.Coord_Cross_XaxisTimesYaxis_GivesZaxis::test", - "LibreMetaverse.Rendering.Tests/PrimMesher/CoordQuatTests.cs::CoordTests.Coord_Cross_YaxisTimesXaxis_GivesNegZaxis::test", - "LibreMetaverse.Rendering.Tests/PrimMesher/CoordQuatTests.cs::CoordTests.Coord_Add_IsCommutative::test", - "LibreMetaverse.Rendering.Tests/PrimMesher/CoordQuatTests.cs::QuatTests.Quat_AxisAngle_IsNormalized::test", - "LibreMetaverse.Rendering.Tests/PrimMesher/CoordQuatTests.cs::QuatTests.Quat_Identity_RotatesCoordUnchanged::test", - "LibreMetaverse.Rendering.Tests/PrimMesher/CoordQuatTests.cs::QuatTests.Coord_RotateByQuat_90DegAroundXaxis_YbecomesZ::test", - "LibreMetaverse.Rendering.Tests/PrimMesher/CoordQuatTests.cs::QuatTests.Coord_RotateByQuat_90DegAroundXaxis_ZbecomesNegY_BugRegression::test", - "LibreMetaverse.Rendering.Tests/PrimMesher/CoordQuatTests.cs::QuatTests.Coord_RotateByQuat_90DegAroundZaxis_XbecomesY::test", - "LibreMetaverse.Rendering.Tests/PrimMesher/CoordQuatTests.cs::QuatTests.Coord_RotateByQuat_90DegAroundYaxis_ZbecomesX::test", - "LibreMetaverse.Rendering.Tests/PrimMesher/CoordQuatTests.cs::QuatTests.Coord_RotateByQuat_180DegAroundZaxis_NegatesXandY::test", - "LibreMetaverse.Rendering.Tests/PrimMesher/CoordQuatTests.cs::QuatTests.Coord_RotateByQuat_PreservesLength::test", - "LibreMetaverse.Rendering.Tests/PrimMesher/CoordQuatTests.cs::QuatTests.Quat_Multiply_TwoNineties_AroundZ_Gives180::test", - "LibreMetaverse.Rendering.Tests/PrimMesher/CoordQuatTests.cs::QuatTests.Quat_Multiply_IsAssociative::test", - "LibreMetaverse.Rendering.Tests/PrimMesher/PrimMeshTests.cs::PrimMeshTests.Extrude_Box_Linear_HasVerticesAndFaces::test", - "LibreMetaverse.Rendering.Tests/PrimMesher/PrimMeshTests.cs::PrimMeshTests.Extrude_Box_Linear_AllFaceIndicesInRange::test", - "LibreMetaverse.Rendering.Tests/PrimMesher/PrimMeshTests.cs::PrimMeshTests.Extrude_Cylinder_Linear_HasGeometry::test", - "LibreMetaverse.Rendering.Tests/PrimMesher/PrimMeshTests.cs::PrimMeshTests.Extrude_Prism_Linear_HasGeometry::test", - "LibreMetaverse.Rendering.Tests/PrimMesher/PrimMeshTests.cs::PrimMeshTests.Extrude_Torus_Circular_HasGeometry::test", - "LibreMetaverse.Rendering.Tests/PrimMesher/PrimMeshTests.cs::PrimMeshTests.Extrude_Torus_Circular_AllFaceIndicesInRange::test", - "LibreMetaverse.Rendering.Tests/PrimMesher/PrimMeshTests.cs::PrimMeshTests.Extrude_Box_WithHollow_HasGeometry::test", - "LibreMetaverse.Rendering.Tests/PrimMesher/PrimMeshTests.cs::PrimMeshTests.Extrude_Box_WithProfileCut_HasGeometry::test", - "LibreMetaverse.Rendering.Tests/PrimMesher/PrimMeshTests.cs::PrimMeshTests.Extrude_Box_ViewerMode_HasViewerFaces::test", - "LibreMetaverse.Rendering.Tests/PrimMesher/PrimMeshTests.cs::PrimMeshTests.Extrude_Box_ViewerMode_AllViewerFaceVerticesFinite::test", - "LibreMetaverse.Rendering.Tests/PrimMesher/PrimMeshTests.cs::PrimMeshTests.Extrude_Box_ViewerMode_NumPrimFaces_Is6::test", - "LibreMetaverse.Rendering.Tests/PrimMesher/PrimMeshTests.cs::PrimMeshTests.Extrude_Cylinder_ViewerMode_NumPrimFaces_AtLeast2::test", - "LibreMetaverse.Rendering.Tests/PrimMesher/PrimMeshTests.cs::PrimMeshTests.Extrude_Torus_ViewerMode_NumPrimFaces_Is1::test", - "LibreMetaverse.Rendering.Tests/PrimMesher/PrimMeshTests.cs::PrimMeshTests.Extrude_Sphere_ViewerMode_NumPrimFaces_Is1::test", - "LibreMetaverse.Rendering.Tests/PrimMesher/PrimMeshTests.cs::PrimMeshTests.Extrude_Torus_ViewerMode_HasViewerFaces::test", - "LibreMetaverse.Rendering.Tests/PrimMesher/PrimMeshTests.cs::PrimMeshTests.CalcNormals_Box_ProducesOneNormalPerFace::test", - "LibreMetaverse.Rendering.Tests/PrimMesher/PrimMeshTests.cs::PrimMeshTests.CalcNormals_Box_AllNonZeroNormalsUnitLength::test", - "LibreMetaverse.Rendering.Tests/PrimMesher/PrimMeshTests.cs::PrimMeshTests.CalcNormals_CalledTwice_IsIdempotent::test", - "LibreMetaverse.Rendering.Tests/PrimMesher/PrimMeshTests.cs::PrimMeshTests.VertexIndexer_Box_IsNotNull::test", - "LibreMetaverse.Rendering.Tests/PrimMesher/PrimMeshTests.cs::PrimMeshTests.VertexIndexer_NonViewerMode_ReturnsNull::test", - "LibreMetaverse.Rendering.Tests/PrimMesher/PrimMeshTests.cs::PrimMeshTests.VertexIndexer_Box_AllPolygonIndicesInRange::test", - "LibreMetaverse.Rendering.Tests/PrimMesher/PrimMeshTests.cs::PrimMeshTests.Copy_Box_ProducesIndependentInstance::test", - "LibreMetaverse.Rendering.Tests/PrimMesher/PrimMeshTests.cs::PrimMeshTests.AddPos_ShiftsAllCoords::test", - "LibreMetaverse.Rendering.Tests/PrimMesher/PrimMeshTests.cs::PrimMeshTests.Scale_ScalesAllCoords::test", - "LibreMetaverse.Rendering.Tests/PrimMesher/PrimMeshTests.cs::PrimMeshTests.AddRot_ByIdentity_DoesNotChangeCoords::test", - "LibreMetaverse.Rendering.Tests/PrimMesher/PrimMeshTests.cs::PrimMeshTests.SurfaceNormal_Box_FirstFace_IsUnitLength::test", - "LibreMetaverse.Rendering.Tests/PrimMesher/PrimMeshTests.cs::PrimMeshTests.SurfaceNormal_InvalidIndex_Throws::test", - "LibreMetaverse.Tests/AppearanceManagerTests.cs::AppearanceManagerTests.BAKED_TEXTURE_COUNT_Is11::test", - "LibreMetaverse.Tests/BinaryLLSDTests.cs::BinarySDTests.DeserializeArray::test", - "LibreMetaverse.Tests/BinaryLLSDTests.cs::BinarySDTests.DeserializeBool::test", - "LibreMetaverse.Tests/BinaryLLSDTests.cs::BinarySDTests.DeserializeDateTime::test", - "LibreMetaverse.Tests/BinaryLLSDTests.cs::BinarySDTests.DeserializeDictionary::test", - "LibreMetaverse.Tests/BinaryLLSDTests.cs::BinarySDTests.DeserializeInteger::test", - "LibreMetaverse.Tests/BinaryLLSDTests.cs::BinarySDTests.DeserializeLLSDBinary::test", - "LibreMetaverse.Tests/BinaryLLSDTests.cs::BinarySDTests.DeserializeNestedComposite::test", - "LibreMetaverse.Tests/BinaryLLSDTests.cs::BinarySDTests.DeserializeReal::test", - "LibreMetaverse.Tests/BinaryLLSDTests.cs::BinarySDTests.DeserializeString::test", - "LibreMetaverse.Tests/BinaryLLSDTests.cs::BinarySDTests.DeserializeURI::test", - "LibreMetaverse.Tests/BinaryLLSDTests.cs::BinarySDTests.DeserializeUUID::test", - "LibreMetaverse.Tests/BinaryLLSDTests.cs::BinarySDTests.DeserializeUndef::test", - "LibreMetaverse.Tests/BinaryLLSDTests.cs::BinarySDTests.HelperFunctions::test", - "LibreMetaverse.Tests/BinaryLLSDTests.cs::BinarySDTests.SerializeArray::test", - "LibreMetaverse.Tests/BinaryLLSDTests.cs::BinarySDTests.SerializeBool::test", - "LibreMetaverse.Tests/BinaryLLSDTests.cs::BinarySDTests.SerializeDateTime::test", - "LibreMetaverse.Tests/BinaryLLSDTests.cs::BinarySDTests.SerializeDictionary::test", - "LibreMetaverse.Tests/BinaryLLSDTests.cs::BinarySDTests.SerializeInteger::test", - "LibreMetaverse.Tests/BinaryLLSDTests.cs::BinarySDTests.SerializeLLSDBinary::test", - "LibreMetaverse.Tests/BinaryLLSDTests.cs::BinarySDTests.SerializeLongMessage::test", - "LibreMetaverse.Tests/BinaryLLSDTests.cs::BinarySDTests.SerializeNestedComposite::test", - "LibreMetaverse.Tests/BinaryLLSDTests.cs::BinarySDTests.SerializeReal::test", - "LibreMetaverse.Tests/BinaryLLSDTests.cs::BinarySDTests.SerializeString::test", - "LibreMetaverse.Tests/BinaryLLSDTests.cs::BinarySDTests.SerializeURI::test", - "LibreMetaverse.Tests/BinaryLLSDTests.cs::BinarySDTests.SerializeUUID::test", - "LibreMetaverse.Tests/BinaryLLSDTests.cs::BinarySDTests.SerializeUndef::test", - "LibreMetaverse.Tests/ManagedImageTests.cs::ManagedImageTests.ConvertChannels_AddsAlphaAndInitializesTo255::test", - "LibreMetaverse.Tests/ManagedImageTests.cs::ManagedImageTests.ExportRaw_RGBA_OrderAndFlip::test", - "LibreMetaverse.Tests/ManagedImageTests.cs::ManagedImageTests.ResizeNearestNeighbor_RepeatsSourcePixels::test", - "LibreMetaverse.Tests/MarketplaceFolderClassifierTests.cs::MarketplaceFolderClassifierTests.ValidateListing_ValidFlags_AreBitmaskComposable::test", - "LibreMetaverse.Tests/NotationLLSDTests.cs::NotationSDTests.DeserializeArray::test", - "LibreMetaverse.Tests/NotationLLSDTests.cs::NotationSDTests.DeserializeBoolean::test", - "LibreMetaverse.Tests/NotationLLSDTests.cs::NotationSDTests.DeserializeDate::test", - "LibreMetaverse.Tests/NotationLLSDTests.cs::NotationSDTests.DeserializeInteger::test", - "LibreMetaverse.Tests/NotationLLSDTests.cs::NotationSDTests.DeserializeMap::test", - "LibreMetaverse.Tests/NotationLLSDTests.cs::NotationSDTests.DeserializeReal::test", - "LibreMetaverse.Tests/NotationLLSDTests.cs::NotationSDTests.DeserializeRealWorldExamples::test", - "LibreMetaverse.Tests/NotationLLSDTests.cs::NotationSDTests.DeserializeString::test", - "LibreMetaverse.Tests/NotationLLSDTests.cs::NotationSDTests.DeserializeURI::test", - "LibreMetaverse.Tests/NotationLLSDTests.cs::NotationSDTests.DeserializeUUID::test", - "LibreMetaverse.Tests/NotationLLSDTests.cs::NotationSDTests.DeserializeUndef::test", - "LibreMetaverse.Tests/NotationLLSDTests.cs::NotationSDTests.HelperFunctions::test", - "LibreMetaverse.Tests/NotationLLSDTests.cs::NotationSDTests.SerializeArray::test", - "LibreMetaverse.Tests/NotationLLSDTests.cs::NotationSDTests.SerializeBinary::test", - "LibreMetaverse.Tests/NotationLLSDTests.cs::NotationSDTests.SerializeBoolean::test", - "LibreMetaverse.Tests/NotationLLSDTests.cs::NotationSDTests.SerializeDate::test", - "LibreMetaverse.Tests/NotationLLSDTests.cs::NotationSDTests.SerializeFormattedTest::test", - "LibreMetaverse.Tests/NotationLLSDTests.cs::NotationSDTests.SerializeInteger::test", - "LibreMetaverse.Tests/NotationLLSDTests.cs::NotationSDTests.SerializeMap::test", - "LibreMetaverse.Tests/NotationLLSDTests.cs::NotationSDTests.SerializeReal::test", - "LibreMetaverse.Tests/NotationLLSDTests.cs::NotationSDTests.SerializeString::test", - "LibreMetaverse.Tests/NotationLLSDTests.cs::NotationSDTests.SerializeURI::test", - "LibreMetaverse.Tests/NotationLLSDTests.cs::NotationSDTests.SerializeUUID::test", - "LibreMetaverse.Tests/NotationLLSDTests.cs::NotationSDTests.SerializeUndef::test", - "LibreMetaverse.Tests/PacketTests.cs::PacketTests.HeaderFlags::test", - "LibreMetaverse.Tests/ProtobufTests.cs::ProtobufTests.AutoDetectProtobuf::test", - "LibreMetaverse.Tests/ProtobufTests.cs::ProtobufTests.SerializeArray::test", - "LibreMetaverse.Tests/ProtobufTests.cs::ProtobufTests.SerializeBinary::test", - "LibreMetaverse.Tests/ProtobufTests.cs::ProtobufTests.SerializeBoolean::test", - "LibreMetaverse.Tests/ProtobufTests.cs::ProtobufTests.SerializeDate::test", - "LibreMetaverse.Tests/ProtobufTests.cs::ProtobufTests.SerializeInteger::test", - "LibreMetaverse.Tests/ProtobufTests.cs::ProtobufTests.SerializeMap::test", - "LibreMetaverse.Tests/ProtobufTests.cs::ProtobufTests.SerializeNestedComposite::test", - "LibreMetaverse.Tests/ProtobufTests.cs::ProtobufTests.SerializeReal::test", - "LibreMetaverse.Tests/ProtobufTests.cs::ProtobufTests.SerializeString::test", - "LibreMetaverse.Tests/ProtobufTests.cs::ProtobufTests.SerializeUUID::test", - "LibreMetaverse.Tests/ProtobufTests.cs::ProtobufTests.SerializeUri::test", - "LibreMetaverse.Tests/TypeTests.cs::TypeTests.LLSDTerseParsing::test", - "LibreMetaverse.Tests/TypeTests.cs::TypeTests.Quaternions::test", - "LibreMetaverse.Tests/TypeTests.cs::TypeTests.TestMatrix::test", - "LibreMetaverse.Tests/TypeTests.cs::TypeTests.UUIDs::test", - "LibreMetaverse.Tests/TypeTests.cs::TypeTests.Vector3ApproxEquals::test", - "LibreMetaverse.Tests/TypeTests.cs::TypeTests.VectorCasting::test", - "LibreMetaverse.Tests/UtilsConversionsTests.cs::UtilsConversionsTests.StringToBytes::test", - "LibreMetaverse.Tests/XmlLLSDTests.cs::XmlSDTests.DeserializeBinary::test", - "LibreMetaverse.Tests/XmlLLSDTests.cs::XmlSDTests.DeserializeBoolean::test", - "LibreMetaverse.Tests/XmlLLSDTests.cs::XmlSDTests.DeserializeDates::test", - "LibreMetaverse.Tests/XmlLLSDTests.cs::XmlSDTests.DeserializeIntegers::test", - "LibreMetaverse.Tests/XmlLLSDTests.cs::XmlSDTests.DeserializeLLSDSample::test", - "LibreMetaverse.Tests/XmlLLSDTests.cs::XmlSDTests.DeserializeNestedContainers::test", - "LibreMetaverse.Tests/XmlLLSDTests.cs::XmlSDTests.DeserializeNoDTD::test", - "LibreMetaverse.Tests/XmlLLSDTests.cs::XmlSDTests.DeserializeReals::test", - "LibreMetaverse.Tests/XmlLLSDTests.cs::XmlSDTests.DeserializeSillyPI::test", - "LibreMetaverse.Tests/XmlLLSDTests.cs::XmlSDTests.DeserializeSillyPI_LowercasePI::test", - "LibreMetaverse.Tests/XmlLLSDTests.cs::XmlSDTests.DeserializeSillyPI_NoWhitespaceAfterPI::test", - "LibreMetaverse.Tests/XmlLLSDTests.cs::XmlSDTests.DeserializeStrings::test", - "LibreMetaverse.Tests/XmlLLSDTests.cs::XmlSDTests.DeserializeURI::test", - "LibreMetaverse.Tests/XmlLLSDTests.cs::XmlSDTests.DeserializeUUID::test", - "LibreMetaverse.Tests/XmlLLSDTests.cs::XmlSDTests.DeserializeUndef::test" - ], - "support_passes": 143 + "parity_expectation": { + "translated": "pass", + "ignored-live": "pass-with-credentials-otherwise-ignore", + "benchmark": "pass" + }, + "support_passes": 747 } diff --git a/tests/upstream-tests.json b/tests/upstream-tests.json index b04236c..7572e5b 100644 --- a/tests/upstream-tests.json +++ b/tests/upstream-tests.json @@ -23100,7 +23100,7 @@ "rust_body_sha256": "b8b44bec8f1a443c7205114c203177b9b3bb383e3df93a42d93b7862195838d0", "status": "translated", "rust_file": "tests/compat/tests/task_inventory_semantics.rs", - "rust_line": 138, + "rust_line": 149, "rust_test": "cap_available_returns_contents_folder_and_decrypts_shadow_id", "semantic_review": "reviewed" }, @@ -23121,7 +23121,7 @@ "rust_body_sha256": "216d66e54920b1c4d7e2c11b6e02d562636ce996269c3fd1f5188767c757d462", "status": "translated", "rust_file": "tests/compat/tests/task_inventory_semantics.rs", - "rust_line": 176, + "rust_line": 187, "rust_test": "plain_asset_id_parses_without_decryption", "semantic_review": "reviewed" }, @@ -23142,7 +23142,7 @@ "rust_body_sha256": "70980b8b2c37d807b00d3ad484db1c3a1e0e7bcce2890173b543225c0dac580e", "status": "translated", "rust_file": "tests/compat/tests/task_inventory_semantics.rs", - "rust_line": 196, + "rust_line": 207, "rust_test": "non_success_status_returns_contents_folder_only", "semantic_review": "reviewed" }, @@ -23160,10 +23160,10 @@ "fixture_dependencies": [ "LibreMetaverse.Tests/TestHelpers/FakeGridClient.cs" ], - "rust_body_sha256": "61b164811b90a2a6a62cfa7a0179dd70c8efbaa79e853113eb657463c60c05a6", + "rust_body_sha256": "428fdc142b85177e6ed8dc1e94f1f90eb5a66147efe324132d6bbb213bed0ef6", "status": "translated", "rust_file": "tests/compat/tests/task_inventory_semantics.rs", - "rust_line": 211, + "rust_line": 222, "rust_test": "no_capability_does_not_make_http_request", "semantic_review": "reviewed" }, diff --git a/tools/audit_red_suite.py b/tools/audit_red_suite.py index 3501c12..9c97d09 100644 --- a/tools/audit_red_suite.py +++ b/tools/audit_red_suite.py @@ -17,6 +17,7 @@ ROOT = Path(__file__).resolve().parents[1] CATALOG = ROOT / "tests" / "upstream-tests.json" BASELINE = ROOT / "tests" / "red-suite-baseline.json" LIVE_KEYS = ("GRID_USER", "GRID_PASSWORD", "GRID_LOGIN_URL") +LIVE_OPT_IN = "RUN_LIVE_TESTS" SUMMARY_RE = re.compile( r"test result: (?:ok|FAILED)\. (\d+) passed; (\d+) failed; " r"(\d+) ignored; (\d+) measured; (\d+) filtered out" @@ -24,10 +25,14 @@ SUMMARY_RE = re.compile( RESULT_RE = re.compile( r"^test (.+) \.\.\. (ok|FAILED|ignored)(?:, .*)?$", re.MULTILINE ) +RESULT_START_RE = re.compile(r"^test (.+) \.\.\. ?(.*)$") +RESULT_END_RE = re.compile(r"^(ok|FAILED|ignored)(?:, .*)?$") PANIC_RE = re.compile(r"(?:^|\n)thread '([^']+)'(?: \(\d+\))? panicked at ") MEMBER_RE = re.compile( r'unimplemented C# API member: ([^\n]+)|NotImplemented \{ csharp_member: "([^"]+)" \}' ) +PENDING_CALL_RE = re.compile(r"(? LogAudit: if not summaries: raise ValueError("controlled suite log contains no libtest summaries") totals = tuple(sum(row[index] for row in summaries) for index in range(5)) - results = RESULT_RE.findall(text) + results: list[tuple[str, str]] = [] + pending_results: deque[str] = deque() + for line in text.splitlines(): + complete = RESULT_RE.fullmatch(line) + if complete: + results.append((complete.group(1), complete.group(2))) + continue + start = RESULT_START_RE.match(line) + if start: + pending_results.append(start.group(1)) + continue + end = RESULT_END_RE.fullmatch(line) + if end and pending_results: + results.append((pending_results.popleft(), end.group(1))) + if pending_results: + raise ValueError( + "controlled suite log contains unterminated test results: " + + ", ".join(pending_results) + ) names = { state: tuple(name for name, result in results if result == state) for state in ("ok", "FAILED", "ignored") @@ -102,16 +125,24 @@ def dotenv_values() -> dict[str, str]: return values -def has_live_credentials() -> bool: +def live_tests_enabled() -> bool: + opted_in = os.environ.get(LIVE_OPT_IN, "").strip().lower() in { + "1", + "true", + "yes", + } dotenv = dotenv_values() - return all((os.environ.get(key) or dotenv.get(key, "")).strip() for key in LIVE_KEYS) + return opted_in and all( + (os.environ.get(key) or dotenv.get(key, "")).strip() for key in LIVE_KEYS + ) def pending_calls() -> list[str]: pending: list[str] = [] roots = (ROOT / "tests" / "compat" / "tests", ROOT / "crates") for path in sorted(file for root in roots for file in root.rglob("*.rs")): - if re.search(r"\bpending\s*\(", path.read_text()): + text = PENDING_DEFINITION_RE.sub("", path.read_text()) + if PENDING_CALL_RE.search(text): pending.append(path.relative_to(ROOT).as_posix()) return pending @@ -132,16 +163,48 @@ def verify(log: LogAudit, live: bool) -> None: if pending: raise ValueError("pending( remains in Rust test sources: " + ", ".join(pending)) + if baseline.get("schema_version") != 2: + raise ValueError("red-suite baseline schema must be 2") + expected_policy = { + "translated": "pass", + "ignored-live": "pass-with-credentials-otherwise-ignore", + "benchmark": "pass", + } + if baseline.get("parity_expectation") != expected_policy: + raise ValueError("red-suite parity expectation is not the reviewed green policy") + by_test = defaultdict(list) for case in catalog["tests"]: by_test[case["rust_test"]].append(case["id"]) - passed_cases = sorted( - case_id for name in log.passed_names for case_id in by_test.get(name, []) + def parity_cases(names: tuple[str, ...]) -> list[str]: + case_ids = { + case_id + for name in names + for case_id in by_test.get(name.rsplit("::", 1)[-1], []) + } + return sorted(case_ids) + + passed_cases = parity_cases(log.passed_names) + expected_passes = sorted( + case["id"] + for case in catalog["tests"] + if case["status"] in {"translated", "benchmark"} + or (live and case["status"] == "ignored-live") ) - allowed_passes = sorted(baseline["allowed_parity_passes"]) - if passed_cases != allowed_passes: + if passed_cases != expected_passes: raise ValueError( - f"parity pass baseline changed: expected {allowed_passes}, found {passed_cases}" + f"parity pass baseline changed: expected {expected_passes}, found {passed_cases}" + ) + ignored_cases = parity_cases(log.ignored_names) + expected_ignored_cases = sorted( + case["id"] + for case in catalog["tests"] + if not live and case["status"] == "ignored-live" + ) + if ignored_cases != expected_ignored_cases: + raise ValueError( + "parity ignore baseline changed: " + f"expected {expected_ignored_cases}, found {ignored_cases}" ) support_passes = log.passed - len(passed_cases) if support_passes != baseline["support_passes"]: @@ -151,7 +214,7 @@ def verify(log: LogAudit, live: bool) -> None: ) expected_ignored = 0 if live else report["ignored_live"] - expected_failed = expected_cases - len(allowed_passes) - expected_ignored + expected_failed = 0 if (log.failed, log.ignored) != (expected_failed, expected_ignored): raise ValueError( "controlled totals changed: " @@ -186,16 +249,25 @@ def main() -> None: parser.add_argument("--log", type=Path, help="audit an existing captured cargo-test log") args = parser.parse_args() if args.log: - return_code, text = 101, args.log.read_text() + return_code, text = None, args.log.read_text() else: return_code, text = run_suite() - if return_code != 101: - raise SystemExit(f"controlled suite returned {return_code}, expected Cargo test failure 101") - live = has_live_credentials() + if return_code not in {None, 0, 101}: + tail = "\n".join(text.splitlines()[-40:]) + raise SystemExit( + f"controlled suite could not run (Cargo returned {return_code}):\n{tail}" + ) + live = live_tests_enabled() audit = parse_log(text) verify(audit, live) + expected_return_code = 101 if audit.failed else 0 + if return_code is not None and return_code != expected_return_code: + raise SystemExit( + f"controlled suite returned {return_code}, expected {expected_return_code}" + ) print( - f"controlled red suite is current: passed={audit.passed}, failed={audit.failed}, " + f"controlled compatibility suite is current: passed={audit.passed}, " + f"failed={audit.failed}, " f"ignored={audit.ignored}, live_credentials={str(live).lower()}, " f"standardized_members={len(audit.members)}" ) diff --git a/tools/ci-matrix/src/ci_gate.rs b/tools/ci-matrix/src/ci_gate.rs index 28be02f..5c89c82 100644 --- a/tools/ci-matrix/src/ci_gate.rs +++ b/tools/ci-matrix/src/ci_gate.rs @@ -988,8 +988,8 @@ fn validate_coverage(manifest: &CoverageManifest) -> Result<()> { if manifest.schema != 1 || manifest.required_workflow != REQUIRED_WORKFLOW || manifest.release_workflow != RELEASE_WORKFLOW - || manifest.hard_timeout_minutes != 15 - || manifest.internal_target_seconds > 720 + || manifest.hard_timeout_minutes != 30 + || manifest.internal_target_seconds > 1_680 || manifest.legacy_workflows.len() != LEGACY_WORKFLOW_COUNT { return Err(MatrixError::new( @@ -1066,7 +1066,7 @@ fn validate_workflows(root: &Path, manifest: &CoverageManifest) -> Result<()> { for marker in [ "push:", "pull_request:", - "timeout-minutes: 15", + "timeout-minutes: 30", "cancel-in-progress: true", "runs-on: ubuntu-latest", "required-gate", diff --git a/tools/test_audit_red_suite.py b/tools/test_audit_red_suite.py index 1d1b39d..3f8b639 100644 --- a/tools/test_audit_red_suite.py +++ b/tools/test_audit_red_suite.py @@ -4,6 +4,21 @@ from audit_red_suite import parse_log class AuditRedSuiteTests(unittest.TestCase): + def test_parses_result_split_by_captured_output(self) -> None: + audit = parse_log( + """ +test noisy ... diagnostic output +more diagnostic output +ok +test expected_panic ... +thread 'expected_panic' panicked at test.rs:1:1: +expected panic +ok +test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out +""" + ) + self.assertEqual(audit.passed_names, ("noisy", "expected_panic")) + def test_associates_standardized_boundaries_with_failed_tests(self) -> None: audit = parse_log( """