117 lines
5.0 KiB
Rust
117 lines
5.0 KiB
Rust
//! Original programs exercise cooperative NC processing and its exact boundary limits.
|
|
use solidcraft_cam::{self as cam, Block, Motion, Toolpath, nc};
|
|
use solidcraft_geom::Vec3;
|
|
use std::{
|
|
cell::Cell,
|
|
future::Future,
|
|
task::{Context, Poll, Waker},
|
|
};
|
|
|
|
fn yield_once() -> impl Future<Output = ()> {
|
|
let mut first = true;
|
|
std::future::poll_fn(move |_| if std::mem::take(&mut first) { Poll::Pending } else { Poll::Ready(()) })
|
|
}
|
|
fn drive<T>(future: impl Future<Output = cam::Result<T>>) -> (cam::Result<T>, usize) {
|
|
let mut future = std::pin::pin!(future);
|
|
let mut polls = 0;
|
|
loop {
|
|
polls += 1;
|
|
assert!(polls < 20_000, "unbounded checkpoint count");
|
|
if let Poll::Ready(result) = future.as_mut().poll(&mut Context::from_waker(Waker::noop())) {
|
|
return (result, polls);
|
|
}
|
|
}
|
|
}
|
|
fn path(count: usize) -> Toolpath {
|
|
let mut blocks = vec![
|
|
Block { operation: 1, motion: Motion::Tool { tool: 1, number: 1 } },
|
|
Block { operation: 1, motion: Motion::Spindle { rpm: 6000. } },
|
|
Block { operation: 1, motion: Motion::Coolant { on: true } },
|
|
];
|
|
for i in 0..count - 6 {
|
|
blocks.push(Block {
|
|
operation: if i < count / 2 { 1 } else { 2 },
|
|
motion: Motion::Linear { to: Vec3::new(if i % 2 == 0 { 1. } else { 19. }, 10., 5.), feed: 100. },
|
|
});
|
|
}
|
|
blocks.extend([
|
|
Block { operation: 2, motion: Motion::Coolant { on: false } },
|
|
Block { operation: 2, motion: Motion::Spindle { rpm: 0. } },
|
|
Block { operation: 2, motion: Motion::Stop },
|
|
]);
|
|
Toolpath { fingerprint: "original-independent-test".into(), blocks }
|
|
}
|
|
#[test]
|
|
fn maximum_path_includes_startup_records_and_excess_is_rejected() {
|
|
let input = cam::corpus::fixture("face").unwrap();
|
|
let mut path = path(cam::MAX_MOVES);
|
|
let (parsed, polls) = drive(nc::round_trip_cooperative(&path, &input.setup, &input.tools, || false, yield_once));
|
|
let parsed = parsed.unwrap();
|
|
assert_eq!(parsed.records.len(), cam::MAX_MOVES + 3);
|
|
assert!(polls > 3000);
|
|
let last = parsed.records.last().unwrap();
|
|
let source = parsed.source_for_line(last.line).unwrap();
|
|
assert_eq!((source.block, source.operation), (cam::MAX_MOVES, 2));
|
|
path.blocks.push(path.blocks.last().unwrap().clone());
|
|
assert!(nc::post(&path, &input.setup).is_err());
|
|
// External NC records remain bounded independently of the posting limit.
|
|
let mut external = "G21 G90 G17 G94 G54\nG0 X0 Y0 Z15\n".to_owned();
|
|
external.push_str(&"G0 Z15\n".repeat(cam::MAX_MOVES + 3));
|
|
external.push_str("M30\n");
|
|
assert!(nc::parse(&external, &input.tools).unwrap_err().to_string().contains("NC motion limit"));
|
|
}
|
|
#[test]
|
|
fn synchronous_and_cooperative_processing_keep_bytes_state_and_provenance() {
|
|
let input = cam::corpus::fixture("face").unwrap();
|
|
let path = path(1024);
|
|
let source = nc::post(&path, &input.setup).unwrap();
|
|
assert!(source.starts_with("(solidcraft-reference-3axis/2; not a qualified machine post)\n(Fingerprint original-independent-test)\nG21 G90 G17 G91.1 G94 G54 G40 G49 G80\nM5\nM9\nG0 X0.000000 Y0.000000 Z15.000000\n(Operation 1)\nT1 M6\n"));
|
|
assert_eq!(drive(nc::post_cooperative(&path, &input.setup, || false, yield_once)).0.unwrap(), source);
|
|
let synchronous = nc::round_trip(&path, &input.setup, &input.tools).unwrap();
|
|
let cooperative = drive(nc::round_trip_cooperative(&path, &input.setup, &input.tools, || false, yield_once)).0.unwrap();
|
|
assert_eq!(serde_json::to_value(&synchronous).unwrap(), serde_json::to_value(&cooperative).unwrap());
|
|
for record in &synchronous.records {
|
|
assert_eq!(synchronous.source_for_line(record.line), cooperative.source_for_line(record.line));
|
|
}
|
|
let broken = source.replace("(Operation 2)", "G123");
|
|
let ordinary = nc::parse(&broken, &input.tools).unwrap_err();
|
|
let incremental = drive(nc::parse_cooperative(&broken, &input.tools, || false, yield_once)).0.unwrap_err();
|
|
assert_eq!(ordinary.to_string(), incremental.to_string());
|
|
assert!(incremental.to_string().contains("unsupported G code"));
|
|
}
|
|
#[test]
|
|
fn cancellation_during_each_stage_returns_no_partial_program_or_line_mapping() {
|
|
let input = cam::corpus::fixture("face").unwrap();
|
|
let path = path(1024);
|
|
let count = Cell::new(0);
|
|
let full = drive(nc::round_trip_cooperative(
|
|
&path,
|
|
&input.setup,
|
|
&input.tools,
|
|
|| false,
|
|
|| {
|
|
count.set(count.get() + 1);
|
|
yield_once()
|
|
},
|
|
))
|
|
.0
|
|
.unwrap();
|
|
assert_eq!(full.records.len(), 1027);
|
|
// Every checkpoint, including post, reader and correspondence stages.
|
|
for stop in 0..=count.get() {
|
|
let at = Cell::new(0);
|
|
let result = drive(nc::round_trip_cooperative(
|
|
&path,
|
|
&input.setup,
|
|
&input.tools,
|
|
|| at.get() >= stop,
|
|
|| {
|
|
at.set(at.get() + 1);
|
|
yield_once()
|
|
},
|
|
))
|
|
.0;
|
|
assert!(result.unwrap_err().to_string().contains("cancelled"), "checkpoint {stop}");
|
|
}
|
|
}
|