wdext 0.1.0

A DbgEng wrapper framework
// SPDX-FileCopyrightText: 2026 takubokudori
// SPDX-License-Identifier: MIT OR Apache-2.0
#[cfg(test)]
pub mod tests {
    use crate::tests::util::local_debug2;
    use once_cell::sync::Lazy;
    use std::{
        cell::{Cell, RefCell},
        rc::Rc,
        sync::Mutex,
    };
    use wdext::{
        callbacks::WdCallbacksErrorPolicy,
        data::{DebugExecutionFlags, DebugOutctlFlags, DebugOutputFlags},
        dbgeng::{
            DebugOutCbText, DebugOutCbfFlags,
            callbacks::DebugOutputCallbacksHandler,
        },
    };

    mod util;

    static GLOBAL_COUNT: Lazy<Mutex<usize>> = Lazy::new(|| Mutex::new(0));

    pub struct TestOutputCallbacks {
        output_count: Cell<usize>,
        expected_mask: Cell<DebugOutputFlags>,
        expected_text: RefCell<String>,
        expected_global_count: Cell<usize>,
    }

    impl DebugOutputCallbacksHandler for TestOutputCallbacks {
        fn output2(
            &self,
            _flags: DebugOutCbfFlags,
            arg: DebugOutputFlags,
            text: &DebugOutCbText,
        ) -> windows::core::Result<()> {
            let text = text.get_text();
            if text.is_none() {
                return Ok(());
            }
            let text = text.unwrap();
            self.output_count.set(self.output_count.get() + 1);
            {
                let cnt = {
                    let mut cnt = GLOBAL_COUNT.lock().unwrap();
                    *cnt += 1;
                    *cnt
                };
                assert_eq!(cnt, self.expected_global_count.get());
            }
            assert_eq!(arg, self.expected_mask.get());
            assert_eq!(text, self.expected_text.borrow().as_str());
            Ok(())
        }
    }

    pub struct PanicTestOutputCallbacks;

    impl DebugOutputCallbacksHandler for PanicTestOutputCallbacks {
        fn output2(
            &self,
            _flags: DebugOutCbfFlags,
            _arg: DebugOutputFlags,
            _text: &DebugOutCbText,
        ) -> windows::core::Result<()> {
            panic!("OutputCallbacks panic");
        }
    }

    #[test]
    fn test_output_callbacks() {
        let ctx = local_debug2();
        let client = ctx.client();
        let control = ctx.control();
        {
            let callbacks1 = Rc::new(TestOutputCallbacks {
                output_count: Cell::new(0),
                expected_mask: Cell::new(DebugOutputFlags::Normal),
                expected_text: RefCell::new(".echo test\ntest\n".to_string()),
                expected_global_count: Cell::new(1),
            });

            assert!(client.get_output_callbacks().unwrap().is_none());
            let _lock1 = client
                .set_forward_output_callbacks(
                    callbacks1.clone(),
                    WdCallbacksErrorPolicy::BackwardError,
                )
                .unwrap();
            assert!(client.get_output_callbacks().unwrap().is_some());

            assert_eq!(callbacks1.output_count.get(), 0);
            control
                .execute(
                    DebugOutctlFlags::ThisClient,
                    ".echo test",
                    DebugExecutionFlags::Echo,
                )
                .unwrap();
            assert_eq!(callbacks1.output_count.get(), 1);

            let callbacks2 = Rc::new(TestOutputCallbacks {
                output_count: Cell::new(0),
                expected_mask: Cell::new(DebugOutputFlags::Normal),
                expected_text: RefCell::new(".echo test\ntest\n".to_string()),
                expected_global_count: Cell::new(1),
            });

            let _lock2 = client
                .set_forward_output_callbacks(
                    callbacks2.clone(),
                    WdCallbacksErrorPolicy::BackwardError,
                )
                .unwrap();
            assert_eq!(callbacks1.output_count.get(), 1);
            assert_eq!(callbacks2.output_count.get(), 0);

            // 2 -> 1
            callbacks2.expected_global_count.set(2);
            callbacks1.expected_global_count.set(3);

            control
                .execute(
                    DebugOutctlFlags::ThisClient,
                    ".echo test",
                    DebugExecutionFlags::Echo,
                )
                .unwrap();
            assert_eq!(callbacks1.output_count.get(), 2);
            assert_eq!(callbacks2.output_count.get(), 1);

            let callbacks3 = Rc::new(TestOutputCallbacks {
                output_count: Cell::new(0),
                expected_mask: Cell::new(DebugOutputFlags::Normal),
                expected_text: RefCell::new(".echo test\ntest\n".to_string()),
                expected_global_count: Cell::new(3),
            });

            {
                let _lock3 = client
                    .set_backward_output_callbacks(
                        callbacks3.clone(),
                        WdCallbacksErrorPolicy::BackwardError,
                    )
                    .unwrap();
                assert_eq!(callbacks1.output_count.get(), 2);
                assert_eq!(callbacks2.output_count.get(), 1);
                assert_eq!(callbacks3.output_count.get(), 0);

                // 2 -> 1 -> 3
                callbacks2.expected_global_count.set(4);
                callbacks1.expected_global_count.set(5);
                callbacks3.expected_global_count.set(6);
                control
                    .execute(
                        DebugOutctlFlags::ThisClient,
                        ".echo test",
                        DebugExecutionFlags::Echo,
                    )
                    .unwrap();
                assert_eq!(callbacks1.output_count.get(), 3);
                assert_eq!(callbacks2.output_count.get(), 2);
                assert_eq!(callbacks3.output_count.get(), 1);
            }
            // The `execute` call here cannot acquire the `GLOBAL_COUNT` lock.
            callbacks2.expected_global_count.set(7);
            callbacks1.expected_global_count.set(8);
            control
                .execute(
                    DebugOutctlFlags::ThisClient,
                    ".echo test",
                    DebugExecutionFlags::Echo,
                )
                .unwrap();
            assert_eq!(callbacks1.output_count.get(), 4);
            assert_eq!(callbacks2.output_count.get(), 3);
            assert_eq!(callbacks3.output_count.get(), 1);
        }

        // Verify that it continues to work correctly after being dropped.
        control
            .execute(
                DebugOutctlFlags::ThisClient,
                ".echo test",
                DebugExecutionFlags::Echo,
            )
            .unwrap();

        let callbacks4 = Rc::new(PanicTestOutputCallbacks);
        let _lock4 = client
            .set_forward_output_callbacks(
                callbacks4.clone(),
                WdCallbacksErrorPolicy::BackwardError,
            )
            .unwrap();
        control
            .execute(
                DebugOutctlFlags::ThisClient,
                ".echo test",
                DebugExecutionFlags::Echo,
            )
            .unwrap();
    }
}