//! Plain (non-buffered) subscribe live diagnostic for F49 / F56. //! //! Mirror of `buffered_subscribe_live.rs` but invokes //! `Session::subscribe` instead of `subscribe_buffered`. Used to //! isolate whether F56's "no DataUpdate" symptom is buffered-specific //! (only `subscribe_buffered` broken) or affects all subscribe paths. #![allow( clippy::unwrap_used, clippy::expect_used, clippy::indexing_slicing, clippy::panic )] #[cfg(all(windows, feature = "live-windows-com"))] mod live { use std::sync::Arc; use std::time::{Duration, Instant}; use futures_util::StreamExt; use mxaccess::{MxValue, RecoveryPolicy, Session, SessionOptions}; use mxaccess_galaxy::SqlTagResolver; use mxaccess_rpc::ntlm::NtlmClientContext; fn ntlm_from_test_env() -> NtlmClientContext { let user = std::env::var("MX_TEST_USER").expect("MX_TEST_USER"); let password = std::env::var("MX_TEST_PASSWORD").expect("MX_TEST_PASSWORD"); let domain = std::env::var("MX_TEST_DOMAIN").unwrap_or_default(); let hostname = std::env::var("COMPUTERNAME").unwrap_or_default(); NtlmClientContext::new(&user, &password, &domain, Some(&hostname)) } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] #[ignore] async fn plain_subscribe_yields_updates() { if std::env::var_os("MX_LIVE").is_none() { eprintln!("MX_LIVE not set — skipping live test"); return; } let tag = std::env::var("MX_TEST_TAG") .unwrap_or_else(|_| "TestChildObject.TestInt".to_string()); let _ = tracing_subscriber::fmt() .with_env_filter( tracing_subscriber::EnvFilter::try_from_default_env() .unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info")), ) .with_test_writer() .try_init(); let galaxy_db = std::env::var("MX_GALAXY_DB").expect("MX_GALAXY_DB"); let resolver = Arc::new( SqlTagResolver::from_ado_string(&galaxy_db).expect("SqlTagResolver"), ); let session = Session::connect_nmx_auto( ntlm_from_test_env, SessionOptions::default(), resolver, RecoveryPolicy::default(), ) .await .expect("connect_nmx_auto"); eprintln!("session connected"); let mut sub = session.subscribe(&tag).await.expect("subscribe"); eprintln!("plain subscribe correlation_id = {:02x?}", sub.correlation_id()); // Background writer to force value changes. let deadline = Instant::now() + Duration::from_secs(20); let writer_session = session.clone(); let writer_tag = tag.clone(); let writer_stop = Arc::new(std::sync::atomic::AtomicBool::new(false)); let writer_stop_clone = writer_stop.clone(); let writer = tokio::spawn(async move { let mut value: i32 = 2_000; while !writer_stop_clone.load(std::sync::atomic::Ordering::Acquire) { if writer_session .write(&writer_tag, MxValue::Int32(value)) .await .is_err() { break; } value = value.wrapping_add(1); tokio::time::sleep(Duration::from_millis(500)).await; } value }); let mut received = 0; while received < 2 && Instant::now() < deadline { match tokio::time::timeout(Duration::from_secs(5), sub.next()).await { Ok(Some(Ok(dc))) => { eprintln!("[{received}] {} = {:?} ts={:?}", dc.reference, dc.value, dc.timestamp); received += 1; } Ok(Some(Err(e))) => { writer_stop.store(true, std::sync::atomic::Ordering::Release); let _ = writer.await; panic!("subscription error: {e}"); } Ok(None) => break, Err(_) => eprintln!("5s gap waiting for next update"), } } writer_stop.store(true, std::sync::atomic::Ordering::Release); let _ = writer.await; assert!(received >= 1, "no DataChange arrived for plain subscribe"); eprintln!("received {received} updates via plain subscribe"); session.unsubscribe(sub).await.expect("unsubscribe"); session.shutdown_nmx().await.expect("shutdown"); } } #[cfg(not(all(windows, feature = "live-windows-com")))] mod live { #[test] #[ignore] fn plain_subscribe_yields_updates() { eprintln!("test skipped: requires Windows + live-windows-com feature"); } }