run-down 0.1.1

An implementation of run-down protection in rust.
Documentation
// Copyright 2019 Brian Gianforcaro

use pretty_assertions::assert_eq;
use run_down::{RundownError, RundownGuard, RundownRef};
use std::sync::Arc;
use std::thread;
use std::time::Duration;

//-------------------------------------------------------------------
// Test: test_rundown_guard_implements_drop
//
// Description:
//  Test that RundownGuard implements Drop.
//
#[test]
#[allow(clippy::drop_bounds)]
fn test_rundown_guard_implements_drop() {
    // Test via compilation.
    fn is_droppable<T: Drop>() {}
    is_droppable::<RundownGuard>();

    // Verify with needs_drop as well.
    assert!(std::mem::needs_drop::<RundownGuard>());
}

//-------------------------------------------------------------------
// Test: test_acquisition_when_not_rundown
//
// Description:
//  Test that acquisition of run-down protection succeeds
//  when the RundownRef has not yet been marked for run-down.
//
#[test]
fn test_acquisition_when_not_rundown() {
    let rundown = RundownRef::new();

    let result = rundown.try_acquire();
    assert!(result.is_ok());

    let _guard: RundownGuard = result.unwrap();
}

//-------------------------------------------------------------------
// Test: test_acquisition_when_rundown
//
// Description:
//  Test that acquisition of run-down protection fails
//  when the RundownRef has successfully been run-down.
//
#[test]
fn test_acquisition_when_rundown() {
    let rundown_ref = RundownRef::new();

    // Rundown the object.
    rundown_ref.wait_for_rundown();

    let result = rundown_ref.try_acquire();
    assert_eq!(result.err(), Some(RundownError::RundownInProgress));
}

// Test: test_multiple_successive_waits
//
// Description:
//  Test that sucessive wait_for_rundown work.
//
#[test]
fn test_multiple_successive_waits() {
    // Setup and completely run-down the object.
    let rundown_ref = RundownRef::new();

    for _ in 0..10 {
        rundown_ref.wait_for_rundown();
    }
}

//-------------------------------------------------------------------
// Test: test_re_init
//
// Description:
//  Test that re_init works in the designed mode.
//
#[test]
fn test_re_init() {
    // Setup and completely run-down the object.
    let rundown_ref = RundownRef::new();
    rundown_ref.wait_for_rundown();

    // Rundown on the object should succeed again.
    rundown_ref.re_init();
    rundown_ref.wait_for_rundown();
}

//-------------------------------------------------------------------
// Test: test_re_init_panic_without_rundown
//
// Description:
//  Test that re_init without running-down the object panics.
//
#[test]
#[should_panic]
fn test_re_init_panic_without_rundown() {
    let rundown_ref = RundownRef::new();

    // Re-init should panic as run-down has not occurred.
    rundown_ref.re_init();
}

//-------------------------------------------------------------------
// Test: test_re_init_panic_on_ref
//
// Description:
//  Test that re_init with and outstanding protection panics.
//
#[test]
#[should_panic]
fn test_re_init_panic_on_ref() {
    let rundown_ref = RundownRef::new();
    let _guard = rundown_ref.try_acquire().unwrap();

    // Re-init should panic as run-down has not occurred.
    rundown_ref.re_init();
}

//-------------------------------------------------------------------
// Test: test_usage_with_concurrency
//
// Description:
//  A simple test case to validate the usage of RundownRef
//  and RundownGuard in the wild. We spawn a few threads all attempting
//  to acquire rundown protection, and holding it for a small duration.
//  In parallel we attempt to run-down the ref so that no one can acquire
//  rundown protection, and we wait for that to complete. On my machine
//  most of the threads succeed, while one of the last threads fail as
//  the object was successfully run-down.
//
#[test]
fn test_usage_with_concurrency() {
    let mut children = vec![];
    let rundown = Arc::new(RundownRef::new());

    for _ in 0..20 {
        let rundown_clone = Arc::clone(&rundown);

        children.push(thread::spawn(move || {
            if let Ok(_guard) = rundown_clone.try_acquire() {
                thread::sleep(Duration::from_millis(10));
            }
        }));
    }

    rundown.wait_for_rundown();

    for child in children {
        let _ = child.join();
    }
}