From 8cb8e2c5c1fcb719a805778ac67010cc37af2d0c Mon Sep 17 00:00:00 2001 From: Jon-Becker Date: Wed, 15 Nov 2023 10:16:27 -0500 Subject: [PATCH] chore(docs): finish doctests and docstrings for `heimdall_common` --- common/src/constants.rs | 22 +- common/src/ether/evm/core/types.rs | 2 +- common/src/ether/rpc.rs | 12 +- common/src/resources/openai.rs | 8 + common/src/resources/transpose.rs | 25 ++ common/src/utils/http.rs | 13 +- common/src/utils/integers.rs | 56 ++- common/src/utils/io/file.rs | 43 ++ common/src/utils/mod.rs | 1 - common/src/utils/strings.rs | 424 +++++++++++++++++++ common/src/utils/testing/benchmarks.rs | 24 ++ common/src/utils/testing/mod.rs | 1 - common/src/utils/testing/tests.rs | 18 - common/src/utils/tests.rs | 553 ------------------------- common/src/utils/threading.rs | 59 +++ common/src/utils/time.rs | 46 ++ common/src/utils/version.rs | 105 ++++- 17 files changed, 809 insertions(+), 603 deletions(-) delete mode 100644 common/src/utils/testing/tests.rs delete mode 100644 common/src/utils/tests.rs diff --git a/common/src/constants.rs b/common/src/constants.rs index cc34c863..3cc66cbb 100644 --- a/common/src/constants.rs +++ b/common/src/constants.rs @@ -3,32 +3,32 @@ use lazy_static::lazy_static; lazy_static! { - // The following regex is used to validate Ethereum addresses. + /// The following regex is used to validate Ethereum addresses. pub static ref ADDRESS_REGEX: Regex = Regex::new(r"^(0x)?[0-9a-fA-F]{40}$").unwrap(); - // The following regex is used to validate Ethereum transaction hashes. + /// The following regex is used to validate Ethereum transaction hashes. pub static ref TRANSACTION_HASH_REGEX: Regex = Regex::new(r"^(0x)?[0-9a-fA-F]{64}$").unwrap(); - // The following regex is used to validate raw bytecode files as targets. - // It also restricts the file to a maximum of ~24kb, the maximum size of a - // contract on Ethereum. + /// The following regex is used to validate raw bytecode files as targets. + /// It also restricts the file to a maximum of ~24kb, the maximum size of a + /// contract on Ethereum. pub static ref BYTECODE_REGEX: Regex = Regex::new(r"^(0x)?[0-9a-fA-F]{0,50000}$").unwrap(); - // The following regex is used to reduce null byte prefixes + /// The following regex is used to reduce null byte prefixes pub static ref REDUCE_HEX_REGEX: Regex = Regex::new(r"^0x(00)*").unwrap(); - // The following regex is used as a search pattern for words + /// The following regex is used as a search pattern for words pub static ref WORD_REGEX: Regex = Regex::new(r"0x[0-9a-fA-F]{0,64}").unwrap(); - // The following regex is used to find type castings + /// The following regex is used to find type castings pub static ref TYPE_CAST_REGEX: Regex = Regex::new(r"(address\(|string\(|bool\(|bytes(\d*)\(|uint(\d*)\(|int(\d*)\()(?!\))").unwrap(); - // The following regex is used to find memory length accesses + /// The following regex is used to find memory length accesses pub static ref MEMLEN_REGEX: Regex = Regex::new(r"memory\[memory\[[0-9x]*\]\]").unwrap(); - // The following regex is used to find memory accesses + /// The following regex is used to find memory accesses pub static ref MEMORY_REGEX: Regex = Regex::new(r"memory\[\(?[0-9x]*\]").unwrap(); - // The following regex is used to find storage accesses + /// The following regex is used to find storage accesses pub static ref STORAGE_REGEX: Regex = Regex::new(r"storage\[\(?[0-9x]*\]").unwrap(); } diff --git a/common/src/ether/evm/core/types.rs b/common/src/ether/evm/core/types.rs index 1465aa62..317257c2 100644 --- a/common/src/ether/evm/core/types.rs +++ b/common/src/ether/evm/core/types.rs @@ -318,7 +318,7 @@ pub fn convert_bitmask(instruction: Instruction) -> (usize, Vec) { /// 2. Potential types that the byte size could be. /// /// ``` -/// use heimdall_common::ether::evm::utils::byte_size_to_type; +/// use heimdall_common::ether::evm::core::types::byte_size_to_type; /// /// let (byte_size, potential_types) = byte_size_to_type(1); /// assert_eq!(byte_size, 1); diff --git a/common/src/ether/rpc.rs b/common/src/ether/rpc.rs index 6e354596..43b1c7ec 100644 --- a/common/src/ether/rpc.rs +++ b/common/src/ether/rpc.rs @@ -13,8 +13,8 @@ use heimdall_cache::{read_cache, store_cache}; /// ```no_run /// use heimdall_common::ether::rpc::chain_id; /// -/// let chain_id = chain_id("https://eth.llamarpc.com").await.unwrap(); -/// assert_eq!(chain_id, 1); +/// // let chain_id = chain_id("https://eth.llamarpc.com").await.unwrap(); +/// //assert_eq!(chain_id, 1); /// ``` pub async fn chain_id(rpc_url: &str) -> Result> { // get a new logger @@ -66,8 +66,8 @@ pub async fn chain_id(rpc_url: &str) -> Result> /// ```no_run /// use heimdall_common::ether::rpc::get_code; /// -/// let bytecode = get_code("0x0", "https://eth.llamarpc.com").await; -/// assert!(bytecode.is_ok()); +/// // let bytecode = get_code("0x0", "https://eth.llamarpc.com").await; +/// // assert!(bytecode.is_ok()); /// ``` pub async fn get_code( contract_address: &str, @@ -136,8 +136,8 @@ pub async fn get_code( /// ```no_run /// use heimdall_common::ether::rpc::get_code; /// -/// let bytecode = get_code("0x0", "https://eth.llamarpc.com").await; -/// assert!(bytecode.is_ok()); +/// // let bytecode = get_code("0x0", "https://eth.llamarpc.com").await; +/// // assert!(bytecode.is_ok()); /// ``` pub async fn get_transaction( transaction_hash: &str, diff --git a/common/src/resources/openai.rs b/common/src/resources/openai.rs index cc0a205c..739aaa53 100644 --- a/common/src/resources/openai.rs +++ b/common/src/resources/openai.rs @@ -1,6 +1,14 @@ use crate::utils::io::logging::Logger; use async_openai::{types::CreateCompletionRequestArgs, Client}; +/// Complete the given prompt using the OpenAI API. +/// +/// ``` +/// use heimdall_common::resources::openai::complete; +/// +/// let prompt = "what is love?"; +/// let api_key = "your-api-key"; +/// // complete(prompt, api_key).await; pub async fn complete(prompt: &str, api_key: &str) -> Option { let client = Client::new().with_api_key(api_key); diff --git a/common/src/resources/transpose.rs b/common/src/resources/transpose.rs index 3be1a931..930d9c77 100644 --- a/common/src/resources/transpose.rs +++ b/common/src/resources/transpose.rs @@ -20,6 +20,7 @@ struct TransposeResponse { results: Vec, } +/// executes a transpose SQL query and returns the response async fn _call_transpose(query: &str, api_key: &str) -> Option { // get a new logger let logger = Logger::default(); @@ -74,6 +75,19 @@ async fn _call_transpose(query: &str, api_key: &str) -> Option, reqwest::Error>` - the response body as JSON +/// let url = "https://example.com"; +/// let timeout = 5; +/// // get_json_from_url(url, timeout).await; +/// ``` pub async fn get_json_from_url(url: &str, timeout: u64) -> Result, reqwest::Error> { _get_json_from_url(url, 0, 5, timeout).await } #[async_recursion] +/// Internal function for making a GET request to the target URL and returning the response body as +/// JSON async fn _get_json_from_url( url: &str, retry_count: u8, diff --git a/common/src/utils/integers.rs b/common/src/utils/integers.rs index 58d338b9..592842c5 100644 --- a/common/src/utils/integers.rs +++ b/common/src/utils/integers.rs @@ -3,7 +3,13 @@ pub trait ToLocaleString { } impl ToLocaleString for usize { - // add commas every 3 digits, e.g. 1000000 -> 1,000,000. + /// Add commas every 3 digits, e.g. 1000000 -> 1,000,000. + /// + /// ``` + /// use heimdall_common::utils::integers::ToLocaleString; + /// + /// assert_eq!(1000000.to_locale_string(), "1,000,000"); + /// ``` fn to_locale_string(&self) -> String { let num_str = self.to_string(); let mut result = String::new(); @@ -18,3 +24,51 @@ impl ToLocaleString for usize { result.chars().rev().collect() } } + +#[cfg(test)] +mod tests { + use crate::utils::integers::ToLocaleString; + + #[test] + fn test_to_locale_string() { + // Test case: Single-digit number + let num = 5; + let expected = "5".to_string(); + assert_eq!(num.to_locale_string(), expected); + + // Test case: Three-digit number + let num = 123; + let expected = "123".to_string(); + assert_eq!(num.to_locale_string(), expected); + + // Test case: Four-digit number + let num = 1234; + let expected = "1,234".to_string(); + assert_eq!(num.to_locale_string(), expected); + + // Test case: Five-digit number + let num = 12345; + let expected = "12,345".to_string(); + assert_eq!(num.to_locale_string(), expected); + + // Test case: Six-digit number + let num = 123456; + let expected = "123,456".to_string(); + assert_eq!(num.to_locale_string(), expected); + + // Test case: Seven-digit number + let num = 1234567; + let expected = "1,234,567".to_string(); + assert_eq!(num.to_locale_string(), expected); + + // Test case: Eight-digit number + let num = 12345678; + let expected = "12,345,678".to_string(); + assert_eq!(num.to_locale_string(), expected); + + // Test case: Nine-digit number + let num = 123456789; + let expected = "123,456,789".to_string(); + assert_eq!(num.to_locale_string(), expected); + } +} diff --git a/common/src/utils/io/file.rs b/common/src/utils/io/file.rs index 427ec303..4395c07c 100644 --- a/common/src/utils/io/file.rs +++ b/common/src/utils/io/file.rs @@ -7,6 +7,15 @@ use std::{ process::Command, }; +/// Convert a long path to a short path. +/// +/// ```no_run +/// use heimdall_common::utils::io::file::short_path; +/// +/// let path = "/some/long/path/that/is/cwd/something.json"; +/// let short_path = short_path(path); +/// assert_eq!(short_path, "./something.json"); +/// ``` pub fn short_path(path: &str) -> String { let current_dir = match env::current_dir() { Ok(dir) => dir.into_os_string().into_string().unwrap(), @@ -15,6 +24,15 @@ pub fn short_path(path: &str) -> String { path.replace(¤t_dir, ".") } +/// Write contents to a file on the disc +/// +/// ```no_run +/// use heimdall_common::utils::io::file::write_file; +/// +/// let path = "/tmp/test.txt"; +/// let contents = "Hello, World!"; +/// let result = write_file(path, contents); +/// ``` pub fn write_file(_path: &str, contents: &str) -> String { let path = std::path::Path::new(_path); let prefix = path.parent().unwrap(); @@ -40,10 +58,27 @@ pub fn write_file(_path: &str, contents: &str) -> String { _path.to_string() } +/// Write contents to a file on the disc +/// +/// ```no_run +/// use heimdall_common::utils::io::file::write_lines_to_file; +/// +/// let path = "/tmp/test.txt"; +/// let contents = vec![String::from("Hello"), String::from("World!")]; +/// let result = write_lines_to_file(path, contents); +/// ``` pub fn write_lines_to_file(_path: &str, contents: Vec) { write_file(_path, &contents.join("\n")); } +/// Read contents from a file on the disc +/// +/// ```no_run +/// use heimdall_common::utils::io::file::read_file; +/// +/// let path = "/tmp/test.txt"; +/// let contents = read_file(path); +/// ``` pub fn read_file(_path: &str) -> String { let path = std::path::Path::new(_path); let mut file = match File::open(path) { @@ -66,6 +101,14 @@ pub fn read_file(_path: &str) -> String { contents } +/// Delete a file from the disc +/// +/// ```no_run +/// use heimdall_common::utils::io::file::delete_path; +/// +/// let path = "/tmp/test.txt"; +/// let result = delete_path(path); +/// ``` pub fn delete_path(_path: &str) -> bool { let path = std::path::Path::new(_path); Command::new("rm").args(["-rf", path.to_str().unwrap()]).output().is_ok() diff --git a/common/src/utils/mod.rs b/common/src/utils/mod.rs index 86494c10..910066c1 100644 --- a/common/src/utils/mod.rs +++ b/common/src/utils/mod.rs @@ -4,7 +4,6 @@ pub mod io; pub mod strings; pub mod sync; pub mod testing; -mod tests; pub mod threading; pub mod time; pub mod version; diff --git a/common/src/utils/strings.rs b/common/src/utils/strings.rs index b8384a5b..feb5595f 100644 --- a/common/src/utils/strings.rs +++ b/common/src/utils/strings.rs @@ -14,11 +14,27 @@ pub fn sign_uint(unsigned: U256) -> I256 { } /// Decodes a hex string into a vector of bytes +/// +/// ``` +/// use heimdall_common::utils::strings::decode_hex; +/// +/// let hex = "48656c6c6f20576f726c64"; // "Hello World" in hex +/// let result = decode_hex(hex); +/// assert_eq!(result, Ok(vec![72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100])); +/// ``` pub fn decode_hex(s: &str) -> Result, ParseIntError> { (0..s.len()).step_by(2).map(|i| u8::from_str_radix(&s[i..i + 2], 16)).collect() } /// Encodes a vector of bytes into a hex string +/// +/// ``` +/// use heimdall_common::utils::strings::encode_hex; +/// +/// let bytes = vec![72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100]; +/// let result = encode_hex(bytes); +/// assert_eq!(result, "48656c6c6f20576f726c64"); +/// ``` pub fn encode_hex(s: Vec) -> String { s.iter().fold(String::new(), |mut acc, b| { write!(acc, "{b:02x}", b = b).unwrap(); @@ -27,6 +43,15 @@ pub fn encode_hex(s: Vec) -> String { } /// Encodes a U256 into a hex string, removing leading zeros +/// +/// ``` +/// use ethers::types::U256; +/// use heimdall_common::utils::strings::encode_hex_reduced; +/// +/// let u256 = U256::max_value(); +/// let result = encode_hex_reduced(u256); +/// assert_eq!(result, "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); +/// ``` pub fn encode_hex_reduced(s: U256) -> String { if s > U256::from(0) { REDUCE_HEX_REGEX.replace(&s.encode_hex(), "0x").to_string() @@ -36,6 +61,14 @@ pub fn encode_hex_reduced(s: U256) -> String { } /// Converts a hex string to an ASCII string +/// +/// ``` +/// use heimdall_common::utils::strings::hex_to_ascii; +/// +/// let hex = "48656c6c6f20576f726c64"; // "Hello World" in hex +/// let result = hex_to_ascii(hex); +/// assert_eq!(result, "Hello World"); +/// ``` pub fn hex_to_ascii(s: &str) -> String { let mut result = String::new(); for i in 0..s.len() / 2 { @@ -51,12 +84,30 @@ pub fn hex_to_ascii(s: &str) -> String { } /// Replaces the last occurrence of a substring in a string +/// +/// ``` +/// use heimdall_common::utils::strings::replace_last; +/// +/// let s = "Hello, world!"; +/// let old = "o"; +/// let new = "0"; +/// let result = replace_last(s, old, new); +/// assert_eq!(result, String::from("Hello, w0rld!")); +/// ``` pub fn replace_last(s: &str, old: &str, new: &str) -> String { let new = new.chars().rev().collect::(); s.chars().rev().collect::().replacen(old, &new, 1).chars().rev().collect::() } /// Finds balanced encapsulator in a string +/// +/// ``` +/// use heimdall_common::utils::strings::find_balanced_encapsulator; +/// +/// let s = "Hello (World)"; +/// let result = find_balanced_encapsulator(s, ('(', ')')); +/// assert_eq!(result, (6, 13, true)); +/// ``` pub fn find_balanced_encapsulator(s: &str, encap: (char, char)) -> (usize, usize, bool) { let mut open = 0; let mut close = 0; @@ -80,6 +131,14 @@ pub fn find_balanced_encapsulator(s: &str, encap: (char, char)) -> (usize, usize } /// Finds balanced parentheses in a string, starting from the end +/// +/// ``` +/// use heimdall_common::utils::strings::find_balanced_encapsulator_backwards; +/// +/// let s = "Hello (World)"; +/// let result = find_balanced_encapsulator_backwards(s, ('(', ')')); +/// assert_eq!(result, (6, 13, true)); +/// ``` pub fn find_balanced_encapsulator_backwards(s: &str, encap: (char, char)) -> (usize, usize, bool) { let mut open = 0; let mut close = 0; @@ -103,6 +162,14 @@ pub fn find_balanced_encapsulator_backwards(s: &str, encap: (char, char)) -> (us } /// Encodes a number into a base26 string +/// +/// ``` +/// use heimdall_common::utils::strings::base26_encode; +/// +/// let n = 123456789; +/// let result = base26_encode(n); +/// assert_eq!(result, "jjddja"); +/// ``` pub fn base26_encode(n: usize) -> String { let mut s = String::new(); let mut n = n; @@ -140,6 +207,14 @@ pub fn split_string_by_regex(input: &str, pattern: Regex) -> Vec { } /// Extracts the condition from a require() or if() statement +/// +/// ``` +/// use heimdall_common::utils::strings::extract_condition; +/// +/// let s = "require(a == b)"; +/// let result = extract_condition(s, "require"); +/// assert_eq!(result, Some("a == b".to_string())); +/// ``` pub fn extract_condition(s: &str, keyword: &str) -> Option { // find the keyword if let Some(start) = s.find(keyword) { @@ -166,6 +241,14 @@ pub fn extract_condition(s: &str, keyword: &str) -> Option { } /// Tokenizes an expression into a vector of tokens +/// +/// ``` +/// use heimdall_common::utils::strings::tokenize; +/// +/// let s = "a + b * c"; +/// let result = tokenize(s); +/// assert_eq!(result, vec!["a", "+", "b", "*", "c"]); +/// ``` pub fn tokenize(s: &str) -> Vec { let mut tokens = Vec::new(); let mut token = String::new(); @@ -268,3 +351,344 @@ pub fn classify_token(token: &str) -> TokenType { // this token must be a function call TokenType::Function } + +#[cfg(test)] +mod tests { + use ethers::types::{I256, U256}; + + use crate::utils::strings::*; + + #[test] + fn test_sign_uint() { + let unsigned = U256::from(10); + let signed = sign_uint(unsigned); + assert_eq!(signed, I256::from(10)); + + let unsigned = U256::from(0); + let signed = sign_uint(unsigned); + assert_eq!(signed, I256::from(0)); + + let unsigned = U256::from(1000); + let signed = sign_uint(unsigned); + assert_eq!(signed, I256::from(1000)); + } + + #[test] + fn test_decode_hex() { + let hex = "48656c6c6f20776f726c64"; // "Hello world" + let result = decode_hex(hex); + assert_eq!(result, Ok(vec![72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100])); + + let hex = "abcdef"; + let result = decode_hex(hex); + assert_eq!(result, Ok(vec![171, 205, 239])); + + let hex = "012345"; + let result = decode_hex(hex); + assert_eq!(result, Ok(vec![1, 35, 69])); + } + + #[test] + fn test_encode_hex() { + let bytes = vec![72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100]; // "Hello world" + let result = encode_hex(bytes); + assert_eq!(result, "48656c6c6f20776f726c64"); + + let bytes = vec![171, 205, 239]; + let result = encode_hex(bytes); + assert_eq!(result, "abcdef"); + + let bytes = vec![1, 35, 69]; + let result = encode_hex(bytes); + assert_eq!(result, "012345"); + } + + #[test] + fn test_encode_hex_reduced() { + let hex = U256::from(10); + let result = encode_hex_reduced(hex); + assert_eq!(result, "0x0a"); + + let hex = U256::from(0); + let result = encode_hex_reduced(hex); + assert_eq!(result, "0"); + + let hex = U256::from(1000); + let result = encode_hex_reduced(hex); + assert_eq!(result, "0x03e8"); + } + + #[test] + fn test_hex_to_ascii() { + let hex = "48656c6c6f20776f726c64"; // "Hello world" + let result = hex_to_ascii(hex); + assert_eq!(result, "Hello world"); + + let hex = "616263646566"; // "abcdef" + let result = hex_to_ascii(hex); + assert_eq!(result, "abcdef"); + + let hex = "303132333435"; // "012345" + let result = hex_to_ascii(hex); + assert_eq!(result, "012345"); + } + + #[test] + fn test_replace_last() { + let s = "Hello, world!"; + let old = "o"; + let new = "0"; + let result = replace_last(s, old, new); + assert_eq!(result, String::from("Hello, w0rld!")); + + let s = "Hello, world!"; + let old = "l"; + let new = "L"; + let result = replace_last(s, old, new); + assert_eq!(result, String::from("Hello, worLd!")); + } + + #[test] + fn test_find_balanced_encapsulator() { + let s = String::from("This is (an example) string."); + let encap = ('(', ')'); + let (start, end, is_balanced) = find_balanced_encapsulator(&s, encap); + assert_eq!(start, 8); + assert_eq!(end, 20); + assert!(is_balanced); + + let s = String::from("This is an example) string."); + let encap = ('(', ')'); + let (start, end, is_balanced) = find_balanced_encapsulator(&s, encap); + assert_eq!(start, 0); + assert_eq!(end, 1); + assert!(!is_balanced); + + let s = String::from("This is (an example string."); + let encap = ('(', ')'); + let (start, end, is_balanced) = find_balanced_encapsulator(&s, encap); + assert_eq!(start, 8); + assert_eq!(end, 1); + assert!(!is_balanced); + } + + #[test] + fn test_find_balanced_encapsulator_backwards() { + let s = String::from("This is (an example) string."); + let encap = ('(', ')'); + let (start, end, is_balanced) = find_balanced_encapsulator_backwards(&s, encap); + assert_eq!(start, 8); + assert_eq!(end, 20); + assert!(is_balanced); + + let s = String::from("This is an example) string."); + let encap = ('(', ')'); + let (_, _, is_balanced) = find_balanced_encapsulator_backwards(&s, encap); + assert!(!is_balanced); + + let s = String::from("This is (an example string."); + let encap = ('(', ')'); + let (_, _, is_balanced) = find_balanced_encapsulator_backwards(&s, encap); + assert!(!is_balanced); + } + + #[test] + fn test_base26_encode() { + let n = 1; + let result = base26_encode(n); + assert_eq!(result, "a"); + + let n = 26; + let result = base26_encode(n); + assert_eq!(result, "z"); + + let n = 27; + let result = base26_encode(n); + assert_eq!(result, "aa"); + + let n = 703; + let result = base26_encode(n); + assert_eq!(result, "aaa"); + } + + #[test] + fn test_split_string_by_regex() { + let input = "Hello,world!"; + let pattern = fancy_regex::Regex::new(r",").unwrap(); + let result = split_string_by_regex(input, pattern); + assert_eq!(result, vec!["Hello", "world!"]); + + let input = "This is a test."; + let pattern = fancy_regex::Regex::new(r"\s").unwrap(); + let result = split_string_by_regex(input, pattern); + assert_eq!(result, vec!["This", "is", "a", "test."]); + + let input = "The quick brown fox jumps over the lazy dog."; + let pattern = fancy_regex::Regex::new(r"\s+").unwrap(); + let result = split_string_by_regex(input, pattern); + assert_eq!( + result, + vec!["The", "quick", "brown", "fox", "jumps", "over", "the", "lazy", "dog."] + ); + } + + #[test] + fn test_extract_condition_present_balanced() { + let s = "require(arg0 == (address(arg0)));"; + let keyword = "require"; + let expected = Some("arg0 == (address(arg0))".to_string()); + assert_eq!(extract_condition(s, keyword), expected); + } + + #[test] + fn test_extract_condition_present_unbalanced() { + let s = "require(arg0 == (address(arg0));"; + let keyword = "require"; + let expected = None; + assert_eq!(extract_condition(s, keyword), expected); + } + + #[test] + fn test_extract_condition_not_present() { + let s = "if (0x01 < var_c.length) {"; + let keyword = "require"; + let expected = None; + assert_eq!(extract_condition(s, keyword), expected); + } + + #[test] + fn test_extract_condition_multiple_keywords() { + let s = "require(var_c.length == var_c.length, \"some revert message\");"; + let keyword = "require"; + let expected = Some("var_c.length == var_c.length".to_string()); + assert_eq!(extract_condition(s, keyword), expected); + } + + #[test] + fn test_extract_condition_empty_string() { + let s = ""; + let keyword = "require"; + let expected = None; + assert_eq!(extract_condition(s, keyword), expected); + } + + #[test] + fn test_tokenize_basic_operators() { + let tokens = tokenize("arg0 + arg1"); + assert_eq!(tokens, vec!["arg0", "+", "arg1"]); + } + + #[test] + fn test_tokenize_parentheses_and_operators() { + let tokens = tokenize("(arg0 + arg1) > (msg.value + 1)"); + assert_eq!( + tokens, + vec!["(", "arg0", "+", "arg1", ")", ">", "(", "msg.value", "+", "1", ")"] + ); + } + + #[test] + fn test_tokenize_multiple_operators() { + let tokens = tokenize("a >= b && c != d"); + assert_eq!(tokens, vec!["a", ">=", "b", "&&", "c", "!=", "d"]); + } + + #[test] + fn test_tokenize_no_spaces() { + let tokens = tokenize("a+b-c*d/e"); + assert_eq!(tokens, vec!["a", "+", "b", "-", "c", "*", "d", "/", "e"]); + } + + #[test] + fn test_tokenize_whitespace_only() { + let tokens = tokenize(" "); + assert_eq!(tokens, Vec::::new()); + } + + #[test] + fn test_tokenize_empty_string() { + let tokens = tokenize(""); + assert_eq!(tokens, Vec::::new()); + } + + #[test] + fn test_tokenize_complex_expression() { + let tokens = tokenize("if (x > 10 && y < 20) || z == 0 { a = b + c }"); + assert_eq!( + tokens, + vec![ + "if", "(", "x", ">", "10", "&&", "y", "<", "20", ")", "||", "z", "==", "0", "{", + "a", "=", "b", "+", "c", "}" + ] + ); + } + + #[test] + fn test_tokenize_separators_at_start_and_end() { + let tokens = tokenize("==text=="); + assert_eq!(tokens, vec!["==", "text", "=="]); + } + + #[test] + fn test_classify_token_parenthesis() { + let classification = classify_token("("); + assert_eq!(classification, TokenType::Control); + + let classification = classify_token(")"); + assert_eq!(classification, TokenType::Control); + } + + #[test] + fn test_classify_token_operators_precedence_1() { + for operator in ["+", "-"].iter() { + let classification = classify_token(operator); + assert_eq!(classification, TokenType::Operator); + } + } + + #[test] + fn test_classify_token_operators_precedence_2() { + for operator in + ["*", "/", "%", "|", "&", "^", "==", ">=", "<=", "!=", "!", "&&", "||"].iter() + { + let classification = classify_token(operator); + assert_eq!(classification, TokenType::Operator); + } + } + + #[test] + fn test_classify_token_constant() { + let classification = classify_token("0x001234567890"); + assert_eq!(classification, TokenType::Constant); + } + + #[test] + fn test_classify_token_variable() { + for variable in [ + "memory[0x01]", + "storage", + "var", + "msg.value", + "block.timestamp", + "this.balance", + "tx.origin", + "arg0", + "ret", + "calldata", + "abi.encode", + ] + .iter() + { + let classification = classify_token(variable); + assert_eq!(classification, TokenType::Variable); + } + } + + #[test] + fn test_classify_token_function() { + for function in ["uint256", "address", "ecrecover", "if"].iter() { + let classification = classify_token(function); + assert_eq!(classification, TokenType::Function); + } + } +} diff --git a/common/src/utils/testing/benchmarks.rs b/common/src/utils/testing/benchmarks.rs index e2dbd41c..ec6e36ed 100644 --- a/common/src/utils/testing/benchmarks.rs +++ b/common/src/utils/testing/benchmarks.rs @@ -1,6 +1,8 @@ use std::{io, io::Write, thread, time::Instant}; #[allow(dead_code)] +/// asyncronous version of the benchmark function. will execute the function to_bench +/// `runs` times, and print the mean and standard deviation of the run times. pub fn benchmark(benchmark_name: &str, runs: usize, to_bench: fn()) { let mut time = 0usize; let mut times = Vec::with_capacity(runs); @@ -46,6 +48,8 @@ pub fn benchmark(benchmark_name: &str, runs: usize, to_bench: fn()) { } #[allow(dead_code)] +/// asyncronous version of the benchmark function. will execute the function to_bench +/// `runs` times, and print the mean and standard deviation of the run times. pub async fn async_bench(benchmark_name: &str, runs: usize, to_bench: F) where F: Fn() -> Fut, @@ -94,6 +98,7 @@ where } #[allow(dead_code)] +/// helper function to format nanoseconds into a human readable format fn format_nanos(nanos: usize) -> String { let mut nanos = nanos; let mut micros = 0; @@ -155,3 +160,22 @@ fn format_nanos(nanos: usize) -> String { result } + +#[cfg(test)] +mod tests { + use std::thread; + + use crate::utils::testing::benchmarks::benchmark; + + #[test] + fn test_benchmark() { + // Test case: Single run + let benchmark_name = "Test Benchmark"; + let runs = 10; + let to_bench = || { + // Code to benchmark + thread::sleep(std::time::Duration::from_millis(200)); + }; + benchmark(benchmark_name, runs, to_bench); + } +} diff --git a/common/src/utils/testing/mod.rs b/common/src/utils/testing/mod.rs index e5ef9c16..53b3f4db 100644 --- a/common/src/utils/testing/mod.rs +++ b/common/src/utils/testing/mod.rs @@ -1,2 +1 @@ pub mod benchmarks; -mod tests; diff --git a/common/src/utils/testing/tests.rs b/common/src/utils/testing/tests.rs deleted file mode 100644 index efdd523a..00000000 --- a/common/src/utils/testing/tests.rs +++ /dev/null @@ -1,18 +0,0 @@ -#[cfg(test)] -mod tests { - use std::thread; - - use crate::utils::testing::benchmarks::benchmark; - - #[test] - fn test_benchmark() { - // Test case: Single run - let benchmark_name = "Test Benchmark"; - let runs = 10; - let to_bench = || { - // Code to benchmark - thread::sleep(std::time::Duration::from_millis(200)); - }; - benchmark(benchmark_name, runs, to_bench); - } -} diff --git a/common/src/utils/tests.rs b/common/src/utils/tests.rs deleted file mode 100644 index 0405f714..00000000 --- a/common/src/utils/tests.rs +++ /dev/null @@ -1,553 +0,0 @@ -#[cfg(test)] -mod test_integers { - use crate::utils::integers::ToLocaleString; - - #[test] - fn test_to_locale_string() { - // Test case: Single-digit number - let num = 5; - let expected = "5".to_string(); - assert_eq!(num.to_locale_string(), expected); - - // Test case: Three-digit number - let num = 123; - let expected = "123".to_string(); - assert_eq!(num.to_locale_string(), expected); - - // Test case: Four-digit number - let num = 1234; - let expected = "1,234".to_string(); - assert_eq!(num.to_locale_string(), expected); - - // Test case: Five-digit number - let num = 12345; - let expected = "12,345".to_string(); - assert_eq!(num.to_locale_string(), expected); - - // Test case: Six-digit number - let num = 123456; - let expected = "123,456".to_string(); - assert_eq!(num.to_locale_string(), expected); - - // Test case: Seven-digit number - let num = 1234567; - let expected = "1,234,567".to_string(); - assert_eq!(num.to_locale_string(), expected); - - // Test case: Eight-digit number - let num = 12345678; - let expected = "12,345,678".to_string(); - assert_eq!(num.to_locale_string(), expected); - - // Test case: Nine-digit number - let num = 123456789; - let expected = "123,456,789".to_string(); - assert_eq!(num.to_locale_string(), expected); - } -} - -#[cfg(test)] -mod test_strings { - use ethers::types::{I256, U256}; - - use crate::utils::strings::*; - - #[test] - fn test_sign_uint() { - let unsigned = U256::from(10); - let signed = sign_uint(unsigned); - assert_eq!(signed, I256::from(10)); - - let unsigned = U256::from(0); - let signed = sign_uint(unsigned); - assert_eq!(signed, I256::from(0)); - - let unsigned = U256::from(1000); - let signed = sign_uint(unsigned); - assert_eq!(signed, I256::from(1000)); - } - - #[test] - fn test_decode_hex() { - let hex = "48656c6c6f20776f726c64"; // "Hello world" - let result = decode_hex(hex); - assert_eq!(result, Ok(vec![72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100])); - - let hex = "abcdef"; - let result = decode_hex(hex); - assert_eq!(result, Ok(vec![171, 205, 239])); - - let hex = "012345"; - let result = decode_hex(hex); - assert_eq!(result, Ok(vec![1, 35, 69])); - } - - #[test] - fn test_encode_hex() { - let bytes = vec![72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100]; // "Hello world" - let result = encode_hex(bytes); - assert_eq!(result, "48656c6c6f20776f726c64"); - - let bytes = vec![171, 205, 239]; - let result = encode_hex(bytes); - assert_eq!(result, "abcdef"); - - let bytes = vec![1, 35, 69]; - let result = encode_hex(bytes); - assert_eq!(result, "012345"); - } - - #[test] - fn test_encode_hex_reduced() { - let hex = U256::from(10); - let result = encode_hex_reduced(hex); - assert_eq!(result, "0x0a"); - - let hex = U256::from(0); - let result = encode_hex_reduced(hex); - assert_eq!(result, "0"); - - let hex = U256::from(1000); - let result = encode_hex_reduced(hex); - assert_eq!(result, "0x03e8"); - } - - #[test] - fn test_hex_to_ascii() { - let hex = "48656c6c6f20776f726c64"; // "Hello world" - let result = hex_to_ascii(hex); - assert_eq!(result, "Hello world"); - - let hex = "616263646566"; // "abcdef" - let result = hex_to_ascii(hex); - assert_eq!(result, "abcdef"); - - let hex = "303132333435"; // "012345" - let result = hex_to_ascii(hex); - assert_eq!(result, "012345"); - } - - #[test] - fn test_replace_last() { - let s = "Hello, world!"; - let old = "o"; - let new = "0"; - let result = replace_last(s, old, new); - assert_eq!(result, String::from("Hello, w0rld!")); - - let s = "Hello, world!"; - let old = "l"; - let new = "L"; - let result = replace_last(s, old, new); - assert_eq!(result, String::from("Hello, worLd!")); - } - - #[test] - fn test_find_balanced_encapsulator() { - let s = String::from("This is (an example) string."); - let encap = ('(', ')'); - let (start, end, is_balanced) = find_balanced_encapsulator(&s, encap); - assert_eq!(start, 8); - assert_eq!(end, 20); - assert!(is_balanced); - - let s = String::from("This is an example) string."); - let encap = ('(', ')'); - let (start, end, is_balanced) = find_balanced_encapsulator(&s, encap); - assert_eq!(start, 0); - assert_eq!(end, 1); - assert!(!is_balanced); - - let s = String::from("This is (an example string."); - let encap = ('(', ')'); - let (start, end, is_balanced) = find_balanced_encapsulator(&s, encap); - assert_eq!(start, 8); - assert_eq!(end, 1); - assert!(!is_balanced); - } - - #[test] - fn test_find_balanced_encapsulator_backwards() { - let s = String::from("This is (an example) string."); - let encap = ('(', ')'); - let (start, end, is_balanced) = find_balanced_encapsulator_backwards(&s, encap); - assert_eq!(start, 8); - assert_eq!(end, 20); - assert!(is_balanced); - - let s = String::from("This is an example) string."); - let encap = ('(', ')'); - let (_, _, is_balanced) = find_balanced_encapsulator_backwards(&s, encap); - assert!(!is_balanced); - - let s = String::from("This is (an example string."); - let encap = ('(', ')'); - let (_, _, is_balanced) = find_balanced_encapsulator_backwards(&s, encap); - assert!(!is_balanced); - } - - #[test] - fn test_base26_encode() { - let n = 1; - let result = base26_encode(n); - assert_eq!(result, "a"); - - let n = 26; - let result = base26_encode(n); - assert_eq!(result, "z"); - - let n = 27; - let result = base26_encode(n); - assert_eq!(result, "aa"); - - let n = 703; - let result = base26_encode(n); - assert_eq!(result, "aaa"); - } - - #[test] - fn test_split_string_by_regex() { - let input = "Hello,world!"; - let pattern = fancy_regex::Regex::new(r",").unwrap(); - let result = split_string_by_regex(input, pattern); - assert_eq!(result, vec!["Hello", "world!"]); - - let input = "This is a test."; - let pattern = fancy_regex::Regex::new(r"\s").unwrap(); - let result = split_string_by_regex(input, pattern); - assert_eq!(result, vec!["This", "is", "a", "test."]); - - let input = "The quick brown fox jumps over the lazy dog."; - let pattern = fancy_regex::Regex::new(r"\s+").unwrap(); - let result = split_string_by_regex(input, pattern); - assert_eq!( - result, - vec!["The", "quick", "brown", "fox", "jumps", "over", "the", "lazy", "dog."] - ); - } - - #[test] - fn test_extract_condition_present_balanced() { - let s = "require(arg0 == (address(arg0)));"; - let keyword = "require"; - let expected = Some("arg0 == (address(arg0))".to_string()); - assert_eq!(extract_condition(s, keyword), expected); - } - - #[test] - fn test_extract_condition_present_unbalanced() { - let s = "require(arg0 == (address(arg0));"; - let keyword = "require"; - let expected = None; - assert_eq!(extract_condition(s, keyword), expected); - } - - #[test] - fn test_extract_condition_not_present() { - let s = "if (0x01 < var_c.length) {"; - let keyword = "require"; - let expected = None; - assert_eq!(extract_condition(s, keyword), expected); - } - - #[test] - fn test_extract_condition_multiple_keywords() { - let s = "require(var_c.length == var_c.length, \"some revert message\");"; - let keyword = "require"; - let expected = Some("var_c.length == var_c.length".to_string()); - assert_eq!(extract_condition(s, keyword), expected); - } - - #[test] - fn test_extract_condition_empty_string() { - let s = ""; - let keyword = "require"; - let expected = None; - assert_eq!(extract_condition(s, keyword), expected); - } - - #[test] - fn test_tokenize_basic_operators() { - let tokens = tokenize("arg0 + arg1"); - assert_eq!(tokens, vec!["arg0", "+", "arg1"]); - } - - #[test] - fn test_tokenize_parentheses_and_operators() { - let tokens = tokenize("(arg0 + arg1) > (msg.value + 1)"); - assert_eq!( - tokens, - vec!["(", "arg0", "+", "arg1", ")", ">", "(", "msg.value", "+", "1", ")"] - ); - } - - #[test] - fn test_tokenize_multiple_operators() { - let tokens = tokenize("a >= b && c != d"); - assert_eq!(tokens, vec!["a", ">=", "b", "&&", "c", "!=", "d"]); - } - - #[test] - fn test_tokenize_no_spaces() { - let tokens = tokenize("a+b-c*d/e"); - assert_eq!(tokens, vec!["a", "+", "b", "-", "c", "*", "d", "/", "e"]); - } - - #[test] - fn test_tokenize_whitespace_only() { - let tokens = tokenize(" "); - assert_eq!(tokens, Vec::::new()); - } - - #[test] - fn test_tokenize_empty_string() { - let tokens = tokenize(""); - assert_eq!(tokens, Vec::::new()); - } - - #[test] - fn test_tokenize_complex_expression() { - let tokens = tokenize("if (x > 10 && y < 20) || z == 0 { a = b + c }"); - assert_eq!( - tokens, - vec![ - "if", "(", "x", ">", "10", "&&", "y", "<", "20", ")", "||", "z", "==", "0", "{", - "a", "=", "b", "+", "c", "}" - ] - ); - } - - #[test] - fn test_tokenize_separators_at_start_and_end() { - let tokens = tokenize("==text=="); - assert_eq!(tokens, vec!["==", "text", "=="]); - } - - #[test] - fn test_classify_token_parenthesis() { - let classification = classify_token("("); - assert_eq!(classification, TokenType::Control); - - let classification = classify_token(")"); - assert_eq!(classification, TokenType::Control); - } - - #[test] - fn test_classify_token_operators_precedence_1() { - for operator in ["+", "-"].iter() { - let classification = classify_token(operator); - assert_eq!(classification, TokenType::Operator); - } - } - - #[test] - fn test_classify_token_operators_precedence_2() { - for operator in - ["*", "/", "%", "|", "&", "^", "==", ">=", "<=", "!=", "!", "&&", "||"].iter() - { - let classification = classify_token(operator); - assert_eq!(classification, TokenType::Operator); - } - } - - #[test] - fn test_classify_token_constant() { - let classification = classify_token("0x001234567890"); - assert_eq!(classification, TokenType::Constant); - } - - #[test] - fn test_classify_token_variable() { - for variable in [ - "memory[0x01]", - "storage", - "var", - "msg.value", - "block.timestamp", - "this.balance", - "tx.origin", - "arg0", - "ret", - "calldata", - "abi.encode", - ] - .iter() - { - let classification = classify_token(variable); - assert_eq!(classification, TokenType::Variable); - } - } - - #[test] - fn test_classify_token_function() { - for function in ["uint256", "address", "ecrecover", "if"].iter() { - let classification = classify_token(function); - assert_eq!(classification, TokenType::Function); - } - } -} - -#[cfg(test)] -mod test_threading { - use crate::utils::threading::*; - - #[test] - fn test_task_pool_with_single_thread() { - // Test case with a single thread - let items = vec![1, 2, 3, 4, 5]; - let num_threads = 1; - let expected_results = vec![2, 4, 6, 8, 10]; - - // Define a simple function to double the input - let f = |x: i32| x * 2; - - let mut results = task_pool(items, num_threads, f); - results.sort(); - assert_eq!(results, expected_results); - } - - #[test] - fn test_task_pool_with_multiple_threads() { - // Test case with multiple threads - let items = vec![1, 2, 3, 4, 5]; - let num_threads = 3; - let expected_results = vec![2, 4, 6, 8, 10]; - - // Define a simple function to double the input - let f = |x: i32| x * 2; - - let mut results = task_pool(items, num_threads, f); - results.sort(); - assert_eq!(results, expected_results); - } - - #[test] - fn test_task_pool_with_empty_items() { - // Test case with empty items vector - let items: Vec = Vec::new(); - let num_threads = 2; - - // Define a simple function to double the input - let f = |x: i32| x * 2; - - let results = task_pool(items, num_threads, f); - assert!(results.is_empty()); - } -} - -#[cfg(test)] -mod test_time { - use crate::utils::time::*; - - #[test] - fn test_calculate_eta() { - assert_eq!(calculate_eta(2.5, 10), 4); - assert_eq!(calculate_eta(0.5, 100), 200); - assert_eq!(calculate_eta(1.0, 0), 0); - assert_eq!(calculate_eta(0.0, 100), std::u128::MAX); - assert_eq!(calculate_eta(10.0, std::usize::MAX), 1844674407370955264); - } - - #[test] - fn test_format_eta() { - assert_eq!(format_eta(0), "0s"); - assert_eq!(format_eta(59), "59s "); - assert_eq!(format_eta(60), "1m 0s"); - assert_eq!(format_eta(3600), "1h 0s"); - assert_eq!(format_eta(3665), "1h 1m 5s "); - assert_eq!(format_eta(86400), "1d 0s"); - assert_eq!(format_eta(172800), "2d 0s"); - assert_eq!(format_eta(180065), "2d 2h 1m 5s "); - } -} - -#[cfg(test)] -mod test_version { - use crate::utils::version::*; - - #[test] - fn test_greater_than() { - let v1 = Version { major: 2, minor: 3, patch: 4 }; - let v2 = Version { major: 2, minor: 3, patch: 3 }; - let v3 = Version { major: 2, minor: 2, patch: 5 }; - let v4 = Version { major: 1, minor: 4, patch: 4 }; - - assert!(v1.gt(&v2)); - assert!(v1.gt(&v3)); - assert!(v1.gt(&v4)); - assert!(!v2.gt(&v1)); - assert!(!v1.gt(&v1)); - } - - #[test] - fn test_greater_than_or_equal_to() { - let v1 = Version { major: 2, minor: 3, patch: 4 }; - let v2 = Version { major: 2, minor: 3, patch: 4 }; - - assert!(v1.gte(&v2)); - assert!(v2.gte(&v1)); - assert!(v1.gte(&Version { major: 1, minor: 0, patch: 0 })); - } - - #[test] - fn test_less_than() { - let v1 = Version { major: 2, minor: 3, patch: 4 }; - let v2 = Version { major: 2, minor: 3, patch: 5 }; - let v3 = Version { major: 2, minor: 4, patch: 4 }; - let v4 = Version { major: 3, minor: 3, patch: 4 }; - - assert!(v1.lt(&v2)); - assert!(v1.lt(&v3)); - assert!(v1.lt(&v4)); - assert!(!v2.lt(&v1)); - assert!(!v1.lt(&v1)); - } - - #[test] - fn test_less_than_or_equal_to() { - let v1 = Version { major: 2, minor: 3, patch: 4 }; - let v2 = Version { major: 2, minor: 3, patch: 4 }; - - assert!(v1.lte(&v2)); - assert!(v2.lte(&v1)); - assert!(v1.lte(&Version { major: 3, minor: 0, patch: 0 })); - } - - #[test] - fn test_equal_to() { - let v1 = Version { major: 2, minor: 3, patch: 4 }; - let v2 = Version { major: 2, minor: 3, patch: 4 }; - let v3 = Version { major: 2, minor: 3, patch: 5 }; - - assert!(v1.eq(&v2)); - assert!(!v1.eq(&v3)); - } - - #[test] - fn test_not_equal_to() { - let v1 = Version { major: 2, minor: 3, patch: 4 }; - let v2 = Version { major: 2, minor: 3, patch: 5 }; - let v3 = Version { major: 3, minor: 3, patch: 4 }; - - assert!(v1.ne(&v2)); - assert!(v1.ne(&v3)); - assert!(!v1.ne(&Version { major: 2, minor: 3, patch: 4 })); - } - - #[test] - fn test_version_display() { - let version = Version { major: 2, minor: 3, patch: 4 }; - - assert_eq!(version.to_string(), "2.3.4"); - } - - #[test] - fn test_version_current() { - let version = current_version(); - - assert_eq!(version.to_string(), env!("CARGO_PKG_VERSION")); - } - - #[tokio::test] - async fn test_version_remote() {} -} diff --git a/common/src/utils/threading.rs b/common/src/utils/threading.rs index 43595da5..0c7fe456 100644 --- a/common/src/utils/threading.rs +++ b/common/src/utils/threading.rs @@ -1,6 +1,17 @@ use crossbeam_channel::unbounded; use std::{sync::Arc, thread}; +/// A simple thread pool implementation that takes a vector of items, splits them into chunks, and +/// processes each chunk in a separate thread. The results are collected and returned. +/// +/// ``` +/// use heimdall_common::utils::threading::task_pool; +/// +/// let items = vec![1, 2, 3, 4, 5]; +/// let num_threads = 2; +/// let results = task_pool(items, num_threads, |item| item * 2); +/// assert_eq!(results, vec![2, 4, 6, 8, 10]); +/// ``` pub fn task_pool< T: Clone + Send + Sync + 'static, R: Send + 'static, @@ -51,3 +62,51 @@ pub fn task_pool< results } + +#[cfg(test)] +mod tests { + use crate::utils::threading::*; + + #[test] + fn test_task_pool_with_single_thread() { + // Test case with a single thread + let items = vec![1, 2, 3, 4, 5]; + let num_threads = 1; + let expected_results = vec![2, 4, 6, 8, 10]; + + // Define a simple function to double the input + let f = |x: i32| x * 2; + + let mut results = task_pool(items, num_threads, f); + results.sort(); + assert_eq!(results, expected_results); + } + + #[test] + fn test_task_pool_with_multiple_threads() { + // Test case with multiple threads + let items = vec![1, 2, 3, 4, 5]; + let num_threads = 3; + let expected_results = vec![2, 4, 6, 8, 10]; + + // Define a simple function to double the input + let f = |x: i32| x * 2; + + let mut results = task_pool(items, num_threads, f); + results.sort(); + assert_eq!(results, expected_results); + } + + #[test] + fn test_task_pool_with_empty_items() { + // Test case with empty items vector + let items: Vec = Vec::new(); + let num_threads = 2; + + // Define a simple function to double the input + let f = |x: i32| x * 2; + + let results = task_pool(items, num_threads, f); + assert!(results.is_empty()); + } +} diff --git a/common/src/utils/time.rs b/common/src/utils/time.rs index 292125f1..59244c1d 100644 --- a/common/src/utils/time.rs +++ b/common/src/utils/time.rs @@ -1,11 +1,25 @@ use chrono::Local; /// Calculate the ETA for a process based on the number of items processed per second +/// +/// ``` +/// use heimdall_common::utils::time::calculate_eta; +/// +/// let eta = calculate_eta(1000.0, 1000); +/// assert_eq!(eta, 1); +/// ``` pub fn calculate_eta(items_per_second: f64, items_remaining: usize) -> u128 { (items_remaining as f64 / items_per_second) as u128 } /// Format seconds into a human readable ETA +/// +/// ``` +/// use heimdall_common::utils::time::format_eta; +/// +/// let eta = format_eta(1000); +/// assert_eq!(eta, "16m 40s "); +/// ``` pub fn format_eta(seconds_remaining: u128) -> String { let days = seconds_remaining / 86400; let hours = (seconds_remaining % 86400) / 3600; @@ -22,7 +36,39 @@ pub fn format_eta(seconds_remaining: u128) -> String { } /// Get the current timestamp in a pretty format +/// +/// ``` +/// use heimdall_common::utils::time::pretty_timestamp; +/// +/// let timestamp = pretty_timestamp(); +/// ``` pub fn pretty_timestamp() -> String { let now = Local::now(); now.format("%d-%m-%Y %H:%M:%S.%f").to_string() } + +#[cfg(test)] +mod tests { + use crate::utils::time::*; + + #[test] + fn test_calculate_eta() { + assert_eq!(calculate_eta(2.5, 10), 4); + assert_eq!(calculate_eta(0.5, 100), 200); + assert_eq!(calculate_eta(1.0, 0), 0); + assert_eq!(calculate_eta(0.0, 100), std::u128::MAX); + assert_eq!(calculate_eta(10.0, std::usize::MAX), 1844674407370955264); + } + + #[test] + fn test_format_eta() { + assert_eq!(format_eta(0), "0s"); + assert_eq!(format_eta(59), "59s "); + assert_eq!(format_eta(60), "1m 0s"); + assert_eq!(format_eta(3600), "1h 0s"); + assert_eq!(format_eta(3665), "1h 1m 5s "); + assert_eq!(format_eta(86400), "1d 0s"); + assert_eq!(format_eta(172800), "2d 0s"); + assert_eq!(format_eta(180065), "2d 2h 1m 5s "); + } +} diff --git a/common/src/utils/version.rs b/common/src/utils/version.rs index b5539a27..30dc24d6 100644 --- a/common/src/utils/version.rs +++ b/common/src/utils/version.rs @@ -9,6 +9,7 @@ pub struct Version { pub patch: u32, } +/// get the current version from cargo pub fn current_version() -> Version { // get the current version from the cargo package let version_string = env!("CARGO_PKG_VERSION"); @@ -21,6 +22,7 @@ pub fn current_version() -> Version { } } +/// get the latest version from github pub async fn remote_version() -> Version { // get the latest release from github let remote_repository_url = @@ -54,41 +56,132 @@ impl Display for Version { } impl Version { - // greater than + /// greater than pub fn gt(&self, other: &Version) -> bool { self.major > other.major || (self.major == other.major && self.minor > other.minor) || (self.major == other.major && self.minor == other.minor && self.patch > other.patch) } - // greater than or equal to + /// greater than or equal to pub fn gte(&self, other: &Version) -> bool { self.major > other.major || (self.major == other.major && self.minor > other.minor) || (self.major == other.major && self.minor == other.minor && self.patch >= other.patch) } - // less than + /// less than pub fn lt(&self, other: &Version) -> bool { self.major < other.major || (self.major == other.major && self.minor < other.minor) || (self.major == other.major && self.minor == other.minor && self.patch < other.patch) } - // less than or equal to + /// less than or equal to pub fn lte(&self, other: &Version) -> bool { self.major < other.major || (self.major == other.major && self.minor < other.minor) || (self.major == other.major && self.minor == other.minor && self.patch <= other.patch) } - // equal to + /// equal to pub fn eq(&self, other: &Version) -> bool { self.major == other.major && self.minor == other.minor && self.patch == other.patch } - // not equal to + /// not equal to pub fn ne(&self, other: &Version) -> bool { self.major != other.major || self.minor != other.minor || self.patch != other.patch } } + +#[cfg(test)] +mod tests { + use crate::utils::version::*; + + #[test] + fn test_greater_than() { + let v1 = Version { major: 2, minor: 3, patch: 4 }; + let v2 = Version { major: 2, minor: 3, patch: 3 }; + let v3 = Version { major: 2, minor: 2, patch: 5 }; + let v4 = Version { major: 1, minor: 4, patch: 4 }; + + assert!(v1.gt(&v2)); + assert!(v1.gt(&v3)); + assert!(v1.gt(&v4)); + assert!(!v2.gt(&v1)); + assert!(!v1.gt(&v1)); + } + + #[test] + fn test_greater_than_or_equal_to() { + let v1 = Version { major: 2, minor: 3, patch: 4 }; + let v2 = Version { major: 2, minor: 3, patch: 4 }; + + assert!(v1.gte(&v2)); + assert!(v2.gte(&v1)); + assert!(v1.gte(&Version { major: 1, minor: 0, patch: 0 })); + } + + #[test] + fn test_less_than() { + let v1 = Version { major: 2, minor: 3, patch: 4 }; + let v2 = Version { major: 2, minor: 3, patch: 5 }; + let v3 = Version { major: 2, minor: 4, patch: 4 }; + let v4 = Version { major: 3, minor: 3, patch: 4 }; + + assert!(v1.lt(&v2)); + assert!(v1.lt(&v3)); + assert!(v1.lt(&v4)); + assert!(!v2.lt(&v1)); + assert!(!v1.lt(&v1)); + } + + #[test] + fn test_less_than_or_equal_to() { + let v1 = Version { major: 2, minor: 3, patch: 4 }; + let v2 = Version { major: 2, minor: 3, patch: 4 }; + + assert!(v1.lte(&v2)); + assert!(v2.lte(&v1)); + assert!(v1.lte(&Version { major: 3, minor: 0, patch: 0 })); + } + + #[test] + fn test_equal_to() { + let v1 = Version { major: 2, minor: 3, patch: 4 }; + let v2 = Version { major: 2, minor: 3, patch: 4 }; + let v3 = Version { major: 2, minor: 3, patch: 5 }; + + assert!(v1.eq(&v2)); + assert!(!v1.eq(&v3)); + } + + #[test] + fn test_not_equal_to() { + let v1 = Version { major: 2, minor: 3, patch: 4 }; + let v2 = Version { major: 2, minor: 3, patch: 5 }; + let v3 = Version { major: 3, minor: 3, patch: 4 }; + + assert!(v1.ne(&v2)); + assert!(v1.ne(&v3)); + assert!(!v1.ne(&Version { major: 2, minor: 3, patch: 4 })); + } + + #[test] + fn test_version_display() { + let version = Version { major: 2, minor: 3, patch: 4 }; + + assert_eq!(version.to_string(), "2.3.4"); + } + + #[test] + fn test_version_current() { + let version = current_version(); + + assert_eq!(version.to_string(), env!("CARGO_PKG_VERSION")); + } + + #[tokio::test] + async fn test_version_remote() {} +}