snapper-box 0.0.4

Cryptographic storage for snapper
Documentation
//! Integration tests for `CryptoBox`
#![cfg(feature = "expensive_tests")]

use std::collections::HashMap;

use proptest::{collection::vec, prelude::*};
use snapper_box::CryptoBox;
use tempfile::tempdir;

/// A single command for a test of `CryptoBox`
#[derive(Clone, Debug)]
enum Command {
    /// Insert an item into the store
    Insert {
        /// Namespace to insert in
        namespace: usize,
        /// Key to insert
        key: u16,
        /// Value to insert
        value: i64,
    },
    /// Flush, drop, and reload the store
    Drop,
}

/// List of commands for a test of `CryptoBox`
#[derive(Clone, Debug)]
struct CommandList {
    /// Total number of namespaces
    namespace_count: usize,
    /// List of commands
    commands: Vec<Command>,
}

impl CommandList {
    /// Convert to a hashmap of hashmaps, for comparison test
    pub fn to_hashmaps(&self) -> HashMap<String, HashMap<u16, i64>> {
        let mut hashes: HashMap<String, HashMap<u16, i64>> = HashMap::new();
        // Initialize maps
        for i in 0..self.namespace_count {
            hashes.insert(i.to_string(), HashMap::new());
        }
        // Perform commands
        for command in &self.commands {
            match command {
                Command::Insert {
                    namespace,
                    key,
                    value,
                } => {
                    let namespace = namespace.to_string();
                    let map = hashes.get_mut(&namespace).expect("Invalid command");
                    map.insert(*key, *value);
                }
                Command::Drop => continue,
            }
        }
        // Remove any empty namespaces
        for name in hashes.keys().cloned().collect::<Vec<_>>() {
            if hashes.get(&name).map(|x| x.is_empty()).unwrap_or(true) {
                hashes.remove(&name);
            }
        }
        hashes
    }
}

// Define strategies
prop_compose! {
    /// Creates a single `Command`.
    ///
    /// Each `Command` has a 1 / `drop_interval` chance of being a `Command::Drop`
    fn command(max_namespaces: usize, drop_interval: u8)
        (namespace in 0..max_namespaces,
         key in any::<u16>(),
         value in any::<i64>(),
         drop in 0_u8..drop_interval) -> Command {
            if drop == 0 {
                Command::Drop
            } else {
                Command::Insert { namespace, key, value }
            }
        }
}
prop_compose! {
    /// Creates a list of commands with a specified number of namespaces,  a specified drop interval, and a
    /// specified length of the command list
    fn command_list_raw(namespaces: usize, drop_interval: u8, command_list_max_len: usize)
        (commands in vec(command(namespaces, drop_interval), 0..command_list_max_len)) -> CommandList {
            CommandList { namespace_count: namespaces, commands }
    }
}
prop_compose! {
    /// Creates a smallish list of `Commands`
    fn command_list_small()
        (command_list in command_list_raw(10, 10, 50)) -> CommandList {
            command_list
        }
}

proptest! {
    #![proptest_config(ProptestConfig {
        cases: 150,
        ..ProptestConfig::default()
    })]
    /// Test small batches of transactions
    #[test]
    fn smoke_test_small(command_list in command_list_small()) {
        println!("{:#?}", command_list);
        // Get the path
        let tempdir = tempdir().expect("Failed to get tempdir");
        let path = tempdir.path().join("box");
        // Get the box
        let mut crypto_box =
            CryptoBox::init(&path, None, None, "testing").expect("Failed to init box");
        // get the command ouput hash maps
        let target = command_list.to_hashmaps();
        // Process the commands
        for command in command_list.commands {
            match command {
                Command::Insert {
                    namespace,
                    key,
                    value,
                } => {
                    let name = namespace.to_string();
                    // Make the namespace if it does not exist
                    if !crypto_box.namespace_exists(&name) {
                        crypto_box
                            .create_namespace(name.clone())
                            .expect("Failed to create namespace");
                    }
                    // Insert the pair
                    crypto_box
                        .insert(&key, &value, &name)
                        .expect("Failed to insert pair");
                }
                Command::Drop => {
                    // Drop and reload the box
                    crypto_box.flush().expect("Failed to flush");
                    std::mem::drop(crypto_box);
                    crypto_box = CryptoBox::open(&path, "testing").expect("Failed to reopen box");
                }
            }
        }
        // get the comparison map
        let mut output: HashMap<String, HashMap<u16, i64>> = HashMap::new();
        for name in crypto_box.namespaces() {
            let namespace = crypto_box
                .to_hashmap(&name)
                .expect("Failed to dump namespace");
            output.insert(name, namespace);
        }
        assert_eq!(output, target);
    }
}