app
SOLIDWORKS Bridge
Public Made by Adomby adom
Launch, model and quit SOLIDWORKS from any Adom container. Nothing is ever lost.
main
Drew Owens
0.9.0: bridge data moved out of the install cache (recovery survives updates), closed-in-place records, gates route thr…
5d02ca9
20d ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
//! ADVERSARIAL REVIEW OF 0.7.0 — pass twenty-two, the data-loss surface.
//!
//! The gates around the sweep are sound. `done = count_after == Some(0) &&
//! failed.is_empty()`, so a document that could not be saved is never closed,
//! keeps the count above zero, and forces `done:false`; an UNREADABLE count is
//! not `Some(0)` either. `swx_shutdown` attempts the quit only on
//! `success && done`, and `swx_stop`'s own `check_preconditions` is a second,
//! independent gate that refuses on a truncated count and on any dialog. Two
//! gates, neither derived from the other — that is the right shape for this.
//!
//! The saving itself is where the risk is.
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 RECOVERY WRITE IS VERIFIED BY EXISTENCE, NOT BY SIZE.
///
/// ```text
/// let on_disk = code == 0
/// && !now_path.trim().is_empty()
/// && std::path::Path::new(&now_path).exists();
/// ```
///
/// A file that exists and is EMPTY passes, the record says `preserved: true`,
/// and the document is then closed — discarding the only copy of the user's
/// unsaved work. A partial or zero-byte write is not a hypothetical here: the
/// machine this verb is meant to run on is down to single-digit gigabytes, and
/// SOLIDWORKS assemblies are large.
///
/// The idiom already exists in this file — `std::fs::metadata(out).map(|m| m.len())`
/// is computed for the export path — so the fix is to carry it here, refuse a
/// zero-length result, and REPORT the size so an operator can tell a real save
/// from an empty one.
///
/// I could not stage the partial write: the mock writes a fixed non-empty
/// string and its own check is `fs::write(..).is_err()`, which is STRONGER than
/// the real backend's. So this test asks for the reportable half — the size of
/// what was written — which is the minimum that makes the outcome auditable and
/// is a precondition for checking it at all.
#[test]
fn every_recovery_record_reports_the_size_it_wrote() {
let b = Bridge::start_with(&[("SWX_ALLOW_USER_WRITES", "1")]);
b.call("swx_start", json!({"reason": "preparing to close everything"}));
let made = b.call("swx_new_document", json!({"type": "part", "keepUnsaved": true}));
assert!(made["success"] == true, "precondition: a dirty pathless document exists: {}", s(&made));
let r = b.call(
"swx_close_all_safely",
json!({"reason": "closing everything safely before a quit", "userAuthorized": true}),
);
let recs = r["recovered"]
.as_array()
.cloned()
.or_else(|| r["records"].as_array().cloned())
.unwrap_or_default();
assert!(
!recs.is_empty(),
"precondition: the sweep preserved at least one document: {}",
s(&r)
);
let preserved: Vec<&Value> = recs
.iter()
.filter(|x| x.get("preserved").and_then(|p| p.as_bool()) == Some(true))
.collect();
assert!(!preserved.is_empty(), "precondition: something was preserved: {}", s(&r));
for rec in preserved {
let has_size = rec.get("bytes").and_then(|v| v.as_u64()).is_some()
|| rec.get("recoveryBytes").and_then(|v| v.as_u64()).is_some()
|| rec.get("size").and_then(|v| v.as_u64()).is_some();
assert!(
has_size,
"a record says preserved:true and does not say how many bytes reached disk. The \
check behind it is `Path::exists()`, so a zero-byte file — an interrupted write on a \
nearly-full disk — passes it, and the document is then CLOSED. Size is the only \
thing that distinguishes a save from a file that merely appeared.\nrecord: {}\n{}",
s(rec),
s(&r)
);
}
}
/// THE USER'S OWN FILE IS NEVER TOUCHED — the invariant Drew actually stated.
///
/// A dirty document that already has a path is written into the recovery folder
/// under a new name; the file the user knows about must be byte-for-byte what it
/// was. This passes, and it is here because it is the one property that must
/// never regress on this surface.
///
/// Worth recording alongside it: `save_as3(doc, out, copy=false)` resolves to
/// `options = 1`, which is `swSaveAsOptions_Silent` ALONE — not Copy, which is
/// 2. The module header describes this call as "silent, copy-mode", and that is
/// wrong. The effect is a RE-POINT: the open document becomes the recovery file.
/// Nothing is overwritten, so the invariant holds — but the sweep then reads
/// `GetPathName` AFTER the save to get its close key, and that is correct only
/// BECAUSE it re-points. If someone later makes the flag match the comment and
/// passes Copy, `GetPathName` would still return the USER'S ORIGINAL path and
/// the close would be aimed at their file. The comment and the code must be made
/// to agree, in that direction.
#[test]
fn a_users_saved_file_is_byte_for_byte_unchanged_by_the_sweep() {
let work = TempDir::new("adv21-userwork");
let users_file = work.join("Bracket.SLDPRT");
let original = b"the user's own work, saved last Tuesday";
std::fs::write(&users_file, original).unwrap();
let before = std::fs::read(&users_file).unwrap();
let b = Bridge::start_with(&[("SWX_ALLOW_USER_WRITES", "1")]);
b.call("swx_start", json!({"reason": "preparing"}));
b.call(
"swx_open",
json!({"path": users_file.to_string_lossy(), "reason": "the user had this open"}),
);
b.call(
"swx_close_all_safely",
json!({"reason": "closing everything safely before a quit", "userAuthorized": true}),
);
let after = std::fs::read(&users_file).unwrap_or_default();
assert_eq!(
after, before,
"the sweep modified a file the USER owns at {}. A dirty saved document must be written \
into recovery under a new name; the file they know about is never the destination.",
users_file.display()
);
assert!(
!after.is_empty(),
"the user's file at {} is now empty",
users_file.display()
);
}
/// "ANY DIALOG STOPS THE SWEEP" — BUT AN UNREADABLE CENSUS DOES NOT.
///
/// The sweep reports `dialogCensusReadable: false` and `stoppedByDialog: null`,
/// and proceeds to close every document anyway. So the rule is really "any
/// dialog I could SEE stops the sweep", and when the desktop cannot be
/// enumerated the sweep runs blind.
///
/// Everywhere else in this bridge an unreadable precondition refuses — the quit
/// ladder's P3 does exactly that ("the window census could not be taken... a
/// quit sent at a modal is a different act"). This is the same act, on the same
/// desktop, one verb earlier, and it is the verb that CLOSES things. A "Save
/// changes to Part1?" raised mid-sweep by SOLIDWORKS itself is precisely what
/// would be missed.
///
/// The recovery save runs first, so this is not by itself a loss of data — it is
/// the loss of the guard that was claimed. On the live session it also means the
/// sweep cannot notice the one dialog it most needs to.
#[test]
fn the_sweep_does_not_close_documents_with_the_dialog_census_unreadable() {
let b = Bridge::start_with(&[("SWX_ALLOW_USER_WRITES", "1")]);
b.call("swx_start", json!({"reason": "preparing"}));
b.call("swx_new_document", json!({"type": "part", "keepUnsaved": true}));
let r = b.call(
"swx_close_all_safely",
json!({"reason": "closing everything safely before a quit", "userAuthorized": true}),
);
let readable = r["dialogCensusReadable"].as_bool();
if readable != Some(false) {
return; // the census was readable; nothing to prove here.
}
assert_eq!(
r["closed"].as_u64().unwrap_or(0),
0,
"the dialog census could not be read (dialogCensusReadable:false) and the sweep closed {} \
document(s) anyway. The verb's own contract is that any dialog stops it; with the \
desktop unreadable it cannot know whether one is up. The quit ladder refuses this exact \
unknown one verb later — this is the same act on the same desktop, and it is the one \
that does the closing.\n{}",
r["closed"],
s(&r)
);
}