From c95fe8e202f470e79123bf4c992f779df249ec42 Mon Sep 17 00:00:00 2001 From: Alex Solomes Date: Thu, 9 Jun 2022 17:09:06 +0100 Subject: Testing utility: TempTedgeDir POC (#1148) * temp tedge dir poc Signed-off-by: initard * renaming crate and removing TedgeChildTempDir - renamed the crate to tedge_test_utils - removed TedgeChildTempDir as TempTedgeDir already had the same functions Signed-off-by: Alex Solomes * changing agent.rs tests to use TempTedgeDir - changing agent.rs to use TempTedgeDir - un-ignoring a test to check if agent restart creates a file in the right place Signed-off-by: Alex Solomes * changing tedge_mapper tests to use TempTedgeDir Signed-off-by: Alex Solomes * changing tedge_config tests to use TempTedgeDir Signed-off-by: Alex Solomes * changing c8y_configuration_plugin tests to use TempTedgeDir Signed-off-by: Alex Solomes * changing tedge_apama_plugin tests to use TempTedgeDir Signed-off-by: Alex Solomes * changing logged_command tests to use TempTedgeDir Signed-off-by: Alex Solomes * adding another method to TempTedgeDir Signed-off-by: initard Co-authored-by: initard --- crates/common/logged_command/Cargo.toml | 2 +- crates/common/logged_command/src/logged_command.rs | 25 +-- crates/common/tedge_config/Cargo.toml | 2 +- .../common/tedge_config/tests/test_tedge_config.rs | 9 +- crates/core/tedge_agent/Cargo.toml | 2 +- crates/core/tedge_agent/src/agent.rs | 110 +++++------ crates/core/tedge_agent/src/state.rs | 63 +++---- crates/core/tedge_mapper/Cargo.toml | 2 +- crates/core/tedge_mapper/src/c8y/converter.rs | 4 +- .../core/tedge_mapper/src/c8y/dynamic_discovery.rs | 6 +- crates/core/tedge_mapper/src/c8y/mapper.rs | 6 +- crates/core/tedge_mapper/src/c8y/tests.rs | 98 +++++----- crates/tests/tedge_test_utils/Cargo.toml | 13 ++ crates/tests/tedge_test_utils/src/fs.rs | 204 +++++++++++++++++++++ crates/tests/tedge_test_utils/src/lib.rs | 1 + 15 files changed, 382 insertions(+), 165 deletions(-) create mode 100644 crates/tests/tedge_test_utils/Cargo.toml create mode 100644 crates/tests/tedge_test_utils/src/fs.rs create mode 100644 crates/tests/tedge_test_utils/src/lib.rs (limited to 'crates') diff --git a/crates/common/logged_command/Cargo.toml b/crates/common/logged_command/Cargo.toml index 2e026e9d..9a9b9442 100644 --- a/crates/common/logged_command/Cargo.toml +++ b/crates/common/logged_command/Cargo.toml @@ -16,5 +16,5 @@ tokio = { version = "1.8", features = [ "fs", "io-util", "macros", "process", "r anyhow = "1.0" assert_matches = "1.5" serial_test = "0.6" -tempfile = "3.2" +tedge_test_utils = { path = "../../tests/tedge_test_utils" } test-case = "2.0" diff --git a/crates/common/logged_command/src/logged_command.rs b/crates/common/logged_command/src/logged_command.rs index ef40ab7d..e276b495 100644 --- a/crates/common/logged_command/src/logged_command.rs +++ b/crates/common/logged_command/src/logged_command.rs @@ -141,16 +141,17 @@ impl LoggedCommand { #[cfg(test)] mod tests { use super::*; - use tempfile::*; + use tedge_test_utils::fs::TempTedgeDir; use tokio::fs::File; #[tokio::test] async fn on_execute_are_logged_command_line_exit_status_stdout_and_stderr( ) -> Result<(), anyhow::Error> { // Prepare a log file - let tmp_dir = TempDir::new()?; - let log_file_path = tmp_dir.path().join("operation.log"); - let log_file = File::create(log_file_path.clone()).await?; + let tmp_dir = TempTedgeDir::new(); + let tmp_file = tmp_dir.file("operation.log"); + let log_file_path = tmp_file.path(); + let log_file = File::create(&log_file_path).await?; let mut logger = BufWriter::new(log_file); // Prepare a command @@ -160,7 +161,7 @@ mod tests { // Execute the command with logging let _ = command.execute(&mut logger).await; - let log_content = String::from_utf8(std::fs::read(&log_file_path)?)?; + let log_content = String::from_utf8(std::fs::read(log_file_path)?)?; assert_eq!( log_content, r#"----- $ echo "Hello" "World!" @@ -180,9 +181,10 @@ EOF #[tokio::test] async fn on_execute_with_error_stderr_is_logged() -> Result<(), anyhow::Error> { // Prepare a log file - let tmp_dir = TempDir::new()?; - let log_file_path = tmp_dir.path().join("operation.log"); - let log_file = File::create(log_file_path.clone()).await?; + let tmp_dir = TempTedgeDir::new(); + let tmp_file = tmp_dir.file("operation.log"); + let log_file_path = tmp_file.path(); + let log_file = File::create(&log_file_path).await?; let mut logger = BufWriter::new(log_file); // Prepare a command that triggers some content on stderr @@ -213,9 +215,10 @@ EOF #[tokio::test] async fn on_execution_error_are_logged_command_line_and_error() -> Result<(), anyhow::Error> { // Prepare a log file - let tmp_dir = TempDir::new()?; - let log_file_path = tmp_dir.path().join("operation.log"); - let log_file = File::create(log_file_path.clone()).await?; + let tmp_dir = TempTedgeDir::new(); + let tmp_file = tmp_dir.file("operation.log"); + let log_file_path = tmp_file.path(); + let log_file = File::create(&log_file_path).await?; let mut logger = BufWriter::new(log_file); // Prepare a command that cannot be executed diff --git a/crates/common/tedge_config/Cargo.toml b/crates/common/tedge_config/Cargo.toml index d71f83d1..9b73955f 100644 --- a/crates/common/tedge_config/Cargo.toml +++ b/crates/common/tedge_config/Cargo.toml @@ -17,4 +17,4 @@ url = "2.2" [dev-dependencies] assert_matches = "1.5" -tempfile = "3.2" +tedge_test_utils = { path = "../../tests/tedge_test_utils" } diff --git a/crates/common/tedge_config/tests/test_tedge_config.rs b/crates/common/tedge_config/tests/test_tedge_config.rs index 86c89615..1dc3aa4f 100644 --- a/crates/common/tedge_config/tests/test_tedge_config.rs +++ b/crates/common/tedge_config/tests/test_tedge_config.rs @@ -3,7 +3,7 @@ use std::convert::TryFrom; use std::io::Write; use std::net::{IpAddr, Ipv4Addr}; use tedge_config::*; -use tempfile::TempDir; +use tedge_test_utils::fs::TempTedgeDir; #[test] fn test_parse_config_with_all_values() -> Result<(), TEdgeConfigError> { @@ -878,11 +878,10 @@ cert_path = "/path/to/cert" Ok(()) } -fn create_temp_tedge_config(content: &str) -> std::io::Result<(TempDir, TEdgeConfigLocation)> { - let dir = TempDir::new()?; +fn create_temp_tedge_config(content: &str) -> std::io::Result<(TempTedgeDir, TEdgeConfigLocation)> { + let dir = TempTedgeDir::new(); + dir.file("tedge.toml").with_raw_content(content); let config_location = TEdgeConfigLocation::from_custom_root(dir.path()); - let mut file = std::fs::File::create(config_location.tedge_config_file_path())?; - file.write_all(content.as_bytes())?; Ok((dir, config_location)) } diff --git a/crates/core/tedge_agent/Cargo.toml b/crates/core/tedge_agent/Cargo.toml index 5fd8177e..4197ad17 100644 --- a/crates/core/tedge_agent/Cargo.toml +++ b/crates/core/tedge_agent/Cargo.toml @@ -50,6 +50,6 @@ mqtt_tests = { path = "../../tests/mqtt_tests" } predicates = "2.1" tedge_users = { path = "../../common/tedge_users"} tedge_utils = { path = "../../common/tedge_utils"} -tempfile = "3.2" +tedge_test_utils = { path = "../../tests/tedge_test_utils"} tokio-test = "0.4" serial_test = "0.6" diff --git a/crates/core/tedge_agent/src/agent.rs b/crates/core/tedge_agent/src/agent.rs index 755fc7af..8fe3e18a 100644 --- a/crates/core/tedge_agent/src/agent.rs +++ b/crates/core/tedge_agent/src/agent.rs @@ -19,7 +19,7 @@ use plugin_sm::{ plugin_manager::{ExternalPlugins, Plugins}, }; use serde_json::json; -use std::process; +use std::process::{self, Command}; use std::{convert::TryInto, fmt::Debug, path::PathBuf, sync::Arc}; use tedge_config::{ ConfigRepository, ConfigSettingAccessor, ConfigSettingAccessorStringExt, LogPathSetting, @@ -555,20 +555,17 @@ impl SmAgent { .await?; let () = restart_operation::create_slash_run_file(&self.config.run_dir)?; - let _process_result = std::process::Command::new("sudo").arg("sync").status(); - // state = "Restarting" - match std::process::Command::new("sudo") - .arg(INIT_COMMAND) - .arg("6") - .status() - { - Ok(process_status) => { - if !process_status.success() { - return Err(AgentError::CommandFailed); + let command_vec = get_restart_operation_commands(); + for mut command in command_vec { + match command.status() { + Ok(status) => { + if !status.success() { + return Err(AgentError::CommandFailed); + } + } + Err(e) => { + return Err(AgentError::FromIo(e)); } - } - Err(e) => { - return Err(AgentError::FromIo(e)); } } @@ -631,6 +628,30 @@ impl SmAgent { } } +#[cfg(test)] +fn get_restart_operation_commands() -> Vec { + let mut vec = vec![]; + // running `echo 6` with no sudo + let mut command = std::process::Command::new(INIT_COMMAND); + command.arg("6"); + vec.push(command); + vec +} + +#[cfg(not(test))] +fn get_restart_operation_commands() -> Vec { + let mut vec = vec![]; + // sync first + let mut sync_command = std::process::Command::new("sudo"); + sync_command.arg("sync"); + vec.push(sync_command); + // running `sudo init 6` + let mut command = std::process::Command::new("sudo"); + command.arg(INIT_COMMAND).arg("6"); + vec.push(command); + vec +} + fn get_default_plugin( config_location: &TEdgeConfigLocation, ) -> Result, AgentError> { @@ -643,7 +664,6 @@ fn get_default_plugin( #[cfg(test)] mod tests { - use std::io::Write; use std::path::PathBuf; use assert_json_diff::assert_json_include; @@ -651,9 +671,10 @@ mod tests { use super::*; + use tedge_test_utils::fs::TempTedgeDir; + const SLASH_RUN_PATH_TEDGE_AGENT_RESTART: &str = "tedge_agent/tedge_agent_restart"; - #[ignore] #[tokio::test] async fn check_agent_restart_file_is_created() -> Result<(), AgentError> { assert_eq!(INIT_COMMAND, "echo"); @@ -666,19 +687,19 @@ mod tests { .unwrap(); // calling handle_restart_operation should create a file in /run/tedge_agent_restart - let (_, mut output_stream) = mqtt_tests::output_stream(); + let (_output, mut output_stream) = mqtt_tests::output_stream(); let response_topic_restart = Topic::new(RestartOperationResponse::topic_name()).expect("Invalid topic"); let () = agent .handle_restart_operation(&mut output_stream, &response_topic_restart) .await?; - assert!( - std::path::Path::new(&dir.path().join(SLASH_RUN_PATH_TEDGE_AGENT_RESTART)).exists() - ); - - // removing the file - let () = - std::fs::remove_file(&dir.path().join(SLASH_RUN_PATH_TEDGE_AGENT_RESTART)).unwrap(); + assert!(std::path::Path::new( + &dir.temp_dir + .path() + .join("run/") + .join(SLASH_RUN_PATH_TEDGE_AGENT_RESTART) + ) + .exists()); Ok(()) } @@ -689,39 +710,26 @@ mod tests { Message::new(&topic, payload) } - fn create_temp_tedge_config() -> std::io::Result<(tempfile::TempDir, TEdgeConfigLocation)> { - let dir = tempfile::TempDir::new()?; - - let dir_path = dir.path().join(".agent"); - std::fs::create_dir(&dir_path).unwrap(); - - let () = { - let _file = std::fs::File::create(dir.path().join(".agent/current-operation")).unwrap(); - }; - - let dir_path = dir.path().join("sm-plugins"); - std::fs::create_dir(dir_path).unwrap(); - - let dir_path = dir.path().join("lock"); - std::fs::create_dir(dir_path).unwrap(); - - let dir_path = dir.path().join("logs"); - std::fs::create_dir(dir_path).unwrap(); - + fn create_temp_tedge_config() -> std::io::Result<(TempTedgeDir, TEdgeConfigLocation)> { + let ttd = TempTedgeDir::new(); + ttd.dir(".agent").file("current-operation"); + ttd.dir("sm-plugins"); + ttd.dir("logs"); + ttd.dir("run").dir("tedge_agent"); + ttd.dir("run").dir("lock"); let toml_conf = &format!( r#" [logs] path = '{}' [run] path = '{}'"#, - &dir.path().join("logs").to_str().unwrap(), - &dir.path().to_str().unwrap() + &ttd.temp_dir.path().join("logs").to_str().unwrap(), + &ttd.temp_dir.path().join("run").to_str().unwrap() ); + ttd.file("tedge.toml").with_raw_content(toml_conf); - let config_location = TEdgeConfigLocation::from_custom_root(dir.path()); - let mut file = std::fs::File::create(config_location.tedge_config_file_path())?; - file.write_all(toml_conf.as_bytes())?; - Ok((dir, config_location)) + let config_location = TEdgeConfigLocation::from_custom_root(ttd.temp_dir.path()); + Ok((ttd, config_location)) } #[tokio::test] @@ -752,7 +760,7 @@ mod tests { let plugins = Arc::new(Mutex::new( ExternalPlugins::open( - PathBuf::from(&dir.path()).join("sm-plugins"), + PathBuf::from(&dir.temp_dir.path()).join("sm-plugins"), get_default_plugin(&agent.config.config_location).unwrap(), Some("sudo".into()), ) @@ -796,7 +804,7 @@ mod tests { let plugins = Arc::new(Mutex::new( ExternalPlugins::open( - PathBuf::from(&dir.path()).join("sm-plugins"), + PathBuf::from(&dir.temp_dir.path()).join("sm-plugins"), get_default_plugin(&agent.config.config_location).unwrap(), Some("sudo".into()), ) diff --git a/crates/core/tedge_agent/src/state.rs b/crates/core/tedge_agent/src/state.rs index b7819448..36b378dd 100644 --- a/crates/core/tedge_agent/src/state.rs +++ b/crates/core/tedge_agent/src/state.rs @@ -123,28 +123,26 @@ mod tests { StateRepository, StateStatus, }; - use tempfile::tempdir; + use tedge_test_utils::fs::TempTedgeDir; #[tokio::test] async fn agent_state_repository_not_exists_fail() { - let temp_dir = tempdir().unwrap(); - let repo = AgentStateRepository::new(temp_dir.into_path()); + let temp_dir = TempTedgeDir::new(); + let repo = AgentStateRepository::new(temp_dir.path().to_path_buf()); repo.load().await.unwrap_err(); } #[tokio::test] async fn agent_state_repository_exists_loads_some() { - let temp_dir = tempdir().unwrap(); - - let _ = tokio::fs::create_dir(temp_dir.path().join(".agent/")).await; - let destination_path = temp_dir.path().join(".agent/current-operation"); - + let temp_dir = TempTedgeDir::new(); let content = "operation_id = \'1234\'\noperation = \"list\""; + temp_dir + .dir(".agent") + .file("current-operation") + .with_raw_content(content); - let _ = tokio::fs::write(destination_path, content.as_bytes()).await; - - let repo = AgentStateRepository::new(temp_dir.into_path()); + let repo = AgentStateRepository::new(temp_dir.path().to_path_buf()); let data = repo.load().await.unwrap(); assert_eq!( @@ -158,16 +156,14 @@ mod tests { #[tokio::test] async fn agent_state_repository_exists_loads_some_restart_variant() { - let temp_dir = tempdir().unwrap(); - - let _ = tokio::fs::create_dir(temp_dir.path().join(".agent/")).await; - let destination_path = temp_dir.path().join(".agent/current-operation"); - + let temp_dir = TempTedgeDir::new(); let content = "operation_id = \'1234\'\noperation = \"Restarting\""; + temp_dir + .dir(".agent") + .file("current-operation") + .with_raw_content(content); - let _ = tokio::fs::write(destination_path, content.as_bytes()).await; - - let repo = AgentStateRepository::new(temp_dir.into_path()); + let repo = AgentStateRepository::new(temp_dir.path().to_path_buf()); let data = repo.load().await.unwrap(); assert_eq!( @@ -181,16 +177,14 @@ mod tests { #[tokio::test] async fn agent_state_repository_exists_loads_none() { - let temp_dir = tempdir().unwrap(); - - let _ = tokio::fs::create_dir(temp_dir.path().join(".agent/")).await; - let destination_path = temp_dir.path().join(".agent/current-operation"); - + let temp_dir = TempTedgeDir::new(); let content = ""; + temp_dir + .dir(".agent") + .file("current-operation") + .with_raw_content(content); - let _ = tokio::fs::write(destination_path, content.as_bytes()).await; - - let repo = AgentStateRepository::new(temp_dir.into_path()); + let repo = AgentStateRepository::new(temp_dir.path().to_path_buf()); let data = repo.load().await.unwrap(); assert_eq!( @@ -204,12 +198,10 @@ mod tests { #[tokio::test] async fn agent_state_repository_exists_store() { - let temp_dir = tempdir().unwrap(); + let temp_dir = TempTedgeDir::new(); + temp_dir.dir(".agent").file("current-operation"); - let _ = tokio::fs::create_dir(temp_dir.path().join(".agent/")).await; - let destination_path = temp_dir.path().join(".agent/current-operation"); - - let repo = AgentStateRepository::new(temp_dir.into_path()); + let repo = AgentStateRepository::new(temp_dir.path().to_path_buf()); repo.store(&State { operation_id: Some("1234".into()), @@ -218,7 +210,12 @@ mod tests { .await .unwrap(); - let data = tokio::fs::read_to_string(destination_path).await.unwrap(); + let data = tokio::fs::read_to_string(&format!( + "{}/.agent/current-operation", + &temp_dir.temp_dir.path().to_str().unwrap() + )) + .await + .unwrap(); assert_eq!(data, "operation_id = \'1234\'\noperation = \'list\'\n"); } diff --git a/crates/core/tedge_mapper/Cargo.toml b/crates/core/tedge_mapper/Cargo.toml index a7876bdf..d7464294 100644 --- a/crates/core/tedge_mapper/Cargo.toml +++ b/crates/core/tedge_mapper/Cargo.toml @@ -64,7 +64,7 @@ mockito = "0.31" mqtt_tests = { path = "../../tests/mqtt_tests" } serde_json = "1.0" serial_test = "0.6" -tempfile = "3.2" +tedge_test_utils = { path = "../../tests/tedge_test_utils" } test-case = "2.0" time = { version = "0.3", features = ["macros"] } tokio-test = "0.4" diff --git a/crates/core/tedge_mapper/src/c8y/converter.rs b/crates/core/tedge_mapper/src/c8y/converter.rs index a7468ebe..9ec58cb0 100644 --- a/crates/core/tedge_mapper/src/c8y/converter.rs +++ b/crates/core/tedge_mapper/src/c8y/converter.rs @@ -837,11 +837,11 @@ pub fn get_child_id_from_topic(topic: &str) -> Result, Conversion #[cfg(test)] mod tests { use plugin_sm::operation_logs::OperationLogs; - use tempfile::TempDir; + use tedge_test_utils::fs::TempTedgeDir; #[tokio::test] async fn test_execute_operation_is_not_blocked() { - let log_dir = TempDir::new().unwrap(); + let log_dir = TempTedgeDir::new(); let operation_logs = OperationLogs::try_new(log_dir.path().to_path_buf()).unwrap(); let now = std::time::Instant::now(); diff --git a/crates/core/tedge_mapper/src/c8y/dynamic_discovery.rs b/crates/core/tedge_mapper/src/c8y/dynamic_discovery.rs index 535f396b..fca9057d 100644 --- a/crates/core/tedge_mapper/src/c8y/dynamic_discovery.rs +++ b/crates/core/tedge_mapper/src/c8y/dynamic_discovery.rs @@ -94,8 +94,8 @@ fn create_inotify_with_non_existing_dir() { #[test] fn create_inotify_with_right_directory() { - use tempfile::TempDir; - let dir = TempDir::new().unwrap().into_path(); - let res = create_inotify_watch(dir); + use tedge_test_utils::fs::TempTedgeDir; + let dir = TempTedgeDir::new(); + let res = create_inotify_watch(dir.path().to_path_buf()); assert!(res.is_ok()); } diff --git a/crates/core/tedge_mapper/src/c8y/mapper.rs b/crates/core/tedge_mapper/src/c8y/mapper.rs index 24dfde84..8ea3b4fc 100644 --- a/crates/core/tedge_mapper/src/c8y/mapper.rs +++ b/crates/core/tedge_mapper/src/c8y/mapper.rs @@ -130,7 +130,7 @@ mod tests { use mqtt_tests::{assert_received_all_expected, test_mqtt_broker}; use serde_json::json; use std::time::Duration; - use tempfile::TempDir; + use tedge_test_utils::fs::TempTedgeDir; use test_case::test_case; const TEST_TIMEOUT_SECS: Duration = Duration::from_secs(5); @@ -188,7 +188,7 @@ mod tests { let size_threshold = SizeThreshold(MQTT_MESSAGE_SIZE_THRESHOLD); let operations = Operations::default(); - let tmp_dir = TempDir::new().unwrap(); + let tmp_dir = TempTedgeDir::new(); let converter = Box::new( CumulocityConverter::from_logs_path( size_threshold, @@ -196,7 +196,7 @@ mod tests { DEVICE_TYPE.into(), operations, proxy, - tmp_dir.into_path(), + tmp_dir.path().to_path_buf(), ) .unwrap(), ); diff --git a/crates/core/tedge_mapper/src/c8y/tests.rs b/crates/core/tedge_mapper/src/c8y/tests.rs index 8596f594..78eee84c 100644 --- a/crates/core/tedge_mapper/src/c8y/tests.rs +++ b/crates/core/tedge_mapper/src/c8y/tests.rs @@ -19,7 +19,7 @@ use mqtt_tests::test_mqtt_server::MqttProcessHandler; use serde_json::json; use serial_test::serial; use std::{path::Path, time::Duration}; -use tempfile::TempDir; +use tedge_test_utils::fs::TempTedgeDir; use test_case::test_case; use tokio::task::JoinHandle; @@ -39,12 +39,12 @@ async fn mapper_publishes_a_software_list_request() { .await; // Start the SM Mapper - let sm_mapper = start_c8y_mapper(broker.port).await; + let (_tmp_dir, sm_mapper) = start_c8y_mapper(broker.port).await.unwrap(); // Expect on `tedge/commands/req/software/list` a software list request. mqtt_tests::assert_received_all_expected(&mut messages, TEST_TIMEOUT_MS, &[r#"{"id":"#]).await; - sm_mapper.unwrap().abort(); + sm_mapper.abort(); } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] @@ -55,12 +55,12 @@ async fn mapper_publishes_a_supported_operation_and_a_pending_operations_onto_c8 let mut messages = broker.messages_published_on("c8y/s/us").await; // Start SM Mapper - let sm_mapper = start_c8y_mapper(broker.port).await; + let (_tmp_dir, sm_mapper) = start_c8y_mapper(broker.port).await.unwrap(); // Expect 500 messages has been received on `c8y/s/us`, if no msg received for the timeout the test fails. mqtt_tests::assert_received_all_expected(&mut messages, TEST_TIMEOUT_MS, &["500\n"]).await; - sm_mapper.unwrap().abort(); + sm_mapper.abort(); } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] @@ -73,8 +73,7 @@ async fn mapper_publishes_software_update_request() { .messages_published_on("tedge/commands/req/software/update") .await; - let sm_mapper = start_c8y_mapper(broker.port).await; - + let (_tmp_dir, sm_mapper) = start_c8y_mapper(broker.port).await.unwrap(); // Prepare and publish a software update smartrest request on `c8y/s/ds`. let smartrest = r#"528,external_id,nodered,1.0.0::debian,,install"#; let _ = broker.publish("c8y/s/ds", smartrest).await.unwrap(); @@ -101,7 +100,7 @@ async fn mapper_publishes_software_update_request() { ) .await; - sm_mapper.unwrap().abort(); + sm_mapper.abort(); } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] @@ -114,7 +113,7 @@ async fn mapper_publishes_software_update_status_onto_c8y_topic() { let mut messages = broker.messages_published_on("c8y/s/us").await; // Start SM Mapper - let sm_mapper = start_c8y_mapper(broker.port).await; + let (_tmp_dir, sm_mapper) = start_c8y_mapper(broker.port).await.unwrap(); let _ = publish_a_fake_jwt_token(broker).await; // Prepare and publish a software update status response message `executing` on `tedge/commands/res/software/update`. @@ -159,7 +158,7 @@ async fn mapper_publishes_software_update_status_onto_c8y_topic() { ) .await; - sm_mapper.unwrap().abort(); + sm_mapper.abort(); } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] @@ -169,7 +168,7 @@ async fn mapper_publishes_software_update_failed_status_onto_c8y_topic() { let mut messages = broker.messages_published_on("c8y/s/us").await; // Start SM Mapper - let sm_mapper = start_c8y_mapper(broker.port).await; + let (_tmp_dir, sm_mapper) = start_c8y_mapper(broker.port).await.unwrap(); let _ = publish_a_fake_jwt_token(broker).await; // The agent publish an error @@ -206,7 +205,7 @@ async fn mapper_publishes_software_update_failed_status_onto_c8y_topic() { ) .await; - sm_mapper.unwrap().abort(); + sm_mapper.abort(); } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] @@ -232,7 +231,7 @@ async fn mapper_fails_during_sw_update_recovers_and_process_response() -> Result let mut responses = broker.messages_published_on("c8y/s/us").await; // Start SM Mapper - let sm_mapper = start_c8y_mapper(broker.port).await?; + let (_tmp_dir, sm_mapper) = start_c8y_mapper(broker.port).await.unwrap(); // Prepare and publish a software update smartrest request on `c8y/s/ds`. let smartrest = r#"528,external_id,nodered,1.0.0::debian,,install"#; @@ -288,7 +287,7 @@ async fn mapper_fails_during_sw_update_recovers_and_process_response() -> Result .unwrap(); // Restart SM Mapper - let sm_mapper = start_c8y_mapper(broker.port).await?; + let (_tmp_dir, sm_mapper) = start_c8y_mapper(broker.port).await.unwrap(); // Validate that the mapper process the response and forward it on 'c8y/s/us' // Expect init messages followed by a 503 (success) @@ -317,8 +316,7 @@ async fn mapper_publishes_software_update_request_with_wrong_action() { // Create a subscriber to receive messages on `c8y/s/us` topic. let mut messages = broker.messages_published_on("c8y/s/us").await; - let _sm_mapper = start_c8y_mapper(broker.port).await; - + let (_tmp_dir, _sm_mapper) = start_c8y_mapper(broker.port).await.unwrap(); // Prepare and publish a c8y_SoftwareUpdate smartrest request on `c8y/s/ds` that contains a wrong action `remove`, that is not known by c8y. let smartrest = r#"528,external_id,nodered,1.0.0::debian,,remove"#; let _ = broker.publish("c8y/s/ds", smartrest).await.unwrap(); @@ -341,7 +339,7 @@ async fn c8y_mapper_alarm_mapping_to_smartrest() { let mut messages = broker.messages_published_on("c8y/s/us").await; // Start the C8Y Mapper - let c8y_mapper = start_c8y_mapper(broker.port).await.unwrap(); + let (_tmp_dir, sm_mapper) = start_c8y_mapper(broker.port).await.unwrap(); let _ = broker .publish_with_opts( @@ -372,7 +370,7 @@ async fn c8y_mapper_alarm_mapping_to_smartrest() { .await .unwrap(); - c8y_mapper.abort(); + sm_mapper.abort(); } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] #[serial] @@ -382,7 +380,7 @@ async fn c8y_mapper_syncs_pending_alarms_on_startup() { let mut messages = broker.messages_published_on("c8y/s/us").await; // Start the C8Y Mapper - let c8y_mapper = start_c8y_mapper(broker.port).await.unwrap(); + let (_tmp_dir, sm_mapper) = start_c8y_mapper(broker.port).await.unwrap(); let mut internal_messages = broker .messages_published_on("c8y-internal/alarms/critical/temperature_alarm") @@ -415,7 +413,7 @@ async fn c8y_mapper_syncs_pending_alarms_on_startup() { .await; // stop the mapper - c8y_mapper.abort(); + sm_mapper.abort(); //Publish a new alarm while the mapper is down let _ = broker @@ -441,7 +439,7 @@ async fn c8y_mapper_syncs_pending_alarms_on_startup() { // .unwrap(); // Restart the C8Y Mapper - let c8y_mapper = start_c8y_mapper(broker.port).await.unwrap(); + let (_tmp_dir, sm_mapper) = start_c8y_mapper(broker.port).await.unwrap(); // Ignored until the rumqttd broker bug that doesn't handle empty retained messages // Expect the previously missed clear temperature alarm message @@ -461,13 +459,13 @@ async fn c8y_mapper_syncs_pending_alarms_on_startup() { ) .await; - c8y_mapper.abort(); + sm_mapper.abort(); } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] #[serial] async fn test_sync_alarms() { - let mut converter = create_c8y_converter(); + let (_temp_dir, mut converter) = create_c8y_converter(); let alarm_topic = "tedge/alarms/critical/temperature_alarm"; let alarm_payload = r#"{ "text": "Temperature very high" }"#; @@ -521,7 +519,7 @@ async fn test_sync_alarms() { #[tokio::test(flavor = "multi_thread", worker_threads = 2)] #[serial] async fn convert_thin_edge_json_with_child_id() { - let mut converter = create_c8y_converter(); + let (_temp_dir, mut converter) = create_c8y_converter(); let in_topic = "tedge/measurements/child1"; let in_payload = r#"{"temp": 1, "time": "2021-11-16T17:45:40.571760714+01:00"}"#; @@ -554,7 +552,7 @@ async fn convert_thin_edge_json_with_child_id() { #[tokio::test(flavor = "multi_thread", worker_threads = 2)] #[serial] async fn convert_first_thin_edge_json_invalid_then_valid_with_child_id() { - let mut converter = create_c8y_converter(); + let (_temp_dir, mut converter) = create_c8y_converter(); let in_topic = "tedge/measurements/child1"; let in_invalid_payload = r#"{"temp": invalid}"#; @@ -589,7 +587,7 @@ async fn convert_first_thin_edge_json_invalid_then_valid_with_child_id() { #[tokio::test(flavor = "multi_thread", worker_threads = 2)] #[serial] async fn convert_two_thin_edge_json_messages_given_different_child_id() { - let mut converter = create_c8y_converter(); + let (_temp_dir, mut converter) = create_c8y_converter(); let in_payload = r#"{"temp": 1, "time": "2021-11-16T17:45:40.571760714+01:00"}"#; // First message from "child1" @@ -655,7 +653,7 @@ fn extract_child_id(in_topic: &str, expected_child_id: Option) { #[tokio::test] async fn check_c8y_threshold_packet_size() -> Result<(), anyhow::Error> { - let mut converter = create_c8y_converter(); + let (_temp_dir, mut converter) = create_c8y_converter(); let alarm_topic = "tedge/alarms/critical/temperature_alarm"; let big_alarm_text = create_packet(1024 * 20); @@ -675,7 +673,7 @@ async fn check_c8y_threshold_packet_size() -> Result<(), anyhow::Error> { #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn convert_event_with_known_fields_to_c8y_smartrest() -> Result<()> { - let mut converter = create_c8y_converter(); + let (_temp_dir, mut converter) = create_c8y_converter(); let event_topic = "tedge/events/click_event"; let event_payload = r#"{ "text": "Someone clicked", "time": "2020-02-02T01:02:03+05:30" }"#; let event_message = Message::new(&Topic::new_unchecked(event_topic), event_payload); @@ -695,7 +693,7 @@ async fn convert_event_with_known_fields_to_c8y_smartrest() -> Result<()> { #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn convert_event_with_extra_fields_to_c8y_json() -> Result<()> { - let mut converter = create_c8y_converter(); + let (_temp_dir, mut converter) = create_c8y_converter(); let event_topic = "tedge/events/click_event"; let event_payload = r#"{ "text": "tick", "foo": "bar" }"#; let event_message = Message::new(&Topic::new_unchecked(event_topic), event_payload); @@ -720,7 +718,7 @@ async fn convert_event_with_extra_fields_to_c8y_json() -> Result<()> { #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_convert_big_event() { - let mut converter = create_c8y_converter(); + let (_temp_dir, mut converter) = create_c8y_converter(); let event_topic = "tedge/events/click_event"; let big_event_text = create_packet((16 + 1) * 1024); // Event payload > size_threshold @@ -732,7 +730,7 @@ async fn test_convert_big_event() { #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_convert_big_measurement() { - let mut converter = create_c8y_converter(); + let (_temp_dir, mut converter) = create_c8y_converter(); let measurement_topic = "tedge/measurements"; let big_measurement_payload = create_thin_edge_measurement(10 * 1024); // Measurement payload > size_threshold after converting to c8y json @@ -761,7 +759,7 @@ async fn test_convert_big_measurement() { #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_convert_small_measurement() { - let mut converter = create_c8y_converter(); + let (_temp_dir, mut converter) = create_c8y_converter(); let measurement_topic = "tedge/measurements"; let big_measurement_payload = create_thin_edge_measurement(20); // Measurement payload size is 20 bytes @@ -785,7 +783,7 @@ async fn test_convert_small_measurement() { #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_convert_big_measurement_for_child_device() { - let mut converter = create_c8y_converter(); + let (_temp_dir, mut converter) = create_c8y_converter(); let measurement_topic = "tedge/measurements/child1"; let big_measurement_payload = create_thin_edge_measurement(10 * 1024); // Measurement payload > size_threshold after converting to c8y json @@ -822,7 +820,7 @@ async fn test_convert_small_measurement_for_child_device() { &Topic::new_unchecked(measurement_topic), big_measurement_payload, ); - let mut converter = create_c8y_converter(); + let (_temp_dir, mut converter) = create_c8y_converter(); let result = converter.convert(&big_measurement_message).await; assert!(result @@ -913,8 +911,8 @@ impl C8YHttpProxy for FakeC8YHttpProxy { } } -async fn start_c8y_mapper(mqtt_port: u16) -> Result, anyhow::Error> { - let converter = create_c8y_converter(); +async fn start_c8y_mapper(mqtt_port: u16) -> Result<(TempTedgeDir, JoinHandle<()>), anyhow::Error> { + let (_temp_dir, converter) = create_c8y_converter(); let mut mapper = create_mapper( "c8y-mapper-test", MQTT_HOST.to_string(), @@ -926,37 +924,31 @@ async fn start_c8y_mapper(mqtt_port: u16) -> Result, anyhow::Erro let mapper_task = tokio::spawn(async move { let _ = mapper.run(None).await; }); - Ok(mapper_task) + Ok((_temp_dir, mapper_task)) } -fn create_c8y_converter() -> CumulocityConverter { +fn create_c8y_converter() -> (TempTedgeDir, CumulocityConverter) { let size_threshold = SizeThreshold(16 * 1024); let device_name = "test-device".into(); let device_type = "test-device-type".into(); let operations = Operations::default(); let http_proxy = FakeC8YHttpProxy {}; - let tmp_dir = TempDir::new().unwrap(); - std::fs::create_dir_all(&format!( - "{}/tedge/agent/", - &tmp_dir.path().to_str().unwrap() - )) - .unwrap(); - std::fs::File::create(&format!( - "{}/tedge/agent/software-list-2011-11-11T11:11:11Z.log", - &tmp_dir.path().to_str().unwrap() - )) - .unwrap(); - - CumulocityConverter::from_logs_path( + let tmp_dir = TempTedgeDir::new(); + tmp_dir + .dir("tedge") + .dir("agent") + .file("software-list-2011-11-11T11:11:11Z.log"); + let converter = CumulocityConverter::from_logs_path( size_threshold, device_name, device_type, operations, http_proxy, - tmp_dir.into_path(), + tmp_dir.path().to_path_buf(), ) - .unwrap() + .unwrap(); + (tmp_dir, converter) } fn remove_whitespace(s: &str) -> String { diff --git a/crates/tests/tedge_test_utils/Cargo.toml b/crates/tests/tedge_test_utils/Cargo.toml new file mode 100644 index 00000000..8594d66a --- /dev/null +++ b/crates/tests/tedge_test_utils/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "tedge_test_utils" +version = "0.7.1" +authors = ["thin-edge.io team "] +edition = "2021" +rust-version = "1.58.1" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +anyhow = "1.0" +tempfile = "3.3" +toml = "0.5" diff --git a/crates/tests/tedge_test_utils/src/fs.rs b/crates/tests/tedge_test_utils/src/fs.rs new file mode 100644 index 00000000..45ed9ff0 --- /dev/null +++ b/crates/tests/tedge_test_utils/src/fs.rs @@ -0,0 +1,204 @@ +use std::{ + fs::{self, OpenOptions}, + io::Write, + path::{Path, PathBuf}, + sync::Arc, +}; +use tempfile::TempDir; + +pub struct TempTedgeDir { + pub temp_dir: Arc, + current_file_path: PathBuf, +} + +pub struct TempTedgeFile { + file_path: PathBuf, +} + +impl TempTedgeDir { + pub fn new() -> Self { + let temp_dir = TempDir::new().unwrap(); + let current_file_path = temp_dir.path().to_path_buf(); + TempTedgeDir { + temp_dir: Arc::new(temp_dir), + current_file_path, + } + } + + pub fn dir(&self, directory_name: &str) -> TempTedgeDir { + let root = self.temp_dir.path().to_path_buf(); + let path = root + .join(self.current_file_path.to_path_buf()) + .join(directory_name); + + if !path.exists() { + let () = fs::create_dir(&path).unwrap(); + }; + + TempTedgeDir { + temp_dir: self.temp_dir.clone(), + current_file_path: path, + } + } + + pub fn file(&self, file_name: &str) -> TempTedgeFile { + let root = self.temp_dir.path().to_path_buf(); + let path = root + .join(self.current_file_path.to_path_buf()) + .join(file_name); + + if !path.exists() { + let _file = fs::File::create(&path).unwrap(); + }; + TempTedgeFile { file_path: path } + } + + pub fn path(&self) -> &Path { + Path::new(self.temp_dir.path()) + } + + pub fn to_path_buf(&self) -> PathBuf { + PathBuf::from(self.path()) + } +} + +impl TempTedgeFile { + pub fn with_raw_content(self, content: &str) { + let mut file = OpenOptions::new() + .write(true) + .create(false) + .open(self.file_path) + .unwrap(); + file.write_all(content.as_bytes()).unwrap(); + } + + pub fn with_toml_content(self, content: toml::Value) { + let mut file = OpenOptions::new() + .write(true) + .create(false) + .open(self.file_path) + .unwrap(); + let file_content = content.to_string(); + file.write_all(file_content.as_bytes()).unwrap(); + } + + pub fn path(&self) -> &Path { + Path::new(&self.file_path) + } + + pub fn to_path_buf(&self) -> PathBuf { + PathBuf::from(self.path()) + } +} + +pub fn create_full_tedge_dir_structure() { + let ttd = TempTedgeDir::new(); + ttd.file("tedge.toml"); + ttd.dir(".agent").file("current-operation"); + ttd.dir("c8y") + .file("c8y-log-plugin.toml") + .with_toml_content(toml::toml! { + files = [ + {type = "software-management", path = "/var/log/tedge/agent/software-*" } + ] + }); + ttd.dir("contrib").dir("collectd").file("collectd.conf"); + ttd.dir("device").file("inventory.json"); + ttd.dir("device-certs"); + ttd.dir("mosquitto-conf").file("c8y-bridge.conf"); + ttd.dir("mosquitto-conf").file("tedge-mosquitto.conf"); + ttd.dir("operations") + .dir("c8y") + .file("c8y_LogfileRequest") + .with_raw_content(""); + ttd.dir("operations").dir("c8y").file("c8y_Restart"); + ttd.dir("operations").dir("c8y").file("c8y_SoftwareUpdate"); + ttd.dir("sm-plugins").file("apt"); +} + +#[cfg(test)] +mod tests { + use super::TempTedgeDir; + use std::{io::Read, path::Path}; + + #[test] + fn assert_dir_file_and_content() -> Result<(), anyhow::Error> { + let tedge_dir = TempTedgeDir::new(); + tedge_dir.dir("c8y").file("c8y-log-plugin.toml"); + tedge_dir + .dir("operations") + .dir("c8y") + .file("c8y_Restart") + .with_toml_content(toml::toml! { + files = [] + }); + + assert!(Path::new(&format!( + "{}/c8y/c8y-log-plugin.toml", + &tedge_dir.temp_dir.path().to_str().unwrap() + )) + .exists()); + + assert!(Path::new(&format!( + "{}/operations/c8y/c8y_Restart", + &tedge_dir.temp_dir.path().to_str().unwrap() + )) + .exists()); + Ok(()) + } + + #[test] + fn test_with_toml() -> Result<(), anyhow::Error> { + let tedge_dir = TempTedgeDir::new(); + tedge_dir + .dir("c8y") + .file("c8y_log_plugin.toml") + .with_toml_content(toml::toml! { + files = [ + { type = "apt", path = "/var/log/apt/history.log"} + ] + }); + let file_path = &format!( + "{}/c8y/c8y_log_plugin.toml", + &tedge_dir.temp_dir.path().to_str().unwrap() + ); + assert!(Path::new(&file_path).exists()); + + let mut file_content = String::new(); + let mut file = std::fs::File::open(&file_path).unwrap(); + file.read_to_string(&mut file_content).unwrap(); + + let as_toml: toml::Value = toml::from_str(&file_content).unwrap(); + assert_eq!( + as_toml, + toml::toml! { + files = [ + { type = "apt", path = "/var/log/apt/history.log"} + ] + } + ); + + Ok(()) + } + + #[test] + fn test_multiple_files_in_same_dir() -> Result<(), anyhow::Error> { + let ttd = TempTedgeDir::new(); + let operations_dir = ttd.dir("operations"); + operations_dir.dir("c8y").file("c8y_Restart"); + operations_dir.dir("c8y").file("c8y_SoftwareUpdate"); + + assert!(Path::new(&format!( + "{}/operations/c8y/c8y_Restart", + &ttd.temp_dir.path().to_str().unwrap() + )) + .exists()); + + assert!(Path::new(&format!( + "{}/operations/c8y/c8y_SoftwareUpdate", + &ttd.temp_dir.path().to_str().unwrap() + )) + .exists()); + Ok(()) + } +} diff --git a/crates/tests/tedge_test_utils/src/lib.rs b/crates/tests/tedge_test_utils/src/lib.rs new file mode 100644 index 00000000..d521fbd7 --- /dev/null +++ b/crates/tests/tedge_test_utils/src/lib.rs @@ -0,0 +1 @@ +pub mod fs; -- cgit v1.2.3