windows-threadpool-sys 0.1.1

Memory-safe access to the Windows thread pool APIs.
Documentation
// Copyright (c) 2026 Mike Grier
use core::mem;

use windows_sys::Win32::System::Threading::{
    TP_CALLBACK_ENVIRON_V3, TP_CALLBACK_PRIORITY_HIGH, TP_CALLBACK_PRIORITY_LOW,
    TP_CALLBACK_PRIORITY_NORMAL,
};

use crate::callback_env::{CallbackEnviron, CallbackPriority};

fn inner<'a>(env: &'a CallbackEnviron<'_>) -> &'a TP_CALLBACK_ENVIRON_V3 {
    env.as_inner()
}

// --- new() field defaults (10 normal cases) ---

#[test]
fn new_version_is_3() {
    assert_eq!(
        inner(&CallbackEnviron::new()).Version,
        expected_abi::ENVIRON_VERSION
    );
}

#[test]
fn new_priority_is_normal() {
    assert_eq!(
        inner(&CallbackEnviron::new()).CallbackPriority,
        TP_CALLBACK_PRIORITY_NORMAL,
    );
}

#[test]
fn new_size_is_sizeof_struct() {
    assert_eq!(
        inner(&CallbackEnviron::new()).Size,
        mem::size_of::<TP_CALLBACK_ENVIRON_V3>() as u32,
    );
}

#[test]
fn new_pool_is_zero() {
    assert_eq!(inner(&CallbackEnviron::new()).Pool, 0);
}

#[test]
fn new_cleanup_group_is_zero() {
    assert_eq!(inner(&CallbackEnviron::new()).CleanupGroup, 0);
}

#[test]
fn new_cleanup_group_cancel_callback_is_none() {
    assert!(
        inner(&CallbackEnviron::new())
            .CleanupGroupCancelCallback
            .is_none()
    );
}

#[test]
fn new_race_dll_is_null() {
    assert!(inner(&CallbackEnviron::new()).RaceDll.is_null());
}

#[test]
fn new_activation_context_is_zero() {
    assert_eq!(inner(&CallbackEnviron::new()).ActivationContext, 0);
}

#[test]
fn new_finalization_callback_is_none() {
    assert!(
        inner(&CallbackEnviron::new())
            .FinalizationCallback
            .is_none()
    );
}

#[test]
fn new_flags_are_zero() {
    // SAFETY: Flags and s._bitfield alias the same u32.
    let flags = unsafe { inner(&CallbackEnviron::new()).u.Flags };
    assert_eq!(flags, 0);
}

// --- default() ---

#[test]
fn default_matches_new() {
    let a = CallbackEnviron::new();
    let b = CallbackEnviron::default();
    let (a, b) = (inner(&a), inner(&b));
    assert_eq!(a.Version, b.Version);
    assert_eq!(a.Pool, b.Pool);
    assert_eq!(a.CleanupGroup, b.CleanupGroup);
    assert_eq!(a.CallbackPriority, b.CallbackPriority);
    assert_eq!(a.Size, b.Size);
    assert_eq!(a.ActivationContext, b.ActivationContext);
    // SAFETY: both unions use the same layout.
    assert_eq!(unsafe { a.u.Flags }, unsafe { b.u.Flags });
}

// --- set_pool ---
//
// `set_pool` takes `&ThreadpoolPool`, so it cannot be handed an invented value,
// and the environment records the borrow as `CallbackEnviron<'pool>`. Its
// behaviour is covered in the pool module's own tests, next to the type that
// makes it sound.

#[test]
fn clear_pool_leaves_the_default_pool() {
    let mut env = CallbackEnviron::new();
    env.clear_pool();
    assert_eq!(inner(&env).Pool, 0);
}

#[test]
fn clear_pool_does_not_alter_priority_or_size() {
    let mut env = CallbackEnviron::new();
    env.clear_pool();
    assert_eq!(inner(&env).CallbackPriority, TP_CALLBACK_PRIORITY_NORMAL);
    assert_eq!(
        inner(&env).Size,
        mem::size_of::<TP_CALLBACK_ENVIRON_V3>() as u32,
    );
}

// --- set_cleanup_group ---
//
// The values below are never handed to the thread pool; these tests only check
// that the setter records what it was given. `set_cleanup_group` is `unsafe`
// precisely because a real call would have the pool dereference them.

#[test]
fn set_cleanup_group_none_callback() {
    let mut env = CallbackEnviron::new();
    // SAFETY: the environment is never used to create an object, so the group
    // value is only stored and read back, never dereferenced by the pool.
    unsafe { env.set_cleanup_group(99, None) };
    assert_eq!(inner(&env).CleanupGroup, 99);
    assert!(inner(&env).CleanupGroupCancelCallback.is_none());
}

#[test]
fn set_cleanup_group_with_callback() {
    unsafe extern "system" fn cancel_cb(
        _obj: *mut core::ffi::c_void,
        _ctx: *mut core::ffi::c_void,
    ) {
    }

    let mut env = CallbackEnviron::new();
    // SAFETY: as above, the environment never reaches an object creation call.
    unsafe { env.set_cleanup_group(7, Some(cancel_cb)) };
    assert_eq!(inner(&env).CleanupGroup, 7);
    assert!(inner(&env).CleanupGroupCancelCallback.is_some());
}

#[test]
fn set_cleanup_group_zero_clears() {
    let mut env = CallbackEnviron::new();
    // SAFETY: as above, the environment never reaches an object creation call.
    unsafe {
        env.set_cleanup_group(55, None);
        env.set_cleanup_group(0, None);
    }
    assert_eq!(inner(&env).CleanupGroup, 0);
}
// --- set_priority ---

#[test]
fn set_priority_high() {
    let mut env = CallbackEnviron::new();
    env.set_priority(CallbackPriority::High);
    assert_eq!(inner(&env).CallbackPriority, TP_CALLBACK_PRIORITY_HIGH);
}

#[test]
fn set_priority_low() {
    let mut env = CallbackEnviron::new();
    env.set_priority(CallbackPriority::Low);
    assert_eq!(inner(&env).CallbackPriority, TP_CALLBACK_PRIORITY_LOW);
}

#[test]
fn set_priority_normal_is_idempotent() {
    let mut env = CallbackEnviron::new();
    env.set_priority(CallbackPriority::Normal);
    assert_eq!(inner(&env).CallbackPriority, TP_CALLBACK_PRIORITY_NORMAL);
}

#[test]
fn set_priority_round_trip() {
    let mut env = CallbackEnviron::new();
    env.set_priority(CallbackPriority::High);
    env.set_priority(CallbackPriority::Normal);
    assert_eq!(inner(&env).CallbackPriority, TP_CALLBACK_PRIORITY_NORMAL);
}

// --- set_runs_long ---

/// The ABI values this module's assertions expect, written independently of the
/// implementation's own constants.
///
/// Asserting `environ_flags::LONG_FUNCTION == environ_flags::LONG_FUNCTION`
/// would pass however the implementation constant were changed, so the expected
/// values are restated here rather than imported. They are named rather than
/// written inline so that what each number *is* stays legible.
mod expected_abi {
    /// The bit `SetThreadpoolCallbackRunsLong` sets in the environment's flags.
    pub(super) const LONG_FUNCTION_BIT: u32 = 1;
    /// The `TP_CALLBACK_ENVIRON_V3` structure version.
    pub(super) const ENVIRON_VERSION: u32 = 3;
}

/// The bit position is ABI, so this pins the named constant to the value the
/// operating system reads, using an independently written expectation.
#[test]
fn the_long_function_flag_is_bit_zero() {
    assert_eq!(
        crate::callback_env::environ_flags::LONG_FUNCTION,
        expected_abi::LONG_FUNCTION_BIT
    );
}

#[test]
fn set_runs_long_sets_bit_zero() {
    let mut env = CallbackEnviron::new();
    env.set_runs_long();
    // SAFETY: Flags aliases s._bitfield as u32.
    assert_eq!(
        unsafe { inner(&env).u.Flags } & expected_abi::LONG_FUNCTION_BIT,
        expected_abi::LONG_FUNCTION_BIT
    );
}

#[test]
fn set_runs_long_is_idempotent() {
    let mut env = CallbackEnviron::new();
    env.set_runs_long();
    env.set_runs_long();
    // SAFETY: Flags aliases s._bitfield as u32.
    assert_eq!(
        unsafe { inner(&env).u.Flags },
        expected_abi::LONG_FUNCTION_BIT
    );
}

#[test]
fn set_runs_long_preserves_version_size_priority() {
    let mut env = CallbackEnviron::new();
    env.set_runs_long();
    let i = inner(&env);
    assert_eq!(i.Version, expected_abi::ENVIRON_VERSION);
    assert_eq!(i.Size, mem::size_of::<TP_CALLBACK_ENVIRON_V3>() as u32);
    assert_eq!(i.CallbackPriority, TP_CALLBACK_PRIORITY_NORMAL);
}

// --- set_library ---

#[test]
fn set_library_stores_pointer() {
    let mut env = CallbackEnviron::new();
    let fake_dll = 0xDEAD_BEEF_usize as *mut core::ffi::c_void;
    // SAFETY: pointer is not dereferenced; only stored.
    unsafe { env.set_library(fake_dll) };
    assert_eq!(inner(&env).RaceDll, fake_dll);
}

#[test]
fn set_library_null_stores_null() {
    let mut env = CallbackEnviron::new();
    // SAFETY: null pointer, only stored.
    unsafe { env.set_library(core::ptr::null_mut()) };
    assert!(inner(&env).RaceDll.is_null());
}

// --- as_mut_ptr ---

#[test]
fn as_mut_ptr_is_nonnull() {
    let mut env = CallbackEnviron::new();
    assert!(!env.as_mut_ptr().is_null());
}

#[test]
fn as_mut_ptr_points_to_correct_version() {
    let mut env = CallbackEnviron::new();
    let ptr = env.as_mut_ptr();
    // SAFETY: ptr is valid for the lifetime of env.
    assert_eq!(unsafe { (*ptr).Version }, expected_abi::ENVIRON_VERSION);
}