main
Drew Owens 0.6.9 source: launch resilience, honest counts, ownership persistence, drain, quit ladder — 21 adversary passes, 303 te… cf16621 1mo ago
//! ADVERSARIAL REVIEW OF 0.5.7 — pass eleven.
//!
//! Pass ten's three findings are closed at the mechanism. `done` now requires
//! `walk_stop.complete() && scanned_all && collisions.is_empty() &&
//! close_failed.is_empty()`, so a walk that stopped at its scan cap or on an
//! invalidated iteration can no longer report a finished drain. `walkStop`,
//! `titleCollisionsCheckedOver`, `closedDocs`, `closeFailed`, `titleCollisions`,
//! `closeIgnoredPath` and `wrongDocumentClosed` are all forwarded to the caller.
//! The mock's drain is now window-bounded (`.take(window)`, `walk_complete =
//! window >= self.docs.len()`), so the window properties are testable at last.
//! And `mock-closedoc-ignores-path` exists, so the capability case has a fixture.
//!
//! One branch of the new discriminator has no fixture, and it is the branch that
//! decides whether a user's document was closed.

mod common;

use common::{Bridge, TempDir};
use serde_json::{json, Value};

fn s(v: &Value) -> String {
    serde_json::to_string_pretty(v).unwrap_or_default()
}

/// THE DISCRIMINATOR'S THIRD CASE HAS NO FIXTURE.
///
/// When `CloseDoc(path)` returns success and the document is still open, 0.5.7
/// asks whether the open-document count moved:
///
/// ```text
/// let nothing_happened = match (expected, now) {
///     (Some(e), Some(n)) => n >= e,
///     _ => true,                      // <-- an UNREADABLE count
/// };
/// ```
///
/// An unreadable count takes the BENIGN branch: `close_by_path_unsupported`, and
/// a `why` that states as fact that "the call did nothing". But the count can
/// become unreadable BECAUSE something large just changed — closing the wrong
/// document (a big assembly) is exactly the kind of event that makes
/// `GetDocumentCount` stop answering. In that case the drain reports a
/// capability problem and accuses nobody, when the truth is that a document may
/// have been closed and nobody can say which.
///
/// Everywhere else in this bridge an unknown refuses and SAYS unknown. Here it
/// picks the reassuring answer. There should be a third outcome — stop, and
/// report the outcome as UNDETERMINED with the paths to check — and it needs a
/// fixture before it can exist: the mock's drain reads its count from a `Vec`
/// length, so `mock-doccount-none` does not reach it and the `_ => true` arm is
/// dead code as far as the suite is concerned.
#[test]
fn the_drain_can_model_an_unreadable_count_during_a_close() {
    let scratch = TempDir::new("adv11-scratch");
    let root = r"C:\Users\drew\AppData\Local\Temp";
    std::fs::write(
        scratch.join("mock-solidworks-open-docs.json"),
        json!({
            "running": true, "untitled": 1, "active": "untitled:t0",
            "docs": [{
                "docType": "part", "path": "untitled:t0", "title": "First.step.SLDPRT",
                "saved": false, "importTemp": true,
                "tempPath": format!("{root}\\swx107688\\IC~~\\First.step.SLDPRT")
            }],
            "files": {}
        })
        .to_string(),
    )
    .unwrap();
    // CloseDoc ignores the path form (the capability case) AND the count cannot
    // be read — the two together are the arm with no coverage.
    std::fs::write(scratch.join("mock-closedoc-ignores-path"), "").unwrap();
    std::fs::write(scratch.join("mock-doccount-none"), "").unwrap();

    let b = Bridge::start_in(TempDir::borrowed(&scratch.0), &[]);
    b.call("swx_start", json!({"reason": "draining"}));
    let r = b.call("swx_close_import_temps", json!({"reason": "draining leaked temps", "batch": 1}));

    assert!(
        r["documentCountBefore"].is_null(),
        "`mock-doccount-none` is set and the drain still reported a document count, because the \
         mock reads it from a Vec length rather than through the same unreadable-count path the \
         real backend has. The `_ => true` arm of the discriminator — an UNREADABLE count taken \
         as 'the call did nothing' — therefore has no fixture, and it is the arm that decides \
         between a capability problem and a user's document having been closed.\n{}",
        s(&r)
    );
}

/// Pass ten's `done` rule, pinned. A walk that stopped at its scan cap must not
/// report a finished drain even when every victim it FOUND was closed. This
/// passes on 0.5.7 and is here so the rule cannot quietly regress.
#[test]
fn done_still_requires_a_walk_that_reached_the_end() {
    let scratch = TempDir::new("adv11-window");
    let root = r"C:\Users\drew\AppData\Local\Temp";
    let mut docs: Vec<Value> = vec![json!({
        "docType": "part", "path": "untitled:t0", "title": "First.step.SLDPRT",
        "saved": false, "importTemp": true,
        "tempPath": format!("{root}\\swx107688\\IC~~\\First.step.SLDPRT")
    })];
    for i in 0..58 {
        docs.push(json!({
            "docType": "part", "path": format!("C:\\work\\P{i}.SLDPRT"),
            "title": format!("P{i}.SLDPRT"), "saved": true, "importTemp": false
        }));
    }
    docs.push(json!({
        "docType": "part", "path": "untitled:t60", "title": "Far.step.SLDPRT",
        "saved": false, "importTemp": true,
        "tempPath": format!("{root}\\swx107688\\IC~~\\Far.step.SLDPRT")
    }));
    std::fs::write(
        scratch.join("mock-solidworks-open-docs.json"),
        json!({"running": true, "untitled": 2, "active": "untitled:t0",
               "docs": docs, "files": {}})
        .to_string(),
    )
    .unwrap();

    let b = Bridge::start_in(TempDir::borrowed(&scratch.0), &[]);
    b.call("swx_start", json!({"reason": "draining"}));
    let r = b.call("swx_close_import_temps", json!({"reason": "draining", "batch": 5}));
    let scanned = r["documentsScanned"].as_u64().unwrap_or(0);
    let before = r["documentCountBefore"].as_u64().unwrap_or(0);
    assert!(before > scanned, "precondition: the walk was window-limited: {}", s(&r));
    assert!(
        r["done"] != true,
        "done:true after scanning {scanned} of {before}: {}",
        s(&r)
    );
}