From 6ff3f301c004038850653c6d3306af29883ea60e Mon Sep 17 00:00:00 2001 From: yyjeqhc <1772413353@qq.com> Date: Thu, 1 May 2025 16:59:59 +0800 Subject: [PATCH 1/2] add comments at libra's command tests. --- libra/src/command/add.rs | 1 + libra/src/command/branch.rs | 7 +- libra/src/command/checkout.rs | 2 + libra/src/command/clone.rs | 3 + libra/src/command/commit.rs | 11 +++ libra/src/command/diff.rs | 5 ++ libra/src/command/init.rs | 31 ++++--- libra/src/command/log.rs | 31 ++++++- libra/src/command/mod.rs | 3 + libra/src/command/push.rs | 4 + libra/src/command/status.rs | 2 + libra/src/command/switch.rs | 136 +++++++++++++++++++++++------- libra/src/utils/client_storage.rs | 9 +- libra/src/utils/util.rs | 8 ++ 14 files changed, 209 insertions(+), 44 deletions(-) diff --git a/libra/src/command/add.rs b/libra/src/command/add.rs index 28f22d0ac..cb930fc86 100644 --- a/libra/src/command/add.rs +++ b/libra/src/command/add.rs @@ -158,6 +158,7 @@ mod test { #[test] #[should_panic] + /// Test that `-A` and `-u` cannot be used together fn test_args_parse_update_conflict_with_all() { AddArgs::try_parse_from(["test", "-A", "-u"]).unwrap(); } diff --git a/libra/src/command/branch.rs b/libra/src/command/branch.rs index 408b7fc38..4eb3e2aca 100644 --- a/libra/src/command/branch.rs +++ b/libra/src/command/branch.rs @@ -240,6 +240,8 @@ mod tests { #[tokio::test] #[serial] + /// Tests core branch management functionality including creation and listing. + /// Verifies branches can be created from specific commits. async fn test_branch() { let temp_path = tempdir().unwrap(); test::setup_with_new_libra_in(temp_path.path()).await; @@ -330,6 +332,8 @@ mod tests { #[tokio::test] #[serial] + /// Tests branch creation using remote branches as starting points. + /// Verifies that local branches can be created from remote branch references. async fn test_create_branch_from_remote() { let temp_path = tempdir().unwrap(); test::setup_with_new_libra_in(temp_path.path()).await; @@ -366,6 +370,7 @@ mod tests { #[tokio::test] #[serial] + /// Tests the behavior of creating a branch with an invalid name. async fn test_invalid_branch_name() { let temp_path = tempdir().unwrap(); test::setup_with_new_libra_in(temp_path.path()).await; @@ -391,7 +396,7 @@ mod tests { }; execute(args).await; - let branch = Branch::find_branch("new", None).await; + let branch = Branch::find_branch("@{mega}", None).await; assert!(branch.is_none(), "invalid branch should not be created"); } } diff --git a/libra/src/command/checkout.rs b/libra/src/command/checkout.rs index 7d49fcc4e..e91f60e2a 100644 --- a/libra/src/command/checkout.rs +++ b/libra/src/command/checkout.rs @@ -170,6 +170,8 @@ mod tests { #[tokio::test] #[serial] + /// Tests branch creation, switching and validation functionality in the checkout module. + /// Verifies proper branch management and HEAD reference updates when switching between branches. async fn test_checkout_module_functions() { println!("\n\x1b[1mTest checkout module functions.\x1b[0m"); diff --git a/libra/src/command/clone.rs b/libra/src/command/clone.rs index ef2d553c1..c90d2f26b 100644 --- a/libra/src/command/clone.rs +++ b/libra/src/command/clone.rs @@ -163,6 +163,7 @@ mod tests { #[tokio::test] #[serial] + /// Test the clone command with a specific branch async fn test_clone_branch() { let temp_path = tempdir().unwrap(); let _guard = test::ChangeDirGuard::new(temp_path.path()); @@ -191,6 +192,7 @@ mod tests { #[tokio::test] #[serial] + /// Test the clone command with the default branch async fn test_clone_default_branch() { let temp_path = tempdir().unwrap(); let _guard = test::ChangeDirGuard::new(temp_path.path()); @@ -219,6 +221,7 @@ mod tests { #[tokio::test] #[serial] + /// Test the clone command with an empty repository async fn test_clone_empty_repo() { let temp_path = tempdir().unwrap(); let _guard = test::ChangeDirGuard::new(temp_path.path()); diff --git a/libra/src/command/commit.rs b/libra/src/command/commit.rs index bb79ad3d4..60f1f9327 100644 --- a/libra/src/command/commit.rs +++ b/libra/src/command/commit.rs @@ -30,6 +30,7 @@ pub struct CommitArgs { #[arg(long, requires("message"))] pub conventional: bool, + /// amend the last commit #[arg(long)] pub amend: bool, } @@ -211,6 +212,7 @@ mod test { use super::*; #[test] + ///testing basic parameter parsing functionality. fn test_parse_args() { let args = CommitArgs::try_parse_from(["commit", "-m", "init"]); assert!(args.is_ok()); @@ -236,6 +238,8 @@ mod test { #[tokio::test] #[serial] + /// Tests the recursive tree creation from index entries. + /// Verifies that tree objects are correctly created, saved to storage, and properly organized in a hierarchical structure. async fn test_create_tree() { let temp_path = tempdir().unwrap(); test::setup_with_new_libra_in(temp_path.path()).await; @@ -264,6 +268,9 @@ mod test { #[tokio::test] #[serial] #[should_panic] + /// A commit with no file changes should fail if `allow_empty` is false. + /// This test verifies that the commit command rejects empty changesets + /// when not explicitly permitted. async fn test_execute_commit_with_empty_index_fail() { let temp_path = tempdir().unwrap(); test::setup_with_new_libra_in(temp_path.path()).await; @@ -280,6 +287,10 @@ mod test { #[tokio::test] #[serial] + /// Tests normal commit functionality with both `--amend` and `--allow_empty` flags. + /// Verifies that: + /// 1. Amending works correctly when allowed + /// 2. Empty commits are permitted when explicitly enabled async fn test_execute_commit() { let temp_path = tempdir().unwrap(); test::setup_with_new_libra_in(temp_path.path()).await; diff --git a/libra/src/command/diff.rs b/libra/src/command/diff.rs index a44190962..297b82766 100644 --- a/libra/src/command/diff.rs +++ b/libra/src/command/diff.rs @@ -350,6 +350,8 @@ mod test { use super::*; #[test] + /// Tests command line argument parsing for the diff command with various parameter combinations. + /// Verifies parameter requirements, conflicts and default values are handled correctly. fn test_args() { { let args = DiffArgs::try_parse_from(["diff", "--old", "old", "--new", "new", "paths"]); @@ -387,6 +389,7 @@ mod test { } #[test] + ///test the function of similar_diff_result. fn test_similar_diff_result() { let old = "Hello World\nThis is the second line.\nThis is the third."; let new = "Hallo Welt\nThis is the second line.\nThis is life.\nMoar and more"; @@ -398,6 +401,8 @@ mod test { #[tokio::test] #[serial] + /// Tests that the get_files_blobs function properly respects .libraignore patterns. + /// Verifies ignored files are correctly excluded from the blob collection process. async fn test_get_files_blob_gitignore() { let temp_path = tempdir().unwrap(); test::setup_with_new_libra_in(temp_path.path()).await; diff --git a/libra/src/command/init.rs b/libra/src/command/init.rs index b86ac33aa..cd36d8f97 100644 --- a/libra/src/command/init.rs +++ b/libra/src/command/init.rs @@ -249,12 +249,12 @@ fn set_dir_hidden(_dir: &str) -> io::Result<()> { /// Unit tests for the init module #[cfg(test)] mod tests { + use super::*; + use crate::internal::head::Head; + use crate::utils::test; use serial_test::serial; use tempfile::tempdir; - use super::*; - use crate::{internal::head::Head, utils::test}; - pub fn verify_init(base_dir: &Path) { // List of subdirectories to verify let dirs = ["objects/pack", "objects/info", "info"]; @@ -273,10 +273,13 @@ mod tests { assert!(file_path.exists(), "File {} does not exist", file); } } - /// Test the init function with no parameters #[tokio::test] + #[serial] + /// Test the init function with no parameters async fn test_init() { let target_dir = tempdir().unwrap().into_path(); + // let _guard = ChangeDirGuard::new(target_dir.clone()); + let args = InitArgs { bare: false, initial_branch: None, @@ -294,8 +297,9 @@ mod tests { verify_init(libra_dir.as_path()); } - /// Test the init function with the --bare flag #[tokio::test] + #[serial] + /// Test the init function with the --bare flag async fn test_init_bare() { let target_dir = tempdir().unwrap().into_path(); // Run the init function with --bare flag @@ -311,8 +315,9 @@ mod tests { // Verify the contents of the other directory verify_init(target_dir.as_path()); } - /// Test the init function with the --bare flag and an existing repository #[tokio::test] + #[serial] + /// Test the init function with the --bare flag and an existing repository async fn test_init_bare_with_existing_repo() { let target_dir = tempdir().unwrap().into_path(); @@ -342,9 +347,9 @@ mod tests { assert!(err.to_string().contains("Initialization failed")); // Check error message contains "Already initialized" } - /// Test the init function with an initial branch name #[tokio::test] #[serial] + /// Test the init function with an initial branch name async fn test_init_with_initial_branch() { // Set up the test environment without a Libra repository let temp_path = tempdir().unwrap(); @@ -372,8 +377,9 @@ mod tests { }; } - /// Test the init function with an invalid branch name #[tokio::test] + #[serial] + /// Test the init function with an invalid branch name async fn test_init_with_invalid_branch() { // Cover all invalid branch name cases test_invalid_branch_name("master ").await; @@ -411,8 +417,9 @@ mod tests { assert!(err.to_string().contains("invalid branch name")); // Check error message contains "invalid branch name" } - /// Test the init function with [directory] parameter #[tokio::test] + #[serial] + /// Test the init function with [directory] parameter async fn test_init_with_directory() { let target_dir = tempdir().unwrap().into_path(); @@ -436,8 +443,9 @@ mod tests { verify_init(&libra_dir); } - /// Test the init function with invalid [directory] parameter #[tokio::test] + #[serial] + /// Test the init function with invalid [directory] parameter async fn test_init_with_invalid_directory() { let target_dir = tempdir().unwrap().into_path(); @@ -465,6 +473,8 @@ mod tests { } #[tokio::test] + #[serial] + /// Tests that repository initialization fails when lacking write permissions in the target directory async fn test_init_with_unauthorized_directory() { let target_dir = tempdir().unwrap().into_path(); @@ -503,6 +513,7 @@ mod tests { } #[tokio::test] + #[serial] /// Test the init function with the --quiet flag by using --show-output async fn test_init_quiet() { let target_dir = tempdir().unwrap().into_path(); diff --git a/libra/src/command/log.rs b/libra/src/command/log.rs index afcdcbc81..99c7ab0c1 100644 --- a/libra/src/command/log.rs +++ b/libra/src/command/log.rs @@ -127,6 +127,7 @@ pub async fn execute(args: LogArgs) { #[cfg(test)] mod tests { use super::*; + use crate::utils::test::ChangeDirGuard; use crate::{command::save_object, utils::test}; use common::utils::format_commit_msg; use mercury::{hash::SHA1, internal::object::commit::Commit}; @@ -135,6 +136,7 @@ mod tests { #[tokio::test] #[serial] + /// Tests retrieval of commits reachable from a specific commit hash async fn test_get_reachable_commits() { let temp_path = tempdir().unwrap(); test::setup_with_new_libra_in(temp_path.path()).await; @@ -147,14 +149,37 @@ mod tests { } #[tokio::test] - #[ignore] // ignore this test because it will open less and block the test + #[serial] + /// Tests log command execution functionality async fn test_execute_log() { let temp_path = tempdir().unwrap(); test::setup_with_new_libra_in(temp_path.path()).await; + let _guard = ChangeDirGuard::new(temp_path.path()); let _ = create_test_commit_tree().await; - let args = LogArgs { number: Some(6) }; - execute(args).await; + let head = Head::current().await; + // check if the current branch has any commits + if let Head::Branch(branch_name) = head.to_owned() { + let branch = Branch::find_branch(&branch_name, None).await; + if branch.is_none() { + panic!( + "fatal: your current branch '{}' does not have any commits yet ", + branch_name + ); + } + } + + let commit_hash = Head::current_commit().await.unwrap().to_string(); + let mut reachable_commits = get_reachable_commits(commit_hash.clone()).await; + // default sort with signature time + reachable_commits.sort_by(|a, b| b.committer.timestamp.cmp(&a.committer.timestamp)); + //the last seven commits + let max_output_number = min(6, reachable_commits.len()); + let mut output_number = 6; + for commit in reachable_commits.iter().take(max_output_number) { + assert_eq!(commit.message, format!("\nCommit_{}", output_number)); + output_number -= 1; + } } /// create a test commit tree structure as graph and create branch (master) head to commit 6 diff --git a/libra/src/command/mod.rs b/libra/src/command/mod.rs index 29b042e0c..4987db780 100644 --- a/libra/src/command/mod.rs +++ b/libra/src/command/mod.rs @@ -138,6 +138,7 @@ mod tests { use crate::utils::test; #[tokio::test] #[serial] + /// Test objects can be correctly saved to and loaded from storage. async fn test_save_load_object() { let temp_path = tempdir().unwrap(); test::setup_with_new_libra_in(temp_path.path()).await; @@ -148,6 +149,8 @@ mod tests { } #[test] + /// Tests commit message formatting and parsing with signatures. + /// Verifies correct handling of GPG/SSH signatures and proper message extraction. fn test_format_and_parse_commit_msg() { { let msg = "commit message"; diff --git a/libra/src/command/push.rs b/libra/src/command/push.rs index 35c2f2ca1..a6994b673 100644 --- a/libra/src/command/push.rs +++ b/libra/src/command/push.rs @@ -331,6 +331,8 @@ fn diff_tree_objs(old_tree: Option<&SHA1>, new_tree: &SHA1) -> HashSet { mod test { use super::*; #[test] + /// Tests successful parsing of push command arguments with different parameter combinations. + /// Verifies repository, refspec and upstream flag settings are correctly interpreted. fn test_parse_args_success() { let args = vec!["push"]; let args = PushArgs::parse_from(args); @@ -352,6 +354,8 @@ mod test { } #[test] + /// Tests failure cases for push command argument parsing with invalid parameter combinations. + /// Verifies that missing required parameters are properly detected as errors. fn test_parse_args_fail() { let args = vec!["push", "-u"]; let args = PushArgs::try_parse_from(args); diff --git a/libra/src/command/status.rs b/libra/src/command/status.rs index 25eb3edfe..a2cb4018e 100644 --- a/libra/src/command/status.rs +++ b/libra/src/command/status.rs @@ -205,6 +205,8 @@ mod test { #[tokio::test] #[serial] + /// Tests the file status detection functionality with respect to ignore patterns. + /// Verifies that files matching patterns in .libraignore are properly excluded from status reports. async fn test_changes_to_be_staged() { let test_dir = tempdir().unwrap(); test::setup_with_new_libra_in(test_dir.path()).await; diff --git a/libra/src/command/switch.rs b/libra/src/command/switch.rs index 30cf74b49..49b0e9f79 100644 --- a/libra/src/command/switch.rs +++ b/libra/src/command/switch.rs @@ -120,13 +120,20 @@ async fn restore_to_commit(commit_id: SHA1) { mod tests { use super::*; use crate::command::add; - use crate::command::init; + use crate::command::commit; + use crate::command::commit::CommitArgs; use crate::command::restore::RestoreArgs; + use crate::{ + command::load_object, + utils::test::{self, ChangeDirGuard}, + }; + use mercury::internal::object::commit::Commit; use serial_test::serial; + use std::fs; use std::str::FromStr; - use std::{env, fs}; use tempfile::tempdir; #[test] + /// Test parsing RestoreArgs from command-line style arguments fn test_parse_from() { let commit_id = SHA1::from_str("0cb5eb6281e1c0df48a70716869686c694706189").unwrap(); let restore_args = RestoreArgs::parse_from([ @@ -165,40 +172,111 @@ mod tests { assert!(check_status().await); } - #[tokio::test] - #[serial] - async fn test_parts_of_switch_module_function() { - println!("\n\x1b[1mTest some functions of the switch module.\x1b[0m"); + async fn test_switch_function() { + println!("\n\x1b[1mTest switch function.\x1b[0m"); - let target_dir = tempdir().unwrap().into_path(); + // create first empty commit + { + let args = CommitArgs { + message: "first".to_string(), + allow_empty: true, + conventional: false, + amend: false, + }; + commit::execute(args).await; + } - // Create a test directory and set args - let test_dir = target_dir.join("test_check_status"); - fs::create_dir(&test_dir).unwrap(); + // create a new branch and switch to it + { + let args = SwitchArgs { + branch: None, + create: Some("test_branch".to_string()), + detach: false, + }; + execute(args).await; + let head = Head::current().await; + let ref_name = match head { + Head::Branch(name) => name, + _ => panic!("head not in branch,unreachable"), + // Head::Detached(name) => name.to_string(), + }; + assert_eq!( + ref_name, "test_branch", + "create a new branch and switch to it failed!" + ); + } - let init_args = init::InitArgs { - bare: false, - initial_branch: None, - repo_directory: test_dir.to_str().unwrap().to_owned(), - quiet: false, - }; + //detach the head to a commit + { + let head = Head::current().await; + let ref_name = match head { + Head::Branch(name) => name, + _ => panic!("head not in branch,unreachable"), + // Head::Detached(name) => name.to_string(), + }; + let branch = Branch::find_branch(&ref_name, None).await.unwrap(); + let commit: Commit = load_object(&branch.commit).unwrap(); + let commit_id_str = commit.id.to_string(); - // Run the init function and change the current directory to the test directory - let raw_dir = env::current_dir().unwrap(); - let result = init::init(init_args).await; - if let Err(e) = result { - eprintln!("Error initializing repository: {}", e); - return; + let args = CommitArgs { + message: "second".to_string(), + allow_empty: true, + conventional: false, + amend: false, + }; + commit::execute(args).await; + + let args = SwitchArgs { + branch: Some(commit_id_str.clone()), + create: None, + detach: true, + }; + execute(args).await; + let head = Head::current().await; + let ref_name = match head { + Head::Detached(name) => name.to_string(), + _ => panic!("head not detached,unreachable"), + // Head::Detached(name) => name.to_string(), + }; + println!("detach {:?}", ref_name); + assert_eq!( + ref_name, commit_id_str, + "detach the head to a commit failed!" + ); } - assert!(env::set_current_dir(&test_dir).is_ok()); + + //switch branch back to the master + { + let args = SwitchArgs { + branch: Some("master".to_string()), + create: None, + detach: false, + }; + execute(args).await; + let head = Head::current().await; + let ref_name = match head { + Head::Branch(name) => name, + _ => panic!("head not in branch,unreachable"), + // Head::Detached(name) => name.to_string(), + }; + assert_eq!(ref_name, "master", "switch bach to the master failed!"); + } + } + #[tokio::test] + #[serial] + /// Tests the core functionality of the switch command module. + /// Validates branch switching operations and working directory status checks. + async fn test_parts_of_switch_module_function() { + println!("\n\x1b[1mTest some functions of the switch module.\x1b[0m"); + let temp_path = tempdir().unwrap(); + test::setup_with_new_libra_in(temp_path.path()).await; + let _guard = ChangeDirGuard::new(temp_path.path()); + println!("temp_path {:?}", temp_path); + + //Test check the branch + test_switch_function().await; // Test the switch module funsctions test_check_status().await; - - // Clean the test data - assert!(env::set_current_dir(&raw_dir).is_ok()); - if let Err(e) = fs::remove_dir_all(&target_dir) { - eprintln!("Error removing test directory: {}", e); - } } } diff --git a/libra/src/utils/client_storage.rs b/libra/src/utils/client_storage.rs index a464aa62f..4743b4efe 100644 --- a/libra/src/utils/client_storage.rs +++ b/libra/src/utils/client_storage.rs @@ -382,7 +382,8 @@ mod tests { use super::ClientStorage; #[test] - #[ignore] + /// Tests blob object storage and retrieval operations through the ClientStorage API. + /// Verifies that objects can be correctly stored, existence checked, retrieved, and content preserved. fn test_content_store() { let content = "Hello, world!"; let blob = Blob::from_content(content); @@ -402,6 +403,8 @@ mod tests { } #[test] + /// Tests object search functionality by partial hash prefix. + /// Verifies that objects can be correctly found when searching with a partial SHA1 hash. fn test_search() { let blob = Blob::from_content("Hello, world!"); @@ -420,6 +423,7 @@ mod tests { #[test] #[serial] + /// Prints all object hashes found in the test objects directory. fn test_list_objs() { let mut source = PathBuf::from(test::find_cargo_dir().parent().unwrap()); source.push("tests/objects"); @@ -431,6 +435,7 @@ mod tests { } #[test] + ///tests the function of get_object_type can get the object's type right. fn test_get_obj_type() { let blob = Blob::from_content("Hello, world!"); @@ -447,6 +452,7 @@ mod tests { } #[test] + ///Tests confirm that the compression and decompression features are functioning as expected. fn test_decompress() { let data = b"blob 13\0Hello, world!"; let compressed_data = ClientStorage::compress_zlib(data).unwrap(); @@ -456,6 +462,7 @@ mod tests { #[test] #[serial] + /// Tests decompression of a specific git object file from test data to verify zlib implementation. fn test_decompress_2() { test::reset_working_dir(); let pack_file = "../tests/data/objects/4b/00093bee9b3ef5afc5f8e3645dc39cfa2f49aa"; diff --git a/libra/src/utils/util.rs b/libra/src/utils/util.rs index 4a30e25e3..f197dc0f5 100644 --- a/libra/src/utils/util.rs +++ b/libra/src/utils/util.rs @@ -384,6 +384,7 @@ mod test { use tempfile::tempdir; #[test] + ///test get current directory success. fn cur_dir_returns_current_directory() { let expected = env::current_dir().unwrap(); let actual = cur_dir(); @@ -392,6 +393,7 @@ mod test { #[test] #[serial] + ///test the function of is_sub_path. fn test_is_sub_path() { let _guard = test::ChangeDirGuard::new(Path::new(env!("CARGO_MANIFEST_DIR"))); @@ -402,6 +404,7 @@ mod test { } #[test] + ///test the function of to_relative. fn test_to_relative() { assert_eq!(to_relative("src/main.rs", "src"), PathBuf::from("main.rs")); assert_eq!(to_relative(".", "src"), PathBuf::from("..")); @@ -409,6 +412,7 @@ mod test { #[tokio::test] #[serial] + ///test the function of to_workdir_path. async fn test_to_workdir_path() { let temp_path = tempdir().unwrap(); test::setup_with_new_libra_in(temp_path.path()).await; @@ -425,6 +429,7 @@ mod test { #[test] #[serial] + /// Tests that files matching patterns in .libraignore are correctly identified as ignored. fn test_check_gitignore_ignore() { let temp_path = tempdir().unwrap(); let _guard = test::ChangeDirGuard::new(temp_path.path()); @@ -438,6 +443,7 @@ mod test { #[test] #[serial] + /// Tests ignore pattern matching in subdirectories with .libraignore files at different directory levels. fn test_check_gitignore_ignore_subdirectory() { let temp_path = tempdir().unwrap(); let _guard = test::ChangeDirGuard::new(temp_path.path()); @@ -455,6 +461,7 @@ mod test { #[test] #[serial] + /// Tests that files not matching patterns in .libraignore are correctly identified as not ignored. fn test_check_gitignore_not_ignore() { let temp_path = tempdir().unwrap(); let _guard = test::ChangeDirGuard::new(temp_path.path()); @@ -469,6 +476,7 @@ mod test { #[test] #[serial] + /// Tests that files not matching subdirectory-specific patterns in .libraignore are correctly identified as not ignored. fn test_check_gitignore_not_ignore_subdirectory() { let temp_path = tempdir().unwrap(); let _guard = test::ChangeDirGuard::new(temp_path.path()); From 8ca668f8e212b650bdf5ec9b330d6f7341f0fb9f Mon Sep 17 00:00:00 2001 From: yyjeqhc <1772413353@qq.com> Date: Thu, 1 May 2025 16:59:59 +0800 Subject: [PATCH 2/2] 1.add comments to the tests of libra 2.test use containers to run mega server --- .github/workflows/base.yml | 2 +- libra/Cargo.toml | 3 + libra/src/command/add.rs | 1 + libra/src/command/branch.rs | 7 +- libra/src/command/checkout.rs | 2 + libra/src/command/clone.rs | 3 + libra/src/command/commit.rs | 11 ++ libra/src/command/diff.rs | 6 + libra/src/command/init.rs | 33 +++-- libra/src/command/log.rs | 34 ++++- libra/src/command/mod.rs | 3 + libra/src/command/push.rs | 4 + libra/src/command/status.rs | 2 + libra/src/command/switch.rs | 136 +++++++++++++++----- libra/src/internal/protocol/lfs_client.rs | 2 +- libra/src/utils/client_storage.rs | 9 +- libra/src/utils/util.rs | 8 ++ libra/tests/mega_test.rs | 145 ++++++++++++++++++++++ mega/Cargo.toml | 3 + mega/tests/lfs_test.rs | 92 ++++++++++++-- 20 files changed, 453 insertions(+), 53 deletions(-) create mode 100644 libra/tests/mega_test.rs diff --git a/.github/workflows/base.yml b/.github/workflows/base.yml index 88078b103..27dd21f50 100644 --- a/.github/workflows/base.yml +++ b/.github/workflows/base.yml @@ -186,4 +186,4 @@ jobs: steps: - uses: actions/checkout@v4 - uses: actions-rust-lang/setup-rust-toolchain@v1 - - run: cargo clippy --manifest-path scorpio/Cargo.toml --all-targets --all-features -- -D warnings + - run: cargo clippy --manifest-path scorpio/Cargo.toml --all-targets --all-features -- -D warnings \ No newline at end of file diff --git a/libra/Cargo.toml b/libra/Cargo.toml index 0c26dbd3c..2ee6c8955 100644 --- a/libra/Cargo.toml +++ b/libra/Cargo.toml @@ -8,6 +8,7 @@ default = [] p2p = ["gemini"] [dependencies] +http = "1.3.1" anyhow = { workspace = true } byte-unit = "5.1.4" byteorder = "1.5.0" @@ -61,3 +62,5 @@ tempfile = { workspace = true } serial_test = { workspace = true } tokio = { workspace = true, features = ["macros", "process"] } tracing-test = "0.2.4" +testcontainers = { version = "0.23.0", features = ["http_wait","reusable-containers"] } +reqwest = { version = "0.11", features = ["blocking"] } diff --git a/libra/src/command/add.rs b/libra/src/command/add.rs index 28f22d0ac..cb930fc86 100644 --- a/libra/src/command/add.rs +++ b/libra/src/command/add.rs @@ -158,6 +158,7 @@ mod test { #[test] #[should_panic] + /// Test that `-A` and `-u` cannot be used together fn test_args_parse_update_conflict_with_all() { AddArgs::try_parse_from(["test", "-A", "-u"]).unwrap(); } diff --git a/libra/src/command/branch.rs b/libra/src/command/branch.rs index 408b7fc38..4eb3e2aca 100644 --- a/libra/src/command/branch.rs +++ b/libra/src/command/branch.rs @@ -240,6 +240,8 @@ mod tests { #[tokio::test] #[serial] + /// Tests core branch management functionality including creation and listing. + /// Verifies branches can be created from specific commits. async fn test_branch() { let temp_path = tempdir().unwrap(); test::setup_with_new_libra_in(temp_path.path()).await; @@ -330,6 +332,8 @@ mod tests { #[tokio::test] #[serial] + /// Tests branch creation using remote branches as starting points. + /// Verifies that local branches can be created from remote branch references. async fn test_create_branch_from_remote() { let temp_path = tempdir().unwrap(); test::setup_with_new_libra_in(temp_path.path()).await; @@ -366,6 +370,7 @@ mod tests { #[tokio::test] #[serial] + /// Tests the behavior of creating a branch with an invalid name. async fn test_invalid_branch_name() { let temp_path = tempdir().unwrap(); test::setup_with_new_libra_in(temp_path.path()).await; @@ -391,7 +396,7 @@ mod tests { }; execute(args).await; - let branch = Branch::find_branch("new", None).await; + let branch = Branch::find_branch("@{mega}", None).await; assert!(branch.is_none(), "invalid branch should not be created"); } } diff --git a/libra/src/command/checkout.rs b/libra/src/command/checkout.rs index 7d49fcc4e..e91f60e2a 100644 --- a/libra/src/command/checkout.rs +++ b/libra/src/command/checkout.rs @@ -170,6 +170,8 @@ mod tests { #[tokio::test] #[serial] + /// Tests branch creation, switching and validation functionality in the checkout module. + /// Verifies proper branch management and HEAD reference updates when switching between branches. async fn test_checkout_module_functions() { println!("\n\x1b[1mTest checkout module functions.\x1b[0m"); diff --git a/libra/src/command/clone.rs b/libra/src/command/clone.rs index ef2d553c1..c90d2f26b 100644 --- a/libra/src/command/clone.rs +++ b/libra/src/command/clone.rs @@ -163,6 +163,7 @@ mod tests { #[tokio::test] #[serial] + /// Test the clone command with a specific branch async fn test_clone_branch() { let temp_path = tempdir().unwrap(); let _guard = test::ChangeDirGuard::new(temp_path.path()); @@ -191,6 +192,7 @@ mod tests { #[tokio::test] #[serial] + /// Test the clone command with the default branch async fn test_clone_default_branch() { let temp_path = tempdir().unwrap(); let _guard = test::ChangeDirGuard::new(temp_path.path()); @@ -219,6 +221,7 @@ mod tests { #[tokio::test] #[serial] + /// Test the clone command with an empty repository async fn test_clone_empty_repo() { let temp_path = tempdir().unwrap(); let _guard = test::ChangeDirGuard::new(temp_path.path()); diff --git a/libra/src/command/commit.rs b/libra/src/command/commit.rs index bb79ad3d4..497b7885b 100644 --- a/libra/src/command/commit.rs +++ b/libra/src/command/commit.rs @@ -30,6 +30,7 @@ pub struct CommitArgs { #[arg(long, requires("message"))] pub conventional: bool, + /// amend the last commit #[arg(long)] pub amend: bool, } @@ -211,6 +212,7 @@ mod test { use super::*; #[test] + ///Testing basic parameter parsing functionality. fn test_parse_args() { let args = CommitArgs::try_parse_from(["commit", "-m", "init"]); assert!(args.is_ok()); @@ -236,6 +238,8 @@ mod test { #[tokio::test] #[serial] + /// Tests the recursive tree creation from index entries. + /// Verifies that tree objects are correctly created, saved to storage, and properly organized in a hierarchical structure. async fn test_create_tree() { let temp_path = tempdir().unwrap(); test::setup_with_new_libra_in(temp_path.path()).await; @@ -264,6 +268,9 @@ mod test { #[tokio::test] #[serial] #[should_panic] + /// A commit with no file changes should fail if `allow_empty` is false. + /// This test verifies that the commit command rejects empty changesets + /// when not explicitly permitted. async fn test_execute_commit_with_empty_index_fail() { let temp_path = tempdir().unwrap(); test::setup_with_new_libra_in(temp_path.path()).await; @@ -280,6 +287,10 @@ mod test { #[tokio::test] #[serial] + /// Tests normal commit functionality with both `--amend` and `--allow_empty` flags. + /// Verifies that: + /// 1. Amending works correctly when allowed + /// 2. Empty commits are permitted when explicitly enabled async fn test_execute_commit() { let temp_path = tempdir().unwrap(); test::setup_with_new_libra_in(temp_path.path()).await; diff --git a/libra/src/command/diff.rs b/libra/src/command/diff.rs index a44190962..6ca6e0844 100644 --- a/libra/src/command/diff.rs +++ b/libra/src/command/diff.rs @@ -350,6 +350,8 @@ mod test { use super::*; #[test] + /// Tests command line argument parsing for the diff command with various parameter combinations. + /// Verifies parameter requirements, conflicts and default values are handled correctly. fn test_args() { { let args = DiffArgs::try_parse_from(["diff", "--old", "old", "--new", "new", "paths"]); @@ -387,6 +389,8 @@ mod test { } #[test] + /// Tests the functionality of the `similar_diff_result` function. + /// Verifies that it correctly generates a diff between two text inputs. fn test_similar_diff_result() { let old = "Hello World\nThis is the second line.\nThis is the third."; let new = "Hallo Welt\nThis is the second line.\nThis is life.\nMoar and more"; @@ -398,6 +402,8 @@ mod test { #[tokio::test] #[serial] + /// Tests that the get_files_blobs function properly respects .libraignore patterns. + /// Verifies ignored files are correctly excluded from the blob collection process. async fn test_get_files_blob_gitignore() { let temp_path = tempdir().unwrap(); test::setup_with_new_libra_in(temp_path.path()).await; diff --git a/libra/src/command/init.rs b/libra/src/command/init.rs index b86ac33aa..e28a38467 100644 --- a/libra/src/command/init.rs +++ b/libra/src/command/init.rs @@ -249,12 +249,12 @@ fn set_dir_hidden(_dir: &str) -> io::Result<()> { /// Unit tests for the init module #[cfg(test)] mod tests { + use super::*; + use crate::internal::head::Head; + use crate::utils::test; use serial_test::serial; use tempfile::tempdir; - use super::*; - use crate::{internal::head::Head, utils::test}; - pub fn verify_init(base_dir: &Path) { // List of subdirectories to verify let dirs = ["objects/pack", "objects/info", "info"]; @@ -273,10 +273,13 @@ mod tests { assert!(file_path.exists(), "File {} does not exist", file); } } - /// Test the init function with no parameters #[tokio::test] + #[serial] + /// Test the init function with no parameters async fn test_init() { let target_dir = tempdir().unwrap().into_path(); + // let _guard = ChangeDirGuard::new(target_dir.clone()); + let args = InitArgs { bare: false, initial_branch: None, @@ -294,10 +297,13 @@ mod tests { verify_init(libra_dir.as_path()); } - /// Test the init function with the --bare flag #[tokio::test] + #[serial] + /// Test the init function with the --bare flag async fn test_init_bare() { let target_dir = tempdir().unwrap().into_path(); + // let _guard = ChangeDirGuard::new(target_dir.clone()); + // Run the init function with --bare flag let args = InitArgs { bare: true, @@ -311,8 +317,9 @@ mod tests { // Verify the contents of the other directory verify_init(target_dir.as_path()); } - /// Test the init function with the --bare flag and an existing repository #[tokio::test] + #[serial] + /// Test the init function with the --bare flag and an existing repository async fn test_init_bare_with_existing_repo() { let target_dir = tempdir().unwrap().into_path(); @@ -342,9 +349,9 @@ mod tests { assert!(err.to_string().contains("Initialization failed")); // Check error message contains "Already initialized" } - /// Test the init function with an initial branch name #[tokio::test] #[serial] + /// Test the init function with an initial branch name async fn test_init_with_initial_branch() { // Set up the test environment without a Libra repository let temp_path = tempdir().unwrap(); @@ -372,8 +379,9 @@ mod tests { }; } - /// Test the init function with an invalid branch name #[tokio::test] + #[serial] + /// Test the init function with an invalid branch name async fn test_init_with_invalid_branch() { // Cover all invalid branch name cases test_invalid_branch_name("master ").await; @@ -411,8 +419,9 @@ mod tests { assert!(err.to_string().contains("invalid branch name")); // Check error message contains "invalid branch name" } - /// Test the init function with [directory] parameter #[tokio::test] + #[serial] + /// Test the init function with [directory] parameter async fn test_init_with_directory() { let target_dir = tempdir().unwrap().into_path(); @@ -436,8 +445,9 @@ mod tests { verify_init(&libra_dir); } - /// Test the init function with invalid [directory] parameter #[tokio::test] + #[serial] + /// Test the init function with invalid [directory] parameter async fn test_init_with_invalid_directory() { let target_dir = tempdir().unwrap().into_path(); @@ -465,6 +475,8 @@ mod tests { } #[tokio::test] + #[serial] + /// Tests that repository initialization fails when lacking write permissions in the target directory async fn test_init_with_unauthorized_directory() { let target_dir = tempdir().unwrap().into_path(); @@ -503,6 +515,7 @@ mod tests { } #[tokio::test] + #[serial] /// Test the init function with the --quiet flag by using --show-output async fn test_init_quiet() { let target_dir = tempdir().unwrap().into_path(); diff --git a/libra/src/command/log.rs b/libra/src/command/log.rs index afcdcbc81..5d344481e 100644 --- a/libra/src/command/log.rs +++ b/libra/src/command/log.rs @@ -127,6 +127,7 @@ pub async fn execute(args: LogArgs) { #[cfg(test)] mod tests { use super::*; + use crate::utils::test::ChangeDirGuard; use crate::{command::save_object, utils::test}; use common::utils::format_commit_msg; use mercury::{hash::SHA1, internal::object::commit::Commit}; @@ -135,6 +136,7 @@ mod tests { #[tokio::test] #[serial] + /// Tests retrieval of commits reachable from a specific commit hash async fn test_get_reachable_commits() { let temp_path = tempdir().unwrap(); test::setup_with_new_libra_in(temp_path.path()).await; @@ -147,14 +149,40 @@ mod tests { } #[tokio::test] - #[ignore] // ignore this test because it will open less and block the test + #[serial] + /// Tests log command execution functionality async fn test_execute_log() { let temp_path = tempdir().unwrap(); test::setup_with_new_libra_in(temp_path.path()).await; + let _guard = ChangeDirGuard::new(temp_path.path()); let _ = create_test_commit_tree().await; - let args = LogArgs { number: Some(6) }; - execute(args).await; + // let args = LogArgs { number: Some(1) }; + // execute(args).await; + let head = Head::current().await; + // check if the current branch has any commits + if let Head::Branch(branch_name) = head.to_owned() { + let branch = Branch::find_branch(&branch_name, None).await; + if branch.is_none() { + panic!( + "fatal: your current branch '{}' does not have any commits yet ", + branch_name + ); + } + } + + let commit_hash = Head::current_commit().await.unwrap().to_string(); + + let mut reachable_commits = get_reachable_commits(commit_hash.clone()).await; + // default sort with signature time + reachable_commits.sort_by(|a, b| b.committer.timestamp.cmp(&a.committer.timestamp)); + //the last seven commits + let max_output_number = min(6, reachable_commits.len()); + let mut output_number = 6; + for commit in reachable_commits.iter().take(max_output_number) { + assert_eq!(commit.message, format!("\nCommit_{}", output_number)); + output_number -= 1; + } } /// create a test commit tree structure as graph and create branch (master) head to commit 6 diff --git a/libra/src/command/mod.rs b/libra/src/command/mod.rs index 29b042e0c..4987db780 100644 --- a/libra/src/command/mod.rs +++ b/libra/src/command/mod.rs @@ -138,6 +138,7 @@ mod tests { use crate::utils::test; #[tokio::test] #[serial] + /// Test objects can be correctly saved to and loaded from storage. async fn test_save_load_object() { let temp_path = tempdir().unwrap(); test::setup_with_new_libra_in(temp_path.path()).await; @@ -148,6 +149,8 @@ mod tests { } #[test] + /// Tests commit message formatting and parsing with signatures. + /// Verifies correct handling of GPG/SSH signatures and proper message extraction. fn test_format_and_parse_commit_msg() { { let msg = "commit message"; diff --git a/libra/src/command/push.rs b/libra/src/command/push.rs index 35c2f2ca1..a6994b673 100644 --- a/libra/src/command/push.rs +++ b/libra/src/command/push.rs @@ -331,6 +331,8 @@ fn diff_tree_objs(old_tree: Option<&SHA1>, new_tree: &SHA1) -> HashSet { mod test { use super::*; #[test] + /// Tests successful parsing of push command arguments with different parameter combinations. + /// Verifies repository, refspec and upstream flag settings are correctly interpreted. fn test_parse_args_success() { let args = vec!["push"]; let args = PushArgs::parse_from(args); @@ -352,6 +354,8 @@ mod test { } #[test] + /// Tests failure cases for push command argument parsing with invalid parameter combinations. + /// Verifies that missing required parameters are properly detected as errors. fn test_parse_args_fail() { let args = vec!["push", "-u"]; let args = PushArgs::try_parse_from(args); diff --git a/libra/src/command/status.rs b/libra/src/command/status.rs index 25eb3edfe..a2cb4018e 100644 --- a/libra/src/command/status.rs +++ b/libra/src/command/status.rs @@ -205,6 +205,8 @@ mod test { #[tokio::test] #[serial] + /// Tests the file status detection functionality with respect to ignore patterns. + /// Verifies that files matching patterns in .libraignore are properly excluded from status reports. async fn test_changes_to_be_staged() { let test_dir = tempdir().unwrap(); test::setup_with_new_libra_in(test_dir.path()).await; diff --git a/libra/src/command/switch.rs b/libra/src/command/switch.rs index 30cf74b49..49b0e9f79 100644 --- a/libra/src/command/switch.rs +++ b/libra/src/command/switch.rs @@ -120,13 +120,20 @@ async fn restore_to_commit(commit_id: SHA1) { mod tests { use super::*; use crate::command::add; - use crate::command::init; + use crate::command::commit; + use crate::command::commit::CommitArgs; use crate::command::restore::RestoreArgs; + use crate::{ + command::load_object, + utils::test::{self, ChangeDirGuard}, + }; + use mercury::internal::object::commit::Commit; use serial_test::serial; + use std::fs; use std::str::FromStr; - use std::{env, fs}; use tempfile::tempdir; #[test] + /// Test parsing RestoreArgs from command-line style arguments fn test_parse_from() { let commit_id = SHA1::from_str("0cb5eb6281e1c0df48a70716869686c694706189").unwrap(); let restore_args = RestoreArgs::parse_from([ @@ -165,40 +172,111 @@ mod tests { assert!(check_status().await); } - #[tokio::test] - #[serial] - async fn test_parts_of_switch_module_function() { - println!("\n\x1b[1mTest some functions of the switch module.\x1b[0m"); + async fn test_switch_function() { + println!("\n\x1b[1mTest switch function.\x1b[0m"); - let target_dir = tempdir().unwrap().into_path(); + // create first empty commit + { + let args = CommitArgs { + message: "first".to_string(), + allow_empty: true, + conventional: false, + amend: false, + }; + commit::execute(args).await; + } - // Create a test directory and set args - let test_dir = target_dir.join("test_check_status"); - fs::create_dir(&test_dir).unwrap(); + // create a new branch and switch to it + { + let args = SwitchArgs { + branch: None, + create: Some("test_branch".to_string()), + detach: false, + }; + execute(args).await; + let head = Head::current().await; + let ref_name = match head { + Head::Branch(name) => name, + _ => panic!("head not in branch,unreachable"), + // Head::Detached(name) => name.to_string(), + }; + assert_eq!( + ref_name, "test_branch", + "create a new branch and switch to it failed!" + ); + } - let init_args = init::InitArgs { - bare: false, - initial_branch: None, - repo_directory: test_dir.to_str().unwrap().to_owned(), - quiet: false, - }; + //detach the head to a commit + { + let head = Head::current().await; + let ref_name = match head { + Head::Branch(name) => name, + _ => panic!("head not in branch,unreachable"), + // Head::Detached(name) => name.to_string(), + }; + let branch = Branch::find_branch(&ref_name, None).await.unwrap(); + let commit: Commit = load_object(&branch.commit).unwrap(); + let commit_id_str = commit.id.to_string(); - // Run the init function and change the current directory to the test directory - let raw_dir = env::current_dir().unwrap(); - let result = init::init(init_args).await; - if let Err(e) = result { - eprintln!("Error initializing repository: {}", e); - return; + let args = CommitArgs { + message: "second".to_string(), + allow_empty: true, + conventional: false, + amend: false, + }; + commit::execute(args).await; + + let args = SwitchArgs { + branch: Some(commit_id_str.clone()), + create: None, + detach: true, + }; + execute(args).await; + let head = Head::current().await; + let ref_name = match head { + Head::Detached(name) => name.to_string(), + _ => panic!("head not detached,unreachable"), + // Head::Detached(name) => name.to_string(), + }; + println!("detach {:?}", ref_name); + assert_eq!( + ref_name, commit_id_str, + "detach the head to a commit failed!" + ); } - assert!(env::set_current_dir(&test_dir).is_ok()); + + //switch branch back to the master + { + let args = SwitchArgs { + branch: Some("master".to_string()), + create: None, + detach: false, + }; + execute(args).await; + let head = Head::current().await; + let ref_name = match head { + Head::Branch(name) => name, + _ => panic!("head not in branch,unreachable"), + // Head::Detached(name) => name.to_string(), + }; + assert_eq!(ref_name, "master", "switch bach to the master failed!"); + } + } + #[tokio::test] + #[serial] + /// Tests the core functionality of the switch command module. + /// Validates branch switching operations and working directory status checks. + async fn test_parts_of_switch_module_function() { + println!("\n\x1b[1mTest some functions of the switch module.\x1b[0m"); + let temp_path = tempdir().unwrap(); + test::setup_with_new_libra_in(temp_path.path()).await; + let _guard = ChangeDirGuard::new(temp_path.path()); + println!("temp_path {:?}", temp_path); + + //Test check the branch + test_switch_function().await; // Test the switch module funsctions test_check_status().await; - - // Clean the test data - assert!(env::set_current_dir(&raw_dir).is_ok()); - if let Err(e) = fs::remove_dir_all(&target_dir) { - eprintln!("Error removing test directory: {}", e); - } } } diff --git a/libra/src/internal/protocol/lfs_client.rs b/libra/src/internal/protocol/lfs_client.rs index ab5175271..23879e11d 100644 --- a/libra/src/internal/protocol/lfs_client.rs +++ b/libra/src/internal/protocol/lfs_client.rs @@ -652,7 +652,7 @@ impl LFSClient { } } - async fn download_chunk( + pub async fn download_chunk( &self, url: &str, hash: &str, diff --git a/libra/src/utils/client_storage.rs b/libra/src/utils/client_storage.rs index a464aa62f..4743b4efe 100644 --- a/libra/src/utils/client_storage.rs +++ b/libra/src/utils/client_storage.rs @@ -382,7 +382,8 @@ mod tests { use super::ClientStorage; #[test] - #[ignore] + /// Tests blob object storage and retrieval operations through the ClientStorage API. + /// Verifies that objects can be correctly stored, existence checked, retrieved, and content preserved. fn test_content_store() { let content = "Hello, world!"; let blob = Blob::from_content(content); @@ -402,6 +403,8 @@ mod tests { } #[test] + /// Tests object search functionality by partial hash prefix. + /// Verifies that objects can be correctly found when searching with a partial SHA1 hash. fn test_search() { let blob = Blob::from_content("Hello, world!"); @@ -420,6 +423,7 @@ mod tests { #[test] #[serial] + /// Prints all object hashes found in the test objects directory. fn test_list_objs() { let mut source = PathBuf::from(test::find_cargo_dir().parent().unwrap()); source.push("tests/objects"); @@ -431,6 +435,7 @@ mod tests { } #[test] + ///tests the function of get_object_type can get the object's type right. fn test_get_obj_type() { let blob = Blob::from_content("Hello, world!"); @@ -447,6 +452,7 @@ mod tests { } #[test] + ///Tests confirm that the compression and decompression features are functioning as expected. fn test_decompress() { let data = b"blob 13\0Hello, world!"; let compressed_data = ClientStorage::compress_zlib(data).unwrap(); @@ -456,6 +462,7 @@ mod tests { #[test] #[serial] + /// Tests decompression of a specific git object file from test data to verify zlib implementation. fn test_decompress_2() { test::reset_working_dir(); let pack_file = "../tests/data/objects/4b/00093bee9b3ef5afc5f8e3645dc39cfa2f49aa"; diff --git a/libra/src/utils/util.rs b/libra/src/utils/util.rs index 4a30e25e3..9c3b12a91 100644 --- a/libra/src/utils/util.rs +++ b/libra/src/utils/util.rs @@ -384,6 +384,7 @@ mod test { use tempfile::tempdir; #[test] + ///Test get current directory success. fn cur_dir_returns_current_directory() { let expected = env::current_dir().unwrap(); let actual = cur_dir(); @@ -392,6 +393,7 @@ mod test { #[test] #[serial] + ///Test the function of is_sub_path. fn test_is_sub_path() { let _guard = test::ChangeDirGuard::new(Path::new(env!("CARGO_MANIFEST_DIR"))); @@ -402,6 +404,7 @@ mod test { } #[test] + ///Test the function of to_relative. fn test_to_relative() { assert_eq!(to_relative("src/main.rs", "src"), PathBuf::from("main.rs")); assert_eq!(to_relative(".", "src"), PathBuf::from("..")); @@ -409,6 +412,7 @@ mod test { #[tokio::test] #[serial] + ///Test the function of to_workdir_path. async fn test_to_workdir_path() { let temp_path = tempdir().unwrap(); test::setup_with_new_libra_in(temp_path.path()).await; @@ -425,6 +429,7 @@ mod test { #[test] #[serial] + /// Tests that files matching patterns in .libraignore are correctly identified as ignored. fn test_check_gitignore_ignore() { let temp_path = tempdir().unwrap(); let _guard = test::ChangeDirGuard::new(temp_path.path()); @@ -438,6 +443,7 @@ mod test { #[test] #[serial] + /// Tests ignore pattern matching in subdirectories with .libraignore files at different directory levels. fn test_check_gitignore_ignore_subdirectory() { let temp_path = tempdir().unwrap(); let _guard = test::ChangeDirGuard::new(temp_path.path()); @@ -455,6 +461,7 @@ mod test { #[test] #[serial] + /// Tests that files not matching patterns in .libraignore are correctly identified as not ignored. fn test_check_gitignore_not_ignore() { let temp_path = tempdir().unwrap(); let _guard = test::ChangeDirGuard::new(temp_path.path()); @@ -469,6 +476,7 @@ mod test { #[test] #[serial] + /// Tests that files not matching subdirectory-specific patterns in .libraignore are correctly identified as not ignored. fn test_check_gitignore_not_ignore_subdirectory() { let temp_path = tempdir().unwrap(); let _guard = test::ChangeDirGuard::new(temp_path.path()); diff --git a/libra/tests/mega_test.rs b/libra/tests/mega_test.rs new file mode 100644 index 000000000..a327b50e1 --- /dev/null +++ b/libra/tests/mega_test.rs @@ -0,0 +1,145 @@ +use http::Method; +use lazy_static::lazy_static; +use libra::internal::protocol::lfs_client::LFSClient; +use libra::internal::protocol::ProtocolClient; +use libra::utils::lfs; +use reqwest::Url; +/// integration tests for the mega module +use std::env; +use std::net::TcpStream; +use std::path::PathBuf; +use testcontainers::core::wait::HttpWaitStrategy; +use testcontainers::{ + core::{IntoContainerPort, ReuseDirective, WaitFor}, + runners::AsyncRunner, + ContainerAsync, GenericImage, ImageExt, +}; +use tokio::time::Duration; + +lazy_static! { + + static ref TARGET: String = { + // mega/mega, absolute + let mut manifest = PathBuf::from(env!("CARGO_MANIFEST_DIR")); // Get env at compile time + manifest.pop(); + manifest.to_str().unwrap().to_string() + }; + + static ref LIBRA: PathBuf = { + let path = if cfg!(target_os = "windows") { + format!("{}/target/debug/libra.exe", TARGET.as_str()) + } else { + format!("{}/target/debug/libra", TARGET.as_str()) + }; + PathBuf::from(path) + }; + + static ref MEGA: PathBuf = { + let path = if cfg!(target_os = "windows") { + format!("{}/target/debug/mega.exe", TARGET.as_str()) + } else { + format!("{}/target/debug/mega", TARGET.as_str()) + }; + PathBuf::from(path) + }; + + static ref CONFIG: PathBuf = { + let path = format!("{}/mega/config.toml",TARGET.as_str()); + PathBuf::from(path) + }; +} + +fn is_port_in_use(port: u16) -> bool { + TcpStream::connect_timeout( + &format!("127.0.0.1:{}", port).parse().unwrap(), + Duration::from_millis(1000), + ) + .is_ok() +} + +async fn mega_container(mapping_port: u16) -> ContainerAsync { + println!("MEGA {:?} ", MEGA.to_str().unwrap()); + println!("CONFIG {:?} ", CONFIG.to_str().unwrap()); + if !MEGA.exists() { + panic!("mega binary not found in \"target/debug/\", skip lfs test"); + } + if is_port_in_use(mapping_port) { + panic!("port {} is already in use", mapping_port); + } + let port_str = mapping_port.to_string(); + let cmd = vec![ + "/root/mega", + "service", + "multi", + "http", + "-p", + &port_str, + "--host", + "0.0.0.0", + ]; + + GenericImage::new("ubuntu", "latest") + .with_exposed_port(mapping_port.tcp()) + .with_wait_for(WaitFor::Http( + HttpWaitStrategy::new("/") + .with_method(Method::GET) + .with_expected_status_code(404_u16), + )) + .with_mapped_port(mapping_port, mapping_port.tcp()) + .with_copy_to("/root/mega", MEGA.clone()) + .with_copy_to("/root/config.toml", CONFIG.clone()) + .with_env_var("MEGA_authentication__enable_http_auth", "false") + .with_working_dir("/root") + .with_reuse(ReuseDirective::Never) + .with_cmd(cmd) + .start() + .await + .expect("Failed to start mega_server") +} + +pub async fn mega_bootstrap_servers(mapping_port: u16) -> (ContainerAsync, String) { + let container = mega_container(mapping_port).await; + let mega_ip = container.get_bridge_ip_address().await.unwrap(); + let mega_port: u16 = container.get_host_port_ipv4(mapping_port).await.unwrap(); + (container, format!("http://{}:{}", mega_ip, mega_port)) +} + +#[tokio::test] +///Use container to run mega server and test push and download +async fn test_push_object_and_download() { + let (_container, mega_server_url) = mega_bootstrap_servers(8000).await; + println!("container: {}", mega_server_url); + let file_map = mercury::test_utils::setup_lfs_file().await; + let file = file_map + .get("git-2d187177923cd618a75da6c6db45bb89d92bd504.pack") + .unwrap(); + let client = LFSClient::from_url(&Url::parse(&mega_server_url).unwrap()); + let oid = lfs::calc_lfs_file_hash(file).unwrap(); + + match client.push_object(&oid, file).await { + Ok(_) => println!("Pushed successfully."), + Err(err) => eprintln!("Push failed: {:?}", err), + } + #[cfg(feature = "p2p")] + test_download_chunk_mega(&mega_server_url).await; +} + +#[cfg(feature = "p2p")] +async fn test_download_chunk_mega(mega_server_url: &str) { + let file_map = mercury::test_utils::setup_lfs_file().await; + let file = file_map + .get("git-2d187177923cd618a75da6c6db45bb89d92bd504.pack") + .unwrap(); + let client = LFSClient::from_url(&Url::parse(mega_server_url).unwrap()); + let oid = lfs::calc_lfs_file_hash(file).unwrap(); + let sub_oid = "ee225720cc31599c749fbe9b18f6c8346fa3246839f0dea7ffd3224dbb067952".to_string(); // offset 83886080 size 20971520 + let url = format!("{}/objects/{}/{}", mega_server_url, oid, sub_oid); + let size = 20971520; + let offset = 83886080; + let data = client + .download_chunk(&url, &sub_oid, size, offset, |_| {}) + .await + .unwrap(); + println!("test_download_chunk_mega success. download_len {}", data.len()); + assert_eq!(data.len(), size); +} diff --git a/mega/Cargo.toml b/mega/Cargo.toml index 912e5dd50..7b91886aa 100644 --- a/mega/Cargo.toml +++ b/mega/Cargo.toml @@ -44,3 +44,6 @@ serial_test = { workspace = true } lazy_static = { workspace = true } assert_cmd = "2.0.16" scopeguard = { workspace = true } +testcontainers = { version = "0.23.0", features = ["http_wait","reusable-containers"] } +reqwest = { version = "0.11", features = ["blocking"] } +http = "1.3.1" \ No newline at end of file diff --git a/mega/tests/lfs_test.rs b/mega/tests/lfs_test.rs index f4b83224a..faefda156 100644 --- a/mega/tests/lfs_test.rs +++ b/mega/tests/lfs_test.rs @@ -11,7 +11,13 @@ use std::process::Command; use std::time::Duration; use std::{env, fs, io, thread}; use tempfile::TempDir; - +use http::Method; +use testcontainers::core::wait::HttpWaitStrategy; +use testcontainers::{ + core::{IntoContainerPort, ReuseDirective, WaitFor}, + runners::AsyncRunner, + ContainerAsync, GenericImage, ImageExt, +}; const LARGE_FILE_SIZE_MB: usize = 60; struct ChildGuard(std::process::Child); @@ -30,27 +36,32 @@ lazy_static! { static ref TARGET: String = { // mega/mega, absolute let mut manifest = PathBuf::from(env!("CARGO_MANIFEST_DIR")); // Get env at compile time - manifest.pop(); // remove "mega" from path - manifest.join("target").to_str().unwrap().to_string() + manifest.pop(); + manifest.to_str().unwrap().to_string() }; static ref LIBRA: PathBuf = { let path = if cfg!(target_os = "windows") { - format!("{}/debug/libra.exe", TARGET.as_str()) + format!("{}/target/debug/libra.exe", TARGET.as_str()) } else { - format!("{}/debug/libra", TARGET.as_str()) + format!("{}/target/debug/libra", TARGET.as_str()) }; PathBuf::from(path) }; static ref MEGA: PathBuf = { let path = if cfg!(target_os = "windows") { - format!("{}/debug/mega.exe", TARGET.as_str()) + format!("{}/target/debug/mega.exe", TARGET.as_str()) } else { - format!("{}/debug/mega", TARGET.as_str()) + format!("{}/target/debug/mega", TARGET.as_str()) }; PathBuf::from(path) }; + + static ref CONFIG: PathBuf = { + let path = format!("{}/mega/config.toml",TARGET.as_str()); + PathBuf::from(path) + }; } /// check if git lfs is installed @@ -245,7 +256,9 @@ fn libra_lfs_clone(url: &str) -> io::Result<()> { } #[test] +#[ignore] #[serial] +//Use containes insted. fn lfs_split_with_git() { assert!(check_git_lfs(), "git lfs is not installed"); @@ -293,3 +306,68 @@ fn lfs_split_with_libra() { clone_result.expect("Failed to clone large file from mega server"); thread::sleep(Duration::from_secs(1)); // wait for server to stop, avoiding affecting other tests } + +async fn mega_container(mapping_port: u16) -> ContainerAsync { + println!("MEGA {:?} ", MEGA.to_str().unwrap()); + println!("CONFIG {:?} ", CONFIG.to_str().unwrap()); + if !MEGA.exists() { + panic!("mega binary not found in \"target/debug/\", skip lfs test"); + } + if is_port_in_use(mapping_port) { + panic!("port {} is already in use", mapping_port); + } + let port_str = mapping_port.to_string(); + let cmd = vec![ + "/root/mega", + "service", + "multi", + "http", + "-p", + &port_str, + "--host", + "0.0.0.0", + ]; + + GenericImage::new("ubuntu", "latest") + .with_exposed_port(mapping_port.tcp()) + .with_wait_for(WaitFor::Http( + HttpWaitStrategy::new("/") + .with_method(Method::GET) + .with_expected_status_code(404_u16), + )) + .with_mapped_port(mapping_port, mapping_port.tcp()) + .with_copy_to("/root/mega", MEGA.clone()) + .with_copy_to("/root/config.toml", CONFIG.clone()) + .with_env_var("MEGA_authentication__enable_http_auth", "false") + .with_working_dir("/root") + .with_reuse(ReuseDirective::Never) + .with_cmd(cmd) + .start() + .await + .expect("Failed to start mega_server") +} + +pub async fn mega_bootstrap_servers(mapping_port: u16) -> (ContainerAsync, String) { + let container = mega_container(mapping_port).await; + let mega_ip = container.get_bridge_ip_address().await.unwrap(); + let mega_port: u16 = container.get_host_port_ipv4(mapping_port).await.unwrap(); + (container, format!("http://{}:{}", mega_ip, mega_port)) +} + +#[tokio::test] +///Use container to run mega server and test lfs_split +async fn test_lfs_split_with_containers() { + let (_container, mega_server_url) = mega_bootstrap_servers(9000).await; + println!("container: {}", mega_server_url); + + let url = &format!("{}/third-part/lfs.git", mega_server_url); + let lfs_url = mega_server_url; + let push_result = git_lfs_push(url, &lfs_url); + let clone_result = git_lfs_clone(url, &lfs_url); + + push_result.expect("Failed to push large file to mega server"); + clone_result.expect("Failed to clone large file from mega server"); + thread::sleep(Duration::from_secs(1)); // wait for server to stop, avoiding affecting other tests +} + +