//! `send_network` against a fake printer on 127.0.0.1. //! //! The Python network backend never reads from the socket (helpers.send //! returns right after the write, even with `blocking=True`), so the fake //! printers here check: the exact bytes arrive, the client closes the //! connection on its own (EOF) right after writing, and it does not wait for //! the status replies a real QL printer sends. use std::io::{Read, Write}; use std::net::{Shutdown, TcpListener}; use std::path::Path; use std::sync::mpsc; use std::thread; use std::time::{Duration, Instant}; use brother_ql::{Error, Outcome, SendStatus, send_network}; fn job() -> Vec { // a real two-label job for L0006 as produced by Python brother_ql let p = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/golden/label_L0006_x2.bin"); std::fs::read(p).expect("golden data") } const SENT: SendStatus = SendStatus { instructions_sent: true, outcome: Outcome::Sent, did_print: false, ready_for_next_job: false, }; /// 32-byte QL status reply (80 20 42 ...): model QL-820NWB (0x41), 23 mm /// die-cut media, given status/phase type. fn status_reply(status_type: u8, phase_type: u8) -> [u8; 32] { let mut r = [0u8; 32]; r[0] = 0x80; r[1] = 0x20; r[2] = 0x42; r[3] = 0x34; r[4] = 0x41; r[5] = 0x30; r[10] = 23; // media width r[11] = 0x0B; // die-cut r[17] = 23; // media length r[18] = status_type; r[19] = phase_type; r } #[test] fn sends_exact_bytes_and_closes() { let data = job(); for prefix in ["tcp://", ""] { let listener = TcpListener::bind("127.0.0.1:0").unwrap(); let port = listener.local_addr().unwrap().port(); let server = thread::spawn(move || { let (mut s, _) = listener.accept().unwrap(); let mut got = Vec::new(); s.read_to_end(&mut got).unwrap(); // returns only once the client closed got }); let status = send_network( &format!("{prefix}127.0.0.1:{port}"), &data, true, Duration::from_secs(5), ) .unwrap(); assert_eq!(status, SENT); let got = server.join().unwrap(); assert_eq!(got.len(), data.len()); assert!(got == data, "received bytes differ"); } } #[test] fn blocking_does_not_wait_for_status_replies() { let data = job(); let n = data.len(); let listener = TcpListener::bind("127.0.0.1:0").unwrap(); let port = listener.local_addr().unwrap().port(); let (tx, rx) = mpsc::channel(); let server = thread::spawn(move || { let (mut s, _) = listener.accept().unwrap(); let mut got = vec![0u8; n]; s.read_exact(&mut got).unwrap(); // A real printer answers ESC i S and later reports "printing // completed" (0x01) and "phase change: waiting to receive" (0x06/0x00). // Hold them back for a while: the client must not wait for them. let eof = { let mut buf = [0u8; 1]; s.set_read_timeout(Some(Duration::from_secs(3))).unwrap(); matches!(s.read(&mut buf), Ok(0)) }; tx.send(()).unwrap(); let _ = s.write_all(&status_reply(0x01, 0x00)); let _ = s.write_all(&status_reply(0x06, 0x00)); let _ = s.shutdown(Shutdown::Both); (got, eof) }); let t0 = Instant::now(); let status = send_network( &format!("tcp://127.0.0.1:{port}"), &data, true, Duration::from_secs(5), ) .unwrap(); let elapsed = t0.elapsed(); assert_eq!(status, SENT, "network backend never reports printed/ready"); let (got, eof) = server.join().unwrap(); rx.recv().unwrap(); assert!(got == data); assert!(eof, "client did not close the connection after writing"); assert!( elapsed < Duration::from_secs(2), "send_network waited {elapsed:?}" ); } #[test] fn non_blocking_is_identical() { let data = b"\x1B\x40hello".to_vec(); let listener = TcpListener::bind("127.0.0.1:0").unwrap(); let port = listener.local_addr().unwrap().port(); let server = thread::spawn(move || { let (mut s, _) = listener.accept().unwrap(); let mut got = Vec::new(); s.read_to_end(&mut got).unwrap(); got }); let status = send_network( &format!("127.0.0.1:{port}"), &data, false, Duration::from_secs(5), ) .unwrap(); assert_eq!(status, SENT); assert_eq!(server.join().unwrap(), data); } #[test] fn connection_refused_is_an_error() { let port = { let l = TcpListener::bind("127.0.0.1:0").unwrap(); l.local_addr().unwrap().port() }; let err = send_network( &format!("tcp://127.0.0.1:{port}"), b"x", true, Duration::from_secs(2), ) .unwrap_err(); assert!(matches!(err, Error::Connect { .. }), "{err}"); let err = send_network("tcp://", b"x", true, Duration::from_secs(2)).unwrap_err(); assert!(matches!(err, Error::InvalidAddress(_)), "{err}"); } #[test] fn write_times_out_when_printer_stalls() { // The fake printer accepts but never reads, so the socket buffers fill up. let listener = TcpListener::bind("127.0.0.1:0").unwrap(); let port = listener.local_addr().unwrap().port(); let (done_tx, done_rx) = mpsc::channel::<()>(); let server = thread::spawn(move || { let (s, _) = listener.accept().unwrap(); let _ = done_rx.recv_timeout(Duration::from_secs(30)); drop(s); }); let data = vec![0u8; 64 << 20]; let t0 = Instant::now(); let res = send_network( &format!("127.0.0.1:{port}"), &data, true, Duration::from_millis(500), ); let elapsed = t0.elapsed(); done_tx.send(()).unwrap(); server.join().unwrap(); match res { Err(Error::Write(e)) => assert_eq!(e.kind(), std::io::ErrorKind::TimedOut, "{e}"), other => panic!("expected a write timeout, got {other:?}"), } assert!(elapsed < Duration::from_secs(5), "timeout took {elapsed:?}"); }