From 227c28a8830919e312a5c578191febf5c27daac0 Mon Sep 17 00:00:00 2001 From: Hahihula Date: Fri, 10 Jan 2025 10:28:16 +0100 Subject: [PATCH] added build and test pipeline (#63) * added build and test pipeline * added some more tests * pretty format test output --------- Co-authored-by: Petr Gadorek --- .github/workflows/build_and_test.yaml | 68 +++++++++++ src/idf_config.rs | 162 ++++++++++++++++++++++++++ src/idf_versions.rs | 46 ++++++++ src/utils.rs | 131 +++++++++++++++++++++ 4 files changed, 407 insertions(+) create mode 100644 .github/workflows/build_and_test.yaml diff --git a/.github/workflows/build_and_test.yaml b/.github/workflows/build_and_test.yaml new file mode 100644 index 0000000..8490555 --- /dev/null +++ b/.github/workflows/build_and_test.yaml @@ -0,0 +1,68 @@ +name: Rust + +on: + push: + tags: + - "v*" + branches: + - master + pull_request: + branches: + - master + release: + types: + - created + workflow_dispatch: + +jobs: + build: + name: Build for multiple platforms + runs-on: ${{ matrix.os }} + strategy: + matrix: + include: + - os: ubuntu-latest + package_name: linux-x64 + - os: windows-latest + package_name: windows-x64 + # - os: windows-latest + # package_name: windows-arm64 + # target: aarch64-pc-windows-msvc + - os: macos-latest + package_name: macos-aarch64 + - os: macos-13 + package_name: macos-x64 + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + override: true + target: ${{ matrix.target }} + + - name: Build + run: | + if [ "${{ matrix.target }}" != "" ]; then + cargo build --release --target ${{ matrix.target }} + else + cargo build --release + fi + shell: bash + + - name: Test + run: | + if [ "${{ matrix.target }}" != "" ]; then + cargo test --no-fail-fast --lib --target ${{ matrix.target }} > result.txt + else + cargo test --no-fail-fast --lib > result.txt + fi + shell: bash + + - name: Format test results + uses: hahihula/rust-test-results-formatter@v1 + with: + results-file: "result.txt" diff --git a/src/idf_config.rs b/src/idf_config.rs index d855424..72149a8 100644 --- a/src/idf_config.rs +++ b/src/idf_config.rs @@ -202,3 +202,165 @@ impl IdfConfig { pub fn parse_idf_config>(path: P) -> Result { IdfConfig::from_file(path) } + +#[cfg(test)] +mod tests { + use super::*; + use std::fs; + use tempfile::tempdir; + + fn create_test_config() -> IdfConfig { + IdfConfig { + git_path: String::from("/opt/homebrew/bin/git"), + idf_installed: vec![ + IdfInstallation { + activation_script: String::from("/tmp/esp-new/activate_idf_v5.4.sh"), + id: String::from("esp-idf-5705c12db93b4d1a8b084c6986173c1b"), + idf_tools_path: String::from("/tmp/esp-new/v5.4/tools"), + name: String::from("ESP-IDF v5.4"), + path: String::from("/tmp/esp-new/v5.4/esp-idf"), + python: String::from("/tmp/esp-new/v5.4/tools/python/bin/python3"), + }, + IdfInstallation { + activation_script: String::from("/tmp/esp-new/activate_idf_v5.1.5.sh"), + id: String::from("esp-idf-5f014e6764904e4c914eeb365325bfcd"), + idf_tools_path: String::from("/tmp/esp-new/v5.1.5/tools"), + name: String::from("v5.1.5"), + path: String::from("/tmp/esp-new/v5.1.5/esp-idf"), + python: String::from("/tmp/esp-new/v5.1.5/tools/python/bin/python3"), + }, + ], + idf_selected_id: String::from("esp-idf-5705c12db93b4d1a8b084c6986173c1b"), + } + } + + #[test] + fn test_get_selected_installation() { + let config = create_test_config(); + let selected = config.get_selected_installation().unwrap(); + assert_eq!(selected.id, "esp-idf-5705c12db93b4d1a8b084c6986173c1b"); + assert_eq!(selected.name, "ESP-IDF v5.4"); + } + + #[test] + fn test_update_installation_name() { + let mut config = create_test_config(); + + // Test updating by ID + assert!(config.update_installation_name( + "esp-idf-5705c12db93b4d1a8b084c6986173c1b", + String::from("New Name") + )); + assert_eq!(config.idf_installed[0].name, "New Name"); + + // Test updating by name + assert!(config.update_installation_name("v5.1.5", String::from("Updated v5.1.5"))); + assert_eq!(config.idf_installed[1].name, "Updated v5.1.5"); + + // Test updating non-existent installation + assert!(!config.update_installation_name("non-existent", String::from("Invalid"))); + } + + #[test] + fn test_select_installation() { + let mut config = create_test_config(); + + // Test selecting by ID + assert!(config.select_installation("esp-idf-5f014e6764904e4c914eeb365325bfcd")); + assert_eq!( + config.idf_selected_id, + "esp-idf-5f014e6764904e4c914eeb365325bfcd" + ); + + // Test selecting by name + assert!(config.select_installation("ESP-IDF v5.4")); + assert_eq!( + config.idf_selected_id, + "esp-idf-5705c12db93b4d1a8b084c6986173c1b" + ); + + // Test selecting non-existent installation + assert!(!config.select_installation("non-existent")); + } + + #[test] + fn test_remove_installation() { + let mut config = create_test_config(); + + // Test removing by ID + assert!(config.remove_installation("esp-idf-5705c12db93b4d1a8b084c6986173c1b")); + assert_eq!(config.idf_installed.len(), 1); + assert!(config.idf_selected_id.is_empty()); // Should clear selection when removing selected installation + + // Test removing by name + assert!(config.remove_installation("v5.1.5")); + assert!(config.idf_installed.is_empty()); + + // Test removing non-existent installation + assert!(!config.remove_installation("non-existent")); + } + + #[test] + fn test_file_operations() -> Result<()> { + let dir = tempdir()?; + let config_path = dir.path().join("test_config.json"); + let mut config = create_test_config(); + + // Test writing config to file + config.to_file(&config_path, true)?; + assert!(config_path.exists()); + + // Test reading config from file + let read_config = IdfConfig::from_file(&config_path)?; + assert_eq!(read_config.git_path, config.git_path); + assert_eq!(read_config.idf_selected_id, config.idf_selected_id); + assert_eq!(read_config.idf_installed.len(), config.idf_installed.len()); + + // Test appending to existing config + let new_installation = IdfInstallation { + activation_script: String::from("/esp/idf/v5.1/export.sh"), + id: String::from("5.1"), + idf_tools_path: String::from("/home/user/.espressif/tools"), + name: String::from("ESP-IDF v5.1"), + path: String::from("/esp/idf/v5.1"), + python: String::from("/usr/bin/python3"), + }; + + config.idf_installed = vec![new_installation.clone()]; + config.to_file(&config_path, true)?; + + let updated_config = IdfConfig::from_file(&config_path)?; + assert!(updated_config + .idf_installed + .iter() + .any(|install| install.id == "5.1")); + assert!(updated_config + .idf_installed + .iter() + .any(|install| install.id == "esp-idf-5705c12db93b4d1a8b084c6986173c1b")); + + Ok(()) + } + + #[test] + fn test_parse_idf_config() -> Result<()> { + let dir = tempdir()?; + let config_path = dir.path().join("parse_test_config.json"); + let config = create_test_config(); + + // Write test config to file + let json = serde_json::to_string_pretty(&config)?; + fs::write(&config_path, json)?; + + // Test parsing + let parsed_config = parse_idf_config(&config_path)?; + assert_eq!(parsed_config.git_path, config.git_path); + assert_eq!(parsed_config.idf_selected_id, config.idf_selected_id); + assert_eq!( + parsed_config.idf_installed.len(), + config.idf_installed.len() + ); + + Ok(()) + } +} diff --git a/src/idf_versions.rs b/src/idf_versions.rs index ba0f6de..8192f5a 100644 --- a/src/idf_versions.rs +++ b/src/idf_versions.rs @@ -189,3 +189,49 @@ pub async fn get_idf_names() -> Vec { } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_get_idf_versions_by_target() { + let releases = Releases { + VERSIONS: vec![ + Version { + name: "v4.4.5".to_string(), + pre_release: false, + old: false, + end_of_life: false, + has_targets: true, + supported_targets: vec!["esp32".to_string(), "esp32s2".to_string()], + }, + Version { + name: "v5.0.0".to_string(), + pre_release: false, + old: false, + end_of_life: false, + has_targets: true, + supported_targets: vec!["esp32".to_string()], + }, + ], + IDF_TARGETS: vec![ + IDFTarget { + text: "ESP32".to_string(), + value: "esp32".to_string(), + }, + IDFTarget { + text: "ESP32-S2".to_string(), + value: "esp32s2".to_string(), + }, + ], + RELEASES: HashMap::new(), + }; + + let versions_by_target = get_idf_versions_by_target(&releases); + + assert_eq!(versions_by_target.len(), 2); + assert_eq!(versions_by_target.get("esp32").unwrap().len(), 2); + assert_eq!(versions_by_target.get("esp32s2").unwrap().len(), 1); + } +} diff --git a/src/utils.rs b/src/utils.rs index 668479d..b87207d 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -226,3 +226,134 @@ pub fn remove_directory_all>(path: P) -> io::Result<()> { Ok(()) } + +#[cfg(test)] +mod tests { + use super::*; + use std::fs::{self, File}; + use std::io::Write; + use tempfile::TempDir; + + #[test] + fn test_find_directories_by_name() { + let temp_dir = TempDir::new().unwrap(); + let base_path = temp_dir.path(); + + // Create test directory structure + let test_dir1 = base_path.join("test_dir"); + let test_dir2 = base_path.join("subdir").join("test_dir"); + fs::create_dir_all(&test_dir1).unwrap(); + fs::create_dir_all(&test_dir2).unwrap(); + + let results = find_directories_by_name(base_path, "test_dir"); + assert_eq!(results.len(), 2); + assert!(results + .iter() + .any(|p| p.contains(test_dir1.to_str().unwrap()))); + assert!(results + .iter() + .any(|p| p.contains(test_dir2.to_str().unwrap()))); + } + + #[test] + fn test_is_valid_idf_directory() { + let temp_dir = TempDir::new().unwrap(); + let base_path = temp_dir.path(); + + // Create invalid directory (no tools.json) + assert!(!is_valid_idf_directory(base_path.to_str().unwrap())); + + // Create valid IDF directory structure + let tools_dir = base_path.join("tools"); + fs::create_dir_all(&tools_dir).unwrap(); + let tools_json_path = tools_dir.join("tools.json"); + let mut file = File::create(tools_json_path).unwrap(); + write!(file, r#"{{"tools": [], "version": 1}}"#).unwrap(); + + assert!(is_valid_idf_directory(base_path.to_str().unwrap())); + } + + #[test] + fn test_filter_duplicate_paths() { + let temp_dir = TempDir::new().unwrap(); + let base_path = temp_dir.path(); + + // Create test files with different content + let file1_path = base_path.join("file1.txt"); + let file2_path = base_path.join("file2.txt"); + + fs::write(&file1_path, "content1").unwrap(); + fs::write(&file2_path, "content2").unwrap(); + + let paths = vec![ + file1_path.to_string_lossy().to_string(), + file1_path.to_string_lossy().to_string(), // Duplicate + file2_path.to_string_lossy().to_string(), + ]; + + let filtered = filter_duplicate_paths(paths); + assert_eq!(filtered.len(), 2); + } + + #[test] + fn test_filter_subpaths() { + let paths = vec![ + "/path/to/dir".to_string(), + "/path/to/dir/subdir".to_string(), + "/path/to/another".to_string(), + ]; + + let filtered = filter_subpaths(paths); + assert_eq!(filtered.len(), 2); + assert!(filtered.contains(&"/path/to/dir".to_string())); + assert!(filtered.contains(&"/path/to/another".to_string())); + assert!(!filtered.contains(&"/path/to/dir/subdir".to_string())); + } + + #[test] + fn test_remove_directory_all() { + let temp_dir = TempDir::new().unwrap(); + let base_path = temp_dir.path(); + + // Create test directory structure + let test_dir = base_path.join("test_dir"); + let test_subdir = test_dir.join("subdir"); + let test_file = test_dir.join("test.txt"); + + fs::create_dir_all(&test_subdir).unwrap(); + fs::write(&test_file, "test content").unwrap(); + + // Test removal + assert!(remove_directory_all(&test_dir).is_ok()); + assert!(!test_dir.exists()); + } + + #[test] + fn test_remove_directory_all_nonexistent() { + let temp_dir = TempDir::new().unwrap(); + let non_existent = temp_dir.path().join("non_existent"); + + assert!(remove_directory_all(&non_existent).is_ok()); + } + + #[test] + fn test_remove_directory_all_readonly() { + let temp_dir = TempDir::new().unwrap(); + let test_dir = temp_dir.path().join("readonly_dir"); + let test_file = test_dir.join("readonly.txt"); + + fs::create_dir_all(&test_dir).unwrap(); + fs::write(&test_file, "readonly content").unwrap(); + + #[cfg(windows)] + { + let metadata = fs::metadata(&test_file).unwrap(); + let mut permissions = metadata.permissions(); + permissions.set_readonly(true); + fs::set_permissions(&test_file, permissions).unwrap(); + } + + assert!(remove_directory_all(&test_dir).is_ok()); + assert!(!test_dir.exists()); + } +}