rust-utils 0.13.1

Various utility routines used in the rust programs I have written
Documentation
#![allow(unused_imports)]
#[cfg(test)]
use crate::{
    utils::{self, get_input},
    logging::*,
    config::*
};
use std::{
    path::*,
    fs, env,
    thread,
    time::Duration
};
use serde_derive::{Deserialize, Serialize};
use chrono::{Timelike, Datelike, Local};
use regex::Regex;

#[cfg(feature = "futures")]
use crate::futures::exec_future;

// convenience function tests
#[test]
fn get_execname() {
    println!("get_execname() test:");
    println!("This test's executable name is {}", utils::get_execname());
}

#[test]
fn capitalize() {
    println!("capitalize() test:");
    let text = utils::capitalize("test");
    assert_eq!(text, "Test");
}

#[test]
fn get_parent() {
    println!("get_parent() test:");
    let home_dir = format!("{}/", env::var("HOME").unwrap());
    println!("{home_dir}");
    let parent = utils::get_parent(home_dir);
    assert_eq!(parent, "/home");
}

#[test]
fn get_filename() {
    println!("get_filename() test:");
    let dir = "/tmp/test.txt";
    fs::write("/tmp/test.txt", "test").unwrap();
    let file = utils::get_filename(dir);
    assert_eq!(file, "test.txt");
    fs::remove_file("/tmp/test.txt").unwrap();
}

#[test]
fn alphabetize() {
    println!("alphabetize() test:");

    // the alphabet, randomized
    const ALPHA_UPPER_EXPECTED: [&str; 26] = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"];
    const ALPHA_LOWER_SCRAMBLED: [&str; 26] = ["e" ,"c", "r", "n", "x", "j", "o", "a", "g", "v", "y", "f", "l", "s", "q", "d", "k", "t", "b", "w", "m", "h", "u", "i", "p", "z"];
    const ALPHA_LOWER_EXPECTED: [&str; 26] = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"];
    const ALPHA_UPPER_SCRAMBLED: [&str; 26] = ["J", "B", "R", "L", "C", "P", "Y", "D", "F", "K", "A", "U", "O", "S", "Z", "E", "X", "Q", "I", "W", "T", "V", "H", "M", "N", "G"];

    // uppercase letters
    let mut alpha_vec: Vec<String> = utils::alphabetize(&ALPHA_UPPER_SCRAMBLED);

    // are the letters in the correct order?
    for (count,letter) in alpha_vec.iter().enumerate() {
        assert_eq!(letter, ALPHA_UPPER_EXPECTED[count]);
    }
    println!("Uppercase: {alpha_vec:?}\n");

    // lowercase letters
    alpha_vec = utils::alphabetize(&ALPHA_LOWER_SCRAMBLED);

    // are the letters in the correct order?
    for (count,letter) in alpha_vec.iter().enumerate() {
        assert_eq!(letter, ALPHA_LOWER_EXPECTED[count]);
    }
    println!("Lowercase: {alpha_vec:?}");
}

// logging API test
#[test]
fn logging_test() {
    let info_regex = Regex::new(r"\[[0-9]*:[0-9][0-9]:[0-9][0-9]\] \[INFO\]: Yes. Is this useful?").unwrap();
    let info_regex2 = Regex::new(r"\[[0-9]*:[0-9][0-9]:[0-9][0-9]\] \[INFO\]: Yes.").unwrap();
    let debug_regex = Regex::new(r"\[[0-9]*:[0-9][0-9]:[0-9][0-9]\] \[DEBUG\]: Debug info").unwrap();
    let debug_regex2 = Regex::new(r"\[[0-9]*:[0-9][0-9]:[0-9][0-9]\] \[DEBUG\]: This shouldn't show up").unwrap();
    let warn_regex = Regex::new(r"\[[0-9]*:[0-9][0-9]:[0-9][0-9]\] \[WARN\]: There may be an issue!").unwrap();
    let error_regex = Regex::new(r"\[[0-9]*:[0-9][0-9]:[0-9][0-9]\] \[ERROR\]: Houston, we have a problem!").unwrap();
    let expected_name = {
        let now = Local::now();
        format!("test-{}-{}-{}.log", now.year(), now.month(), now.day())
    };
    let path = format!("{}/.local/share/coof-testing/{expected_name}", env::var("HOME").expect("Where the hell is your home folder?!"));
    fs::remove_file(&path).unwrap_or(());
    let log = Log::new("test", "coof-testing");
    log.line(LogLevel::Info, "Yes. Is this useful?", true);
    log.line_basic("Yes.", true);
    log.line(LogLevel::Debug(true), "Debug info", true);
    log.line(LogLevel::Debug(false), "This shouldn't show up", true);
    log.line(LogLevel::Warn, "There may be an issue!", true);
    log.line(LogLevel::Error, "Houston, we have a problem!", true);
    let file = fs::read_to_string(&path).unwrap();
    assert!(info_regex.is_match(&file));
    assert!(info_regex2.is_match(&file));
    assert!(debug_regex.is_match(&file));
    assert!(!debug_regex2.is_match(&file));
    assert!(warn_regex.is_match(&file));
    assert!(error_regex.is_match(&file));

    let log2 = log.clone();
    let thread = thread::spawn(move || {
        for _ in 0..100 {
            log2.line(LogLevel::Info, "Concurrent logging...", true);
        }
    });

    for _ in 0..100 {
        log.line(LogLevel::Info, "Concurrent logging!!!", true);
    }

    thread.join().expect("thread did not complete successfully!");
}

#[test]
#[should_panic]
fn logging_panic_test() {
    let log = Log::new("test", "coof-testing");
    log.report_panics(true);
    let user = env::var("USER").expect("What is your name again?");
    panic!("{user} has tested positive for the coof!");
}

// config api test
#[test]
fn config_test() {
    #[derive(Deserialize, Serialize, PartialEq, Debug)]
    struct TestConfig {
        pub string: String,
        pub number: u32,
        pub boolean: bool
    }

    impl Default for TestConfig {
        fn default() -> TestConfig {
            TestConfig {
                string: "string".to_string(),
                number: 100,
                boolean: true
            }
        }
    }

    impl Config for TestConfig {
        const FILE_NAME: &'static str = "test.toml";
        fn get_save_dir() -> String { format!("{}/.config/coof-testing", env::var("HOME").expect("Where the hell is your home folder?!")) }
    }

    let config_dir = format!("{}/.config/coof-testing", env::var("HOME").expect("Where the hell is your home folder?!"));
    fs::remove_dir_all(config_dir).unwrap_or(());
    let expected = TestConfig::default();
    let mut config = TestConfig::load();

    assert_eq!(expected, config);

    config.number = 5;
    config.save().expect("Unable to save config!");

    let expected = TestConfig {
        string: "string".to_string(),
        number: 5,
        boolean: true
    };

    let config = TestConfig::load();
    assert_eq!(expected, config);
}

// command executor test
#[test]
fn run_cmd_test() {
    let reg_cmd = utils::run_command("nmcli", false, ["networking", "connectivity", "check"]);
    let priv_cmd = utils::run_command("nmcli", true, ["networking", "connectivity", "check"]);
    assert_eq!(reg_cmd.output, "full");
    assert_eq!(priv_cmd.output, "full");
}

// process spawner test
// requires Firefox dev edition and okular to be installed
#[test]
fn spawn_proc_test() {
    utils::spawn_process("firefox-developer-edition", false, false, ["aercloud-systems.net"]);
    utils::spawn_process("okular", false, true, ["README.md"]);
    utils::spawn_process("echo", false, false, ["yes"]);
}

#[test]
fn user_input() {
    let name = get_input("What is your name?");
    println!("Your name is {name}.");
}

#[cfg(feature = "futures")]
#[test]
fn futures() {
    let val = exec_future(future_fn());
    println!("Value from async function: {val}");
}

#[cfg(feature = "futures")]
async fn future_fn() -> String {
    let log = Log::new("test", "coof-testing");
    log.line_basic("async rust function being executed!", true);
    log.line_basic(format!("2 + 2 is {}, not 5!", 2 + 2), true);
    "yes".to_string()
}