app
Fusion - the Fusion 360 Bridge
Public Made by Adomby adom
Drive Autodesk Fusion 360 from the cloud via Adom Bridge: component libraries, IPC package generation, board layout, exports (STEP/Gerbers/BOM/CPL), fast APS cloud search, and parametric modeling.
master
Drew Owens
Share frozen native Rust mechanical development checkpoint and qualification scope
b2d7cdb
26d ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335
use super::*;
use std::sync::{Arc, Barrier};
pub(super) struct Directory(pub(super) PathBuf);
impl Directory {
pub(super) fn new() -> Self {
let stage = Staging::create(&std::env::temp_dir()).unwrap();
Self(stage.directory)
}
fn file(&self) -> PathBuf {
self.0.join("bom.csv")
}
fn entries(&self) -> usize {
fs::read_dir(&self.0).unwrap().count()
}
}
impl Drop for Directory {
fn drop(&mut self) {
fs::remove_dir_all(&self.0).unwrap();
}
}
fn injected() -> io::Error {
io::Error::other("injected filesystem failure")
}
#[test]
fn new_csv_has_exact_bytes_size_hash_and_no_staging_left() {
let dir = Directory::new();
let bytes = b"part,quantity\r\nbolt,4\r\n";
let result = publish_csv(&dir.file(), bytes, false).unwrap();
assert!(result.published);
assert!(!result.overwrite);
assert_eq!(result.output_path, fs::canonicalize(&dir.file()).unwrap());
assert_eq!(result.file_size, bytes.len());
assert_eq!(result.sha256, format!("{:x}", Sha256::digest(bytes)));
assert!(result.cleanup_warning.is_none());
assert_eq!(fs::read(dir.file()).unwrap(), bytes);
assert_eq!(dir.entries(), 1);
}
#[test]
fn existing_destination_requires_explicit_replace() {
let dir = Directory::new();
fs::write(dir.file(), b"original").unwrap();
let err = publish_csv(&dir.file(), b"replacement", false).unwrap_err();
assert_eq!(err.source.kind(), io::ErrorKind::AlreadyExists);
assert_eq!(fs::read(dir.file()).unwrap(), b"original");
assert_eq!(dir.entries(), 1);
let result = publish_csv(&dir.file(), b"replacement", true).unwrap();
assert!(result.published && result.overwrite);
assert_eq!(fs::read(dir.file()).unwrap(), b"replacement");
assert_eq!(dir.entries(), 1);
}
#[test]
fn replacement_does_not_mutate_another_hardlink_to_old_file() {
let dir = Directory::new();
fs::write(dir.file(), b"original").unwrap();
let link = dir.0.join("old.csv");
fs::hard_link(dir.file(), &link).unwrap();
publish_csv(&dir.file(), b"replacement", true).unwrap();
assert_eq!(fs::read(link).unwrap(), b"original");
assert_eq!(fs::read(dir.file()).unwrap(), b"replacement");
}
#[test]
fn nonempty_and_size_bounds_apply_before_parent_creation() {
let dir = Directory::new();
let output = dir.0.join("absent/bom.csv");
assert!(publish_csv(&output, b"", false).is_err());
assert!(publish_csv(&output, &vec![b'x'; MAX_BYTES + 1], false).is_err());
assert_eq!(dir.entries(), 0);
publish_csv(&output, &vec![b'x'; MAX_BYTES], false).unwrap();
assert_eq!(fs::metadata(output).unwrap().len(), MAX_BYTES as u64);
}
#[test]
fn csv_requires_utf8_and_matching_extension_without_filesystem_changes() {
let dir = Directory::new();
assert!(publish_csv(&dir.file(), &[0xff], false).is_err());
assert!(publish_csv(&dir.0.join("part.step"), b"x", false).is_err());
assert_eq!(dir.entries(), 0);
publish_csv(
&dir.0.join("pièce.CSV"),
"名,数\r\n螺栓,3\r\n".as_bytes(),
false,
)
.unwrap();
assert_eq!(dir.entries(), 1);
}
#[test]
fn relative_directory_and_parent_traversal_destinations_are_refused() {
let dir = Directory::new();
assert!(publish_bytes(Path::new("bom.csv"), b"x", false).is_err());
assert!(publish_bytes(&dir.0, b"x", true).is_err());
assert!(publish_bytes(&dir.0.join("inner/../bom.csv"), b"x", true).is_err());
assert_eq!(dir.entries(), 0);
}
#[test]
fn file_as_parent_is_refused_and_preserved() {
let dir = Directory::new();
let parent = dir.0.join("parent");
fs::write(&parent, b"original").unwrap();
assert!(publish_csv(&parent.join("bom.csv"), b"x", false).is_err());
assert_eq!(fs::read(parent).unwrap(), b"original");
assert_eq!(dir.entries(), 1);
}
#[test]
fn commit_errors_preserve_unknown_outcome_while_preflight_is_not_attempted() {
let dir = Directory::new();
let err = publish_with(&dir.file(), b"ours", false, |point, stage| {
if point == Checkpoint::BeforeCommit {
fs::remove_file(stage)?;
}
Ok(())
})
.unwrap_err();
assert_eq!(err.stage, "commit_exclusive");
assert_eq!(err.publication, PublicationState::Unknown);
assert!(!dir.file().exists());
assert_eq!(dir.entries(), 0);
assert_eq!(
publish_bytes(&dir.file(), b"", false)
.unwrap_err()
.publication,
PublicationState::NotAttempted
);
}
#[test]
fn concurrent_exclusive_writers_have_exactly_one_winner_and_whole_payload() {
let dir = Directory::new();
let gate = Arc::new(Barrier::new(8));
let mut handles = vec![];
for i in 0..8 {
let output = dir.file();
let gate = gate.clone();
handles.push(std::thread::spawn(move || {
let bytes = vec![b'0' + i; 65_537];
let result = publish_with(&output, &bytes, false, |point, _| {
if point == Checkpoint::BeforeCommit {
gate.wait();
}
Ok(())
});
(i, result)
}));
}
let results = handles
.into_iter()
.map(|h| h.join().unwrap())
.collect::<Vec<_>>();
assert_eq!(results.iter().filter(|(_, r)| r.is_ok()).count(), 1);
let winner = results.iter().find(|(_, r)| r.is_ok()).unwrap().0;
assert_eq!(fs::read(dir.file()).unwrap(), vec![b'0' + winner; 65_537]);
assert!(results
.iter()
.filter_map(|(_, r)| r.as_ref().err())
.all(|e| e.source.kind() == io::ErrorKind::AlreadyExists));
assert_eq!(dir.entries(), 1);
}
#[test]
fn late_destination_creation_cannot_be_overwritten_by_exclusive_publish() {
let dir = Directory::new();
let output = dir.file();
let err = publish_with(&output, b"ours", false, |point, _| {
if point == Checkpoint::BeforeCommit {
fs::write(&output, b"other writer")?;
}
Ok(())
})
.unwrap_err();
assert_eq!(err.source.kind(), io::ErrorKind::AlreadyExists);
assert_eq!(fs::read(&output).unwrap(), b"other writer");
assert_eq!(dir.entries(), 1);
}
#[test]
fn failure_before_write_or_after_full_staging_preserves_destination() {
for fail_at in [Checkpoint::BeforeWrite, Checkpoint::BeforeCommit] {
let dir = Directory::new();
fs::write(dir.file(), b"original").unwrap();
let error = publish_with(&dir.file(), b"ours", true, |point, _| {
if point == fail_at {
Err(injected())
} else {
Ok(())
}
})
.unwrap_err();
assert!(error.cleanup_warning.is_none());
assert_eq!(fs::read(dir.file()).unwrap(), b"original");
assert_eq!(dir.entries(), 1);
}
}
#[test]
fn directory_created_at_destination_prevents_commit_and_is_not_removed() {
let dir = Directory::new();
let output = dir.file();
assert!(publish_with(&output, b"ours", true, |point, _| {
if point == Checkpoint::BeforeCommit {
fs::create_dir(&output)?;
fs::write(output.join("keep"), b"user")?;
}
Ok(())
})
.is_err());
assert_eq!(fs::read(output.join("keep")).unwrap(), b"user");
assert_eq!(dir.entries(), 1);
}
#[test]
fn post_commit_failure_is_success_with_warning_not_a_retryable_failure() {
let dir = Directory::new();
let result = publish_with(&dir.file(), b"committed", false, |point, _| {
if point == Checkpoint::AfterCommit {
Err(injected())
} else {
Ok(())
}
})
.unwrap();
assert!(result.published);
assert!(result.cleanup_warning.unwrap().contains("injected"));
assert_eq!(fs::read(dir.file()).unwrap(), b"committed");
assert_eq!(dir.entries(), 1);
}
#[test]
fn unexpected_staging_child_is_retained_and_cannot_mask_success() {
let dir = Directory::new();
let mut other = None;
let result = publish_with(&dir.file(), b"committed", false, |point, stage| {
if point == Checkpoint::AfterCommit {
let path = stage.parent().unwrap().join("unowned");
fs::write(&path, b"keep")?;
other = Some(path);
}
Ok(())
})
.unwrap();
assert!(result.published && result.cleanup_warning.is_some());
assert_eq!(fs::read(other.unwrap()).unwrap(), b"keep");
assert_eq!(fs::read(dir.file()).unwrap(), b"committed");
}
#[test]
fn cleanup_failure_before_commit_is_reported_with_original_failure() {
let dir = Directory::new();
let mut other = None;
let result = publish_with(&dir.file(), b"not committed", false, |point, stage| {
if point == Checkpoint::BeforeCommit {
let path = stage.parent().unwrap().join("unowned");
fs::write(&path, b"keep")?;
other = Some(path);
return Err(injected());
}
Ok(())
})
.unwrap_err();
assert_eq!(result.stage, "before_commit");
assert!(result.cleanup_warning.is_some());
assert!(!dir.file().exists());
assert_eq!(fs::read(other.unwrap()).unwrap(), b"keep");
}
#[cfg(unix)]
#[test]
fn existing_and_dangling_symlinks_are_refused_without_following_them() {
use std::os::unix::fs::symlink;
let dir = Directory::new();
let target = dir.0.join("target.csv");
fs::write(&target, b"original").unwrap();
symlink(&target, dir.file()).unwrap();
assert!(publish_csv(&dir.file(), b"ours", true).is_err());
assert_eq!(fs::read(&target).unwrap(), b"original");
fs::remove_file(&target).unwrap();
assert!(publish_csv(&dir.file(), b"ours", true).is_err());
assert!(!target.exists());
assert!(fs::symlink_metadata(dir.file()).unwrap().is_symlink());
}
#[cfg(windows)]
#[test]
fn windows_device_stream_and_ambiguous_names_are_refused_before_io() {
for path in [
r"C:\out\CON.csv",
r"C:\out\NUL.csv",
r"C:\out\COM1.csv",
r"C:\out\CON .csv",
r"C:\out\LPT1 .csv",
r"C:\out\CONIN$",
r"C:\out\CONOUT$",
r"C:\out\LPT².csv",
r"C:\out\file:stream.csv",
r"C:\out\bom.csv.",
r"\\?\C:\out\bom.csv",
r"\\.\C:\out\bom.csv",
r"C:\out\file?.csv",
r"C:\out\file*.csv",
r"C:\out\file|.csv",
r"C:\out\file<.csv",
] {
assert_eq!(
publish_bytes(Path::new(path), b"x", false)
.unwrap_err()
.stage,
"preflight",
"{path}"
);
}
}
#[cfg(windows)]
#[test]
fn windows_readonly_destination_remains_intact_on_replace_failure() {
let dir = Directory::new();
fs::write(dir.file(), b"original").unwrap();
let mut perms = fs::metadata(dir.file()).unwrap().permissions();
perms.set_readonly(true);
fs::set_permissions(dir.file(), perms).unwrap();
let result = publish_csv(&dir.file(), b"ours", true);
let actual = fs::read(dir.file()).unwrap();
let mut perms = fs::metadata(dir.file()).unwrap().permissions();
perms.set_readonly(false);
fs::set_permissions(dir.file(), perms).unwrap();
assert!(result.is_err());
assert_eq!(actual, b"original");
assert_eq!(dir.entries(), 1);
}