From 6cd9fe4adbca396358cb9177fa687422a4a41d58 Mon Sep 17 00:00:00 2001 From: WangHaiMing Date: Wed, 30 Jul 2025 11:02:35 +0800 Subject: [PATCH 1/6] =?UTF-8?q?feat:=E5=A2=9E=E5=8A=A0add=E3=80=81diff?= =?UTF-8?q?=E3=80=81remove=E5=91=BD=E4=BB=A4=E6=B5=8B=E8=AF=95=E7=94=A8?= =?UTF-8?q?=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: WangHaiMing --- libra/tests/command/add_test.rs | 241 ++++++++++++++++++ libra/tests/command/diff_test.rs | 390 +++++++++++++++++++++++++++++ libra/tests/command/remove_test.rs | 319 +++++++++++++++++++++++ 3 files changed, 950 insertions(+) diff --git a/libra/tests/command/add_test.rs b/libra/tests/command/add_test.rs index 8b1378917..3eb54f481 100644 --- a/libra/tests/command/add_test.rs +++ b/libra/tests/command/add_test.rs @@ -1 +1,242 @@ +use super::*; +use std::fs; +use std::io::Write; +#[tokio::test] +#[serial] +/// Tests the basic functionality of add command by adding a single file +async fn test_add_single_file() { + let test_dir = tempdir().unwrap(); + test::setup_with_new_libra_in(test_dir.path()).await; + let _guard = test::ChangeDirGuard::new(test_dir.path()); + + // Create a new file + let file_content = "Hello, World!"; + let file_path = "test_file.txt"; + let mut file = fs::File::create(file_path).unwrap(); + file.write_all(file_content.as_bytes()).unwrap(); + + // Execute add command + add::execute(AddArgs { + pathspec: vec![String::from(file_path)], + all: false, + update: false, + verbose: false, + dry_run: false, + ignore_errors: false, + }) + .await; + + // Verify the file was added to index + let changes = changes_to_be_staged(); + assert!(!changes.new.iter().any(|x| x.to_str().unwrap() == file_path)); + assert!(changes.staged.iter().any(|x| x.to_str().unwrap() == file_path)); +} + +#[tokio::test] +#[serial] +/// Tests adding multiple files at once +async fn test_add_multiple_files() { + let test_dir = tempdir().unwrap(); + test::setup_with_new_libra_in(test_dir.path()).await; + let _guard = test::ChangeDirGuard::new(test_dir.path()); + + // Create multiple files + for i in 1..=3 { + let file_content = format!("File content {}", i); + let file_path = format!("test_file_{}.txt", i); + let mut file = fs::File::create(&file_path).unwrap(); + file.write_all(file_content.as_bytes()).unwrap(); + } + + // Execute add command + add::execute(AddArgs { + pathspec: vec![ + String::from("test_file_1.txt"), + String::from("test_file_2.txt"), + String::from("test_file_3.txt"), + ], + all: false, + update: false, + verbose: false, + dry_run: false, + ignore_errors: false, + }) + .await; + + // Verify all files were added to index + let changes = changes_to_be_staged(); + assert!(changes.staged.iter().any(|x| x.to_str().unwrap() == "test_file_1.txt")); + assert!(changes.staged.iter().any(|x| x.to_str().unwrap() == "test_file_2.txt")); + assert!(changes.staged.iter().any(|x| x.to_str().unwrap() == "test_file_3.txt")); +} + +#[tokio::test] +#[serial] +/// Tests the --all flag which adds all files in the working tree +async fn test_add_all_flag() { + let test_dir = tempdir().unwrap(); + test::setup_with_new_libra_in(test_dir.path()).await; + let _guard = test::ChangeDirGuard::new(test_dir.path()); + + // Create multiple files + for i in 1..=3 { + let file_content = format!("File content {}", i); + let file_path = format!("test_file_{}.txt", i); + let mut file = fs::File::create(&file_path).unwrap(); + file.write_all(file_content.as_bytes()).unwrap(); + } + + // Execute add command with --all flag + add::execute(AddArgs { + pathspec: vec![], + all: true, + update: false, + verbose: false, + dry_run: false, + ignore_errors: false, + }) + .await; + + // Verify all files were added to index + let changes = changes_to_be_staged(); + assert!(changes.staged.iter().any(|x| x.to_str().unwrap() == "test_file_1.txt")); + assert!(changes.staged.iter().any(|x| x.to_str().unwrap() == "test_file_2.txt")); + assert!(changes.staged.iter().any(|x| x.to_str().unwrap() == "test_file_3.txt")); +} + +#[tokio::test] +#[serial] +/// Tests the --update flag which only updates files already in the index +async fn test_add_update_flag() { + let test_dir = tempdir().unwrap(); + test::setup_with_new_libra_in(test_dir.path()).await; + let _guard = test::ChangeDirGuard::new(test_dir.path()); + + // Create files and add one to the index + let tracked_file = "tracked_file.txt"; + let untracked_file = "untracked_file.txt"; + + // Create and write initial content + let mut file1 = fs::File::create(tracked_file).unwrap(); + file1.write_all(b"Initial content").unwrap(); + + let mut file2 = fs::File::create(untracked_file).unwrap(); + file2.write_all(b"Initial content").unwrap(); + + // Add only one file to the index + add::execute(AddArgs { + pathspec: vec![String::from(tracked_file)], + all: false, + update: false, + verbose: false, + dry_run: false, + ignore_errors: false, + }) + .await; + + // Modify both files + let mut file1 = fs::OpenOptions::new().write(true).open(tracked_file).unwrap(); + file1.write_all(b" - Modified").unwrap(); + + let mut file2 = fs::OpenOptions::new().write(true).open(untracked_file).unwrap(); + file2.write_all(b" - Modified").unwrap(); + + // Execute add command with --update flag + add::execute(AddArgs { + pathspec: vec![String::from(".")], + all: false, + update: true, + verbose: false, + dry_run: false, + ignore_errors: false, + }) + .await; + + // Verify only tracked file was updated + let changes = changes_to_be_staged(); + // Tracked file should appear in changes as modified (because it was updated) + assert!(changes.modified.iter().any(|x| x.to_str().unwrap() == tracked_file)); + // Untracked file should still be untracked and show as new + assert!(changes.new.iter().any(|x| x.to_str().unwrap() == untracked_file)); +} + +#[tokio::test] +#[serial] +/// Tests adding files with respect to ignore patterns in .libraignore +async fn test_add_with_ignore_patterns() { + let test_dir = tempdir().unwrap(); + test::setup_with_new_libra_in(test_dir.path()).await; + let _guard = test::ChangeDirGuard::new(test_dir.path()); + + // Create .libraignore file + let mut ignore_file = fs::File::create(".libraignore").unwrap(); + ignore_file.write_all(b"ignored_*.txt\nignore_dir/").unwrap(); + + // Create files that should be ignored and not ignored + let ignored_file = "ignored_file.txt"; + let tracked_file = "tracked_file.txt"; + + // Create directory that should be ignored + fs::create_dir("ignore_dir").unwrap(); + let ignored_dir_file = "ignore_dir/file.txt"; + + // Create and write content + let mut file1 = fs::File::create(ignored_file).unwrap(); + file1.write_all(b"Should be ignored").unwrap(); + + let mut file2 = fs::File::create(tracked_file).unwrap(); + file2.write_all(b"Should be tracked").unwrap(); + + let mut file3 = fs::File::create(ignored_dir_file).unwrap(); + file3.write_all(b"Should be ignored").unwrap(); + + // Execute add command with all files + add::execute(AddArgs { + pathspec: vec![String::from(".")], + all: true, + update: false, + verbose: false, + dry_run: false, + ignore_errors: false, + }) + .await; + + // Verify only non-ignored files were added + let changes = changes_to_be_staged(); + // Ignored files should not appear in changes.new + assert!(!changes.new.iter().any(|x| x.to_str().unwrap() == ignored_file)); + // Directory files should not appear in changes.new + assert!(!changes.new.iter().any(|x| x.to_str().unwrap() == ignored_dir_file)); + // Non-ignored file should not show as new (was added) + assert!(!changes.new.iter().any(|x| x.to_str().unwrap() == tracked_file)); +} + +#[tokio::test] +#[serial] +/// Tests the dry-run flag which should not actually add files +async fn test_add_dry_run() { + let test_dir = tempdir().unwrap(); + test::setup_with_new_libra_in(test_dir.path()).await; + let _guard = test::ChangeDirGuard::new(test_dir.path()); + + // Create a file + let file_path = "test_file.txt"; + let mut file = fs::File::create(file_path).unwrap(); + file.write_all(b"Test content").unwrap(); + + // Execute add command with dry-run + add::execute(AddArgs { + pathspec: vec![String::from(file_path)], + all: false, + update: false, + verbose: false, + dry_run: true, + ignore_errors: false, + }) + .await; + + // Verify the file was not actually added to index + let changes = changes_to_be_staged(); + assert!(changes.new.iter().any(|x| x.to_str().unwrap() == file_path)); +} diff --git a/libra/tests/command/diff_test.rs b/libra/tests/command/diff_test.rs index 8b1378917..019abde83 100644 --- a/libra/tests/command/diff_test.rs +++ b/libra/tests/command/diff_test.rs @@ -1 +1,391 @@ +use super::*; +use std::fs; +use std::io::Write; +use std::path::PathBuf; +use libra::command::diff::{self, DiffArgs}; + +/// Helper function to create a file with content +fn create_file(path: &str, content: &str) { + let mut file = fs::File::create(path).unwrap(); + file.write_all(content.as_bytes()).unwrap(); +} + +/// Helper function to modify a file with new content +fn modify_file(path: &str, content: &str) { + let mut file = fs::OpenOptions::new().write(true).truncate(true).open(path).unwrap(); + file.write_all(content.as_bytes()).unwrap(); +} + +#[tokio::test] +#[serial] +/// Tests the basic diff functionality between working directory and HEAD +async fn test_basic_diff() { + let test_dir = tempdir().unwrap(); + test::setup_with_new_libra_in(test_dir.path()).await; + let _guard = test::ChangeDirGuard::new(test_dir.path()); + + // Create a file and add it to index + create_file("file1.txt", "Initial content\nLine 2\nLine 3\n"); + + add::execute(AddArgs { + pathspec: vec![String::from("file1.txt")], + all: false, + update: false, + verbose: false, + dry_run: false, + ignore_errors: false, + }) + .await; + + // Create initial commit + commit::execute(CommitArgs { + message: Some("Initial commit".to_string()), + allow_empty: false, + all: false, + amend: false, + signoff: false, + }) + .await + .unwrap(); + + // Modify the file + modify_file("file1.txt", "Modified content\nLine 2\nLine 3 changed\n"); + + // Run diff command + diff::execute(DiffArgs { + old: None, + new: None, + staged: false, + pathspec: vec![], + algorithm: Some("histogram".to_string()), + output: None, + }) + .await; + + // We can't easily capture stdout, so we'll check that the command didn't panic +} + +#[tokio::test] +#[serial] +/// Tests diff with staged changes +async fn test_diff_staged() { + let test_dir = tempdir().unwrap(); + test::setup_with_new_libra_in(test_dir.path()).await; + let _guard = test::ChangeDirGuard::new(test_dir.path()); + + // Create a file and add it to index + create_file("file1.txt", "Initial content\nLine 2\nLine 3\n"); + + add::execute(AddArgs { + pathspec: vec![String::from("file1.txt")], + all: false, + update: false, + verbose: false, + dry_run: false, + ignore_errors: false, + }) + .await; + + // Create initial commit + commit::execute(CommitArgs { + message: Some("Initial commit".to_string()), + allow_empty: false, + all: false, + amend: false, + signoff: false, + }) + .await + .unwrap(); + + // Modify the file and stage it + modify_file("file1.txt", "Modified content\nLine 2\nLine 3 changed\n"); + + add::execute(AddArgs { + pathspec: vec![String::from("file1.txt")], + all: false, + update: false, + verbose: false, + dry_run: false, + ignore_errors: false, + }) + .await; + + // Modify the file again (so working dir differs from staged) + modify_file("file1.txt", "Modified content again\nLine 2\nLine 3 changed again\n"); + + // Run diff command with --staged flag + diff::execute(DiffArgs { + old: None, + new: None, + staged: true, + pathspec: vec![], + algorithm: Some("histogram".to_string()), + output: None, + }) + .await; + + // The command should complete without panicking +} + +#[tokio::test] +#[serial] +/// Tests diff between two specific commits +async fn test_diff_between_commits() { + let test_dir = tempdir().unwrap(); + test::setup_with_new_libra_in(test_dir.path()).await; + let _guard = test::ChangeDirGuard::new(test_dir.path()); + + // Create a file and make initial commit + create_file("file1.txt", "Initial content\nLine 2\nLine 3\n"); + + add::execute(AddArgs { + pathspec: vec![String::from("file1.txt")], + all: false, + update: false, + verbose: false, + dry_run: false, + ignore_errors: false, + }) + .await; + + commit::execute(CommitArgs { + message: Some("Initial commit".to_string()), + allow_empty: false, + all: false, + amend: false, + signoff: false, + }) + .await + .unwrap(); + + // Get the first commit hash + let first_commit = Head::current_commit().await.unwrap(); + + // Modify file and create a second commit + modify_file("file1.txt", "Modified content\nLine 2\nLine 3 changed\n"); + + add::execute(AddArgs { + pathspec: vec![String::from("file1.txt")], + all: false, + update: false, + verbose: false, + dry_run: false, + ignore_errors: false, + }) + .await; + + commit::execute(CommitArgs { + message: Some("Second commit".to_string()), + allow_empty: false, + all: false, + amend: false, + signoff: false, + }) + .await + .unwrap(); + + // Get the second commit hash + let second_commit = Head::current_commit().await.unwrap(); + + // Run diff command comparing the two commits + diff::execute(DiffArgs { + old: Some(first_commit.to_string()), + new: Some(second_commit.to_string()), + staged: false, + pathspec: vec![], + algorithm: Some("histogram".to_string()), + output: None, + }) + .await; + + // The command should complete without panicking +} + +#[tokio::test] +#[serial] +/// Tests diff with specific file path +async fn test_diff_with_pathspec() { + let test_dir = tempdir().unwrap(); + test::setup_with_new_libra_in(test_dir.path()).await; + let _guard = test::ChangeDirGuard::new(test_dir.path()); + + // Create multiple files and commit them + create_file("file1.txt", "File 1 content\nLine 2\nLine 3\n"); + create_file("file2.txt", "File 2 content\nLine 2\nLine 3\n"); + + add::execute(AddArgs { + pathspec: vec![String::from(".")] + .iter() + .map(|s| s.to_string()) + .collect(), + all: false, + update: false, + verbose: false, + dry_run: false, + ignore_errors: false, + }) + .await; + + commit::execute(CommitArgs { + message: Some("Initial commit".to_string()), + allow_empty: false, + all: false, + amend: false, + signoff: false, + }) + .await + .unwrap(); + + // Modify both files + modify_file("file1.txt", "File 1 modified\nLine 2\nLine 3 changed\n"); + modify_file("file2.txt", "File 2 modified\nLine 2\nLine 3 changed\n"); + + // Run diff command with specific file path + diff::execute(DiffArgs { + old: None, + new: None, + staged: false, + pathspec: vec![String::from("file1.txt")], + algorithm: Some("histogram".to_string()), + output: None, + }) + .await; + + // The command should complete without panicking +} + +#[tokio::test] +#[serial] +/// Tests diff with output to a file +async fn test_diff_output_to_file() { + let test_dir = tempdir().unwrap(); + test::setup_with_new_libra_in(test_dir.path()).await; + let _guard = test::ChangeDirGuard::new(test_dir.path()); + + // Create a file and commit it + create_file("file1.txt", "Initial content\nLine 2\nLine 3\n"); + + add::execute(AddArgs { + pathspec: vec![String::from("file1.txt")], + all: false, + update: false, + verbose: false, + dry_run: false, + ignore_errors: false, + }) + .await; + + commit::execute(CommitArgs { + message: Some("Initial commit".to_string()), + allow_empty: false, + all: false, + amend: false, + signoff: false, + }) + .await + .unwrap(); + + // Modify the file + modify_file("file1.txt", "Modified content\nLine 2\nLine 3 changed\n"); + + // Output file path + let output_file = "diff_output.txt"; + + // Run diff command with output to file + diff::execute(DiffArgs { + old: None, + new: None, + staged: false, + pathspec: vec![], + algorithm: Some("histogram".to_string()), + output: Some(output_file.to_string()), + }) + .await; + + // Verify the output file exists + assert!(fs::metadata(output_file).is_ok(), "Output file should exist"); + + // Read the file content to make sure it contains diff output + let content = fs::read_to_string(output_file).unwrap(); + assert!(content.contains("diff --git"), "Output should contain diff header"); +} + +#[tokio::test] +#[serial] +/// Tests diff with different algorithms +async fn test_diff_algorithms() { + let test_dir = tempdir().unwrap(); + test::setup_with_new_libra_in(test_dir.path()).await; + let _guard = test::ChangeDirGuard::new(test_dir.path()); + + // Create a file with some content to make a non-trivial diff + create_file( + "file1.txt", + "Line 1\nLine 2\nLine 3\nLine 4\nLine 5\nLine 6\nLine 7\n", + ); + + add::execute(AddArgs { + pathspec: vec![String::from("file1.txt")], + all: false, + update: false, + verbose: false, + dry_run: false, + ignore_errors: false, + }) + .await; + + commit::execute(CommitArgs { + message: Some("Initial commit".to_string()), + allow_empty: false, + all: false, + amend: false, + signoff: false, + }) + .await + .unwrap(); + + // Make complex changes to test different algorithms + modify_file( + "file1.txt", + "Line 1\nModified Line\nLine 3\nNew Line\nLine 5\nLine 6\nDeleted Line 7\n", + ); + + // Test histogram algorithm + diff::execute(DiffArgs { + old: None, + new: None, + staged: false, + pathspec: vec![], + algorithm: Some("histogram".to_string()), + output: Some("histogram_diff.txt".to_string()), + }) + .await; + + // Test myers algorithm + diff::execute(DiffArgs { + old: None, + new: None, + staged: false, + pathspec: vec![], + algorithm: Some("myers".to_string()), + output: Some("myers_diff.txt".to_string()), + }) + .await; + + // Test myersMinimal algorithm + diff::execute(DiffArgs { + old: None, + new: None, + staged: false, + pathspec: vec![], + algorithm: Some("myersMinimal".to_string()), + output: Some("myersMinimal_diff.txt".to_string()), + }) + .await; + + // Verify all output files exist + assert!(fs::metadata("histogram_diff.txt").is_ok(), "Histogram output file should exist"); + assert!(fs::metadata("myers_diff.txt").is_ok(), "Myers output file should exist"); + assert!(fs::metadata("myersMinimal_diff.txt").is_ok(), "MyersMinimal output file should exist"); +} diff --git a/libra/tests/command/remove_test.rs b/libra/tests/command/remove_test.rs index 8b1378917..5bfe9c017 100644 --- a/libra/tests/command/remove_test.rs +++ b/libra/tests/command/remove_test.rs @@ -1 +1,320 @@ +use super::*; +use std::fs; +use std::io::Write; +use std::path::PathBuf; +use libra::command::remove::{self, RemoveArgs}; + +/// Helper function to create a file with content +fn create_file(path: &str, content: &str) -> PathBuf { + let path = PathBuf::from(path); + if let Some(parent) = path.parent() { + fs::create_dir_all(parent).unwrap(); + } + let mut file = fs::File::create(&path).unwrap(); + file.write_all(content.as_bytes()).unwrap(); + path +} + +#[tokio::test] +#[serial] +/// Tests the basic remove functionality by removing a single file +async fn test_remove_single_file() { + let test_dir = tempdir().unwrap(); + test::setup_with_new_libra_in(test_dir.path()).await; + let _guard = test::ChangeDirGuard::new(test_dir.path()); + + // Create a file and add it to index + let file_path = create_file("test_file.txt", "Test content"); + + add::execute(AddArgs { + pathspec: vec![String::from("test_file.txt")], + all: false, + update: false, + verbose: false, + dry_run: false, + ignore_errors: false, + }) + .await; + + // Make sure the file exists + assert!(file_path.exists(), "File should exist before removal"); + + // Remove the file + remove::execute(RemoveArgs { + pathspec: vec![String::from("test_file.txt")], + cached: false, + recursive: false, + }) + .unwrap(); + + // Verify the file was removed from the filesystem + assert!(!file_path.exists(), "File should be removed from filesystem"); + + // Verify file is no longer in the index + let changes = changes_to_be_staged(); + assert!(!changes.new.iter().any(|x| x.to_str().unwrap() == "test_file.txt"), + "File should not appear in changes as new"); + assert!(!changes.modified.iter().any(|x| x.to_str().unwrap() == "test_file.txt"), + "File should not appear in changes as modified"); + assert!(!changes.deleted.iter().any(|x| x.to_str().unwrap() == "test_file.txt"), + "File should not appear in changes as deleted"); +} + +#[tokio::test] +#[serial] +/// Tests removing a file with --cached flag, which only removes from the index but keeps the file +async fn test_remove_cached() { + let test_dir = tempdir().unwrap(); + test::setup_with_new_libra_in(test_dir.path()).await; + let _guard = test::ChangeDirGuard::new(test_dir.path()); + + // Create a file and add it to index + let file_path = create_file("test_file.txt", "Test content"); + + add::execute(AddArgs { + pathspec: vec![String::from("test_file.txt")], + all: false, + update: false, + verbose: false, + dry_run: false, + ignore_errors: false, + }) + .await; + + // Make sure the file exists + assert!(file_path.exists(), "File should exist before removal"); + + // Remove the file with --cached flag + remove::execute(RemoveArgs { + pathspec: vec![String::from("test_file.txt")], + cached: true, + recursive: false, + }) + .unwrap(); + + // Verify the file still exists in the filesystem + assert!(file_path.exists(), "File should still exist in filesystem"); + + // Verify file appears as new (untracked) in the index + let changes = changes_to_be_staged(); + assert!(changes.new.iter().any(|x| x.to_str().unwrap() == "test_file.txt"), + "File should appear in changes as new/untracked"); +} + +#[tokio::test] +#[serial] +/// Tests recursive removal of a directory +async fn test_remove_directory_recursive() { + let test_dir = tempdir().unwrap(); + test::setup_with_new_libra_in(test_dir.path()).await; + let _guard = test::ChangeDirGuard::new(test_dir.path()); + + // Create a directory with files + let file1 = create_file("test_dir/file1.txt", "File 1 content"); + let file2 = create_file("test_dir/file2.txt", "File 2 content"); + let file3 = create_file("test_dir/subdir/file3.txt", "File 3 content"); + + // Add all files to the index + add::execute(AddArgs { + pathspec: vec![String::from("test_dir")], + all: false, + update: false, + verbose: false, + dry_run: false, + ignore_errors: false, + }) + .await; + + // Make sure the directory and files exist + assert!(fs::metadata("test_dir").is_ok(), "Directory should exist"); + assert!(file1.exists(), "File 1 should exist"); + assert!(file2.exists(), "File 2 should exist"); + assert!(file3.exists(), "File 3 should exist"); + + // Remove the directory recursively + remove::execute(RemoveArgs { + pathspec: vec![String::from("test_dir")], + cached: false, + recursive: true, + }) + .unwrap(); + + // Verify the directory and files were removed + assert!(!fs::metadata("test_dir").is_ok(), "Directory should be removed"); + assert!(!file1.exists(), "File 1 should be removed"); + assert!(!file2.exists(), "File 2 should be removed"); + assert!(!file3.exists(), "File 3 should be removed"); + + // Verify files are no longer in the index + let changes = changes_to_be_staged(); + for file in &[file1, file2, file3] { + let file_str = file.to_str().unwrap(); + assert!(!changes.new.iter().any(|x| x.to_str().unwrap() == file_str), + "File should not appear in changes as new"); + assert!(!changes.modified.iter().any(|x| x.to_str().unwrap() == file_str), + "File should not appear in changes as modified"); + assert!(!changes.deleted.iter().any(|x| x.to_str().unwrap() == file_str), + "File should not appear in changes as deleted"); + } +} + +#[tokio::test] +#[serial] +/// Tests attempting to remove a directory without -r flag should fail +async fn test_remove_directory_without_recursive() { + let test_dir = tempdir().unwrap(); + test::setup_with_new_libra_in(test_dir.path()).await; + let _guard = test::ChangeDirGuard::new(test_dir.path()); + + // Create a directory with files + let file1 = create_file("test_dir/file1.txt", "File 1 content"); + let file2 = create_file("test_dir/file2.txt", "File 2 content"); + + // Add all files to the index + add::execute(AddArgs { + pathspec: vec![String::from("test_dir")], + all: false, + update: false, + verbose: false, + dry_run: false, + ignore_errors: false, + }) + .await; + + // Make sure the directory and files exist + assert!(fs::metadata("test_dir").is_ok(), "Directory should exist"); + assert!(file1.exists(), "File 1 should exist"); + assert!(file2.exists(), "File 2 should exist"); + + // Attempt to remove the directory without recursive flag + remove::execute(RemoveArgs { + pathspec: vec![String::from("test_dir")], + cached: false, + recursive: false, + }) + .unwrap(); // This should not error, but it should not remove anything either + + // Verify the directory and files still exist + assert!(fs::metadata("test_dir").is_ok(), "Directory should still exist"); + assert!(file1.exists(), "File 1 should still exist"); + assert!(file2.exists(), "File 2 should still exist"); +} + +#[tokio::test] +#[serial] +/// Tests removing a file that does not exist in the index +async fn test_remove_untracked_file() { + let test_dir = tempdir().unwrap(); + test::setup_with_new_libra_in(test_dir.path()).await; + let _guard = test::ChangeDirGuard::new(test_dir.path()); + + // Create a file but don't add it to the index + let file_path = create_file("untracked_file.txt", "Untracked content"); + + // Make sure the file exists + assert!(file_path.exists(), "File should exist"); + + // Attempt to remove the untracked file (should fail/do nothing) + remove::execute(RemoveArgs { + pathspec: vec![String::from("untracked_file.txt")], + cached: false, + recursive: false, + }) + .unwrap(); // Should not panic but should print error + + // Verify the file still exists + assert!(file_path.exists(), "File should still exist"); +} + +#[tokio::test] +#[serial] +/// Tests removing a file that has been modified after being added to the index +async fn test_remove_modified_file() { + let test_dir = tempdir().unwrap(); + test::setup_with_new_libra_in(test_dir.path()).await; + let _guard = test::ChangeDirGuard::new(test_dir.path()); + + // Create a file and add it to index + let file_path = create_file("test_file.txt", "Initial content"); + + add::execute(AddArgs { + pathspec: vec![String::from("test_file.txt")], + all: false, + update: false, + verbose: false, + dry_run: false, + ignore_errors: false, + }) + .await; + + // Modify the file + let mut file = fs::OpenOptions::new().write(true).open(&file_path).unwrap(); + file.write_all(b" - Modified").unwrap(); + + // Remove the file + remove::execute(RemoveArgs { + pathspec: vec![String::from("test_file.txt")], + cached: false, + recursive: false, + }) + .unwrap(); + + // Verify the file was removed + assert!(!file_path.exists(), "File should be removed"); + + // Verify file is not in the index + let changes = changes_to_be_staged(); + assert!(!changes.new.iter().any(|x| x.to_str().unwrap() == "test_file.txt"), + "File should not appear in changes as new"); + assert!(!changes.modified.iter().any(|x| x.to_str().unwrap() == "test_file.txt"), + "File should not appear in changes as modified"); + assert!(!changes.deleted.iter().any(|x| x.to_str().unwrap() == "test_file.txt"), + "File should not appear in changes as deleted"); +} + +#[tokio::test] +#[serial] +/// Tests removing multiple files at once +async fn test_remove_multiple_files() { + let test_dir = tempdir().unwrap(); + test::setup_with_new_libra_in(test_dir.path()).await; + let _guard = test::ChangeDirGuard::new(test_dir.path()); + + // Create multiple files + let file1 = create_file("file1.txt", "File 1 content"); + let file2 = create_file("file2.txt", "File 2 content"); + let file3 = create_file("file3.txt", "File 3 content"); + + // Add all files to the index + add::execute(AddArgs { + pathspec: vec![String::from(".")], + all: false, + update: false, + verbose: false, + dry_run: false, + ignore_errors: false, + }) + .await; + + // Make sure all files exist + assert!(file1.exists(), "File 1 should exist"); + assert!(file2.exists(), "File 2 should exist"); + assert!(file3.exists(), "File 3 should exist"); + + // Remove multiple files at once + remove::execute(RemoveArgs { + pathspec: vec![ + String::from("file1.txt"), + String::from("file3.txt"), + ], + cached: false, + recursive: false, + }) + .unwrap(); + + // Verify the specified files were removed + assert!(!file1.exists(), "File 1 should be removed"); + assert!(file2.exists(), "File 2 should still exist"); + assert!(!file3.exists(), "File 3 should be removed"); +} \ No newline at end of file From 8c5bf1135274e5acbfdd1a21bf89acb8ef319b31 Mon Sep 17 00:00:00 2001 From: Ming0213 <145410660+Ming0213@users.noreply.github.com> Date: Wed, 30 Jul 2025 12:17:49 +0800 Subject: [PATCH 2/6] Update libra/tests/command/remove_test.rs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Ming0213 <145410660+Ming0213@users.noreply.github.com> --- libra/tests/command/remove_test.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libra/tests/command/remove_test.rs b/libra/tests/command/remove_test.rs index 5bfe9c017..67302a3ab 100644 --- a/libra/tests/command/remove_test.rs +++ b/libra/tests/command/remove_test.rs @@ -249,7 +249,7 @@ async fn test_remove_modified_file() { .await; // Modify the file - let mut file = fs::OpenOptions::new().write(true).open(&file_path).unwrap(); + let mut file = fs::OpenOptions::new().write(true).truncate(true).open(&file_path).unwrap(); file.write_all(b" - Modified").unwrap(); // Remove the file From 9b95536026026daff6dc8609755197d210dba85c Mon Sep 17 00:00:00 2001 From: Ming0213 <145410660+Ming0213@users.noreply.github.com> Date: Wed, 30 Jul 2025 12:18:11 +0800 Subject: [PATCH 3/6] Update libra/tests/command/add_test.rs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Ming0213 <145410660+Ming0213@users.noreply.github.com> --- libra/tests/command/add_test.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libra/tests/command/add_test.rs b/libra/tests/command/add_test.rs index 3eb54f481..ec12b9e6f 100644 --- a/libra/tests/command/add_test.rs +++ b/libra/tests/command/add_test.rs @@ -136,7 +136,7 @@ async fn test_add_update_flag() { .await; // Modify both files - let mut file1 = fs::OpenOptions::new().write(true).open(tracked_file).unwrap(); + let mut file1 = fs::OpenOptions::new().write(true).truncate(true).open(tracked_file).unwrap(); file1.write_all(b" - Modified").unwrap(); let mut file2 = fs::OpenOptions::new().write(true).open(untracked_file).unwrap(); From 976bb4dc8204b95d35673a0a16575fe13af6e24b Mon Sep 17 00:00:00 2001 From: Ming0213 <145410660+Ming0213@users.noreply.github.com> Date: Wed, 30 Jul 2025 12:18:19 +0800 Subject: [PATCH 4/6] Update libra/tests/command/add_test.rs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Ming0213 <145410660+Ming0213@users.noreply.github.com> --- libra/tests/command/add_test.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libra/tests/command/add_test.rs b/libra/tests/command/add_test.rs index ec12b9e6f..31f490a47 100644 --- a/libra/tests/command/add_test.rs +++ b/libra/tests/command/add_test.rs @@ -139,7 +139,7 @@ async fn test_add_update_flag() { let mut file1 = fs::OpenOptions::new().write(true).truncate(true).open(tracked_file).unwrap(); file1.write_all(b" - Modified").unwrap(); - let mut file2 = fs::OpenOptions::new().write(true).open(untracked_file).unwrap(); + let mut file2 = fs::OpenOptions::new().write(true).truncate(true).open(untracked_file).unwrap(); file2.write_all(b" - Modified").unwrap(); // Execute add command with --update flag From b1b71ad39220aa2de4684e2de4c80c99dac859ee Mon Sep 17 00:00:00 2001 From: Ming0213 <145410660+Ming0213@users.noreply.github.com> Date: Wed, 30 Jul 2025 12:18:42 +0800 Subject: [PATCH 5/6] Update libra/tests/command/diff_test.rs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Ming0213 <145410660+Ming0213@users.noreply.github.com> --- libra/tests/command/diff_test.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/libra/tests/command/diff_test.rs b/libra/tests/command/diff_test.rs index 019abde83..175988616 100644 --- a/libra/tests/command/diff_test.rs +++ b/libra/tests/command/diff_test.rs @@ -215,10 +215,7 @@ async fn test_diff_with_pathspec() { create_file("file2.txt", "File 2 content\nLine 2\nLine 3\n"); add::execute(AddArgs { - pathspec: vec![String::from(".")] - .iter() - .map(|s| s.to_string()) - .collect(), + pathspec: vec![String::from(".")], all: false, update: false, verbose: false, From 9db7856ecdbf53f748faf21874ab25589995f276 Mon Sep 17 00:00:00 2001 From: WangHaiMing Date: Wed, 30 Jul 2025 17:35:48 +0800 Subject: [PATCH 6/6] =?UTF-8?q?bugFix:=E4=BF=AE=E5=A4=8Dadd=5Ftest.rs?= =?UTF-8?q?=E3=80=81diff=5Ftest.rs=E3=80=81remove=5Ftest.rs=E4=B8=89?= =?UTF-8?q?=E4=B8=AA=E6=B5=8B=E8=AF=95=E7=94=A8=E4=BE=8B=E7=9A=84Clippy?= =?UTF-8?q?=E6=A3=80=E6=9F=A5=E7=9A=84=E6=8A=A5=E9=94=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: WangHaiMing --- libra/tests/command/add_test.rs | 32 +++++++++------ libra/tests/command/diff_test.rs | 65 +++++++++++++++++------------- libra/tests/command/remove_test.rs | 6 +++ 3 files changed, 61 insertions(+), 42 deletions(-) diff --git a/libra/tests/command/add_test.rs b/libra/tests/command/add_test.rs index 31f490a47..b88c5fbad 100644 --- a/libra/tests/command/add_test.rs +++ b/libra/tests/command/add_test.rs @@ -21,6 +21,7 @@ async fn test_add_single_file() { pathspec: vec![String::from(file_path)], all: false, update: false, + refresh: false, verbose: false, dry_run: false, ignore_errors: false, @@ -30,7 +31,6 @@ async fn test_add_single_file() { // Verify the file was added to index let changes = changes_to_be_staged(); assert!(!changes.new.iter().any(|x| x.to_str().unwrap() == file_path)); - assert!(changes.staged.iter().any(|x| x.to_str().unwrap() == file_path)); } #[tokio::test] @@ -58,6 +58,7 @@ async fn test_add_multiple_files() { ], all: false, update: false, + refresh: false, verbose: false, dry_run: false, ignore_errors: false, @@ -66,9 +67,9 @@ async fn test_add_multiple_files() { // Verify all files were added to index let changes = changes_to_be_staged(); - assert!(changes.staged.iter().any(|x| x.to_str().unwrap() == "test_file_1.txt")); - assert!(changes.staged.iter().any(|x| x.to_str().unwrap() == "test_file_2.txt")); - assert!(changes.staged.iter().any(|x| x.to_str().unwrap() == "test_file_3.txt")); + assert!(!changes.new.iter().any(|x| x.to_str().unwrap() == "test_file_1.txt")); + assert!(!changes.new.iter().any(|x| x.to_str().unwrap() == "test_file_2.txt")); + assert!(!changes.new.iter().any(|x| x.to_str().unwrap() == "test_file_3.txt")); } #[tokio::test] @@ -92,6 +93,7 @@ async fn test_add_all_flag() { pathspec: vec![], all: true, update: false, + refresh: false, verbose: false, dry_run: false, ignore_errors: false, @@ -100,9 +102,9 @@ async fn test_add_all_flag() { // Verify all files were added to index let changes = changes_to_be_staged(); - assert!(changes.staged.iter().any(|x| x.to_str().unwrap() == "test_file_1.txt")); - assert!(changes.staged.iter().any(|x| x.to_str().unwrap() == "test_file_2.txt")); - assert!(changes.staged.iter().any(|x| x.to_str().unwrap() == "test_file_3.txt")); + assert!(!changes.new.iter().any(|x| x.to_str().unwrap() == "test_file_1.txt")); + assert!(!changes.new.iter().any(|x| x.to_str().unwrap() == "test_file_2.txt")); + assert!(!changes.new.iter().any(|x| x.to_str().unwrap() == "test_file_3.txt")); } #[tokio::test] @@ -129,6 +131,7 @@ async fn test_add_update_flag() { pathspec: vec![String::from(tracked_file)], all: false, update: false, + refresh: false, verbose: false, dry_run: false, ignore_errors: false, @@ -147,6 +150,7 @@ async fn test_add_update_flag() { pathspec: vec![String::from(".")], all: false, update: true, + refresh: false, verbose: false, dry_run: false, ignore_errors: false, @@ -155,8 +159,8 @@ async fn test_add_update_flag() { // Verify only tracked file was updated let changes = changes_to_be_staged(); - // Tracked file should appear in changes as modified (because it was updated) - assert!(changes.modified.iter().any(|x| x.to_str().unwrap() == tracked_file)); + // Tracked file should not appear in changes (because it was updated in index) + assert!(!changes.modified.iter().any(|x| x.to_str().unwrap() == tracked_file)); // Untracked file should still be untracked and show as new assert!(changes.new.iter().any(|x| x.to_str().unwrap() == untracked_file)); } @@ -196,6 +200,7 @@ async fn test_add_with_ignore_patterns() { pathspec: vec![String::from(".")], all: true, update: false, + refresh: false, verbose: false, dry_run: false, ignore_errors: false, @@ -204,10 +209,10 @@ async fn test_add_with_ignore_patterns() { // Verify only non-ignored files were added let changes = changes_to_be_staged(); - // Ignored files should not appear in changes.new - assert!(!changes.new.iter().any(|x| x.to_str().unwrap() == ignored_file)); - // Directory files should not appear in changes.new - assert!(!changes.new.iter().any(|x| x.to_str().unwrap() == ignored_dir_file)); + // Ignored files should still show as new (not added due to ignore) + assert!(changes.new.iter().any(|x| x.to_str().unwrap() == ignored_file)); + // Directory files should still show as new (not added due to ignore) + assert!(changes.new.iter().any(|x| x.to_str().unwrap() == ignored_dir_file)); // Non-ignored file should not show as new (was added) assert!(!changes.new.iter().any(|x| x.to_str().unwrap() == tracked_file)); } @@ -230,6 +235,7 @@ async fn test_add_dry_run() { pathspec: vec![String::from(file_path)], all: false, update: false, + refresh: false, verbose: false, dry_run: true, ignore_errors: false, diff --git a/libra/tests/command/diff_test.rs b/libra/tests/command/diff_test.rs index 175988616..6d4f7cef6 100644 --- a/libra/tests/command/diff_test.rs +++ b/libra/tests/command/diff_test.rs @@ -1,7 +1,6 @@ use super::*; use std::fs; use std::io::Write; -use std::path::PathBuf; use libra::command::diff::{self, DiffArgs}; @@ -32,6 +31,7 @@ async fn test_basic_diff() { pathspec: vec![String::from("file1.txt")], all: false, update: false, + refresh: false, verbose: false, dry_run: false, ignore_errors: false, @@ -40,14 +40,14 @@ async fn test_basic_diff() { // Create initial commit commit::execute(CommitArgs { - message: Some("Initial commit".to_string()), + message: "Initial commit".to_string(), allow_empty: false, - all: false, + conventional: false, amend: false, signoff: false, + disable_pre: false, }) - .await - .unwrap(); + .await; // Modify the file modify_file("file1.txt", "Modified content\nLine 2\nLine 3 changed\n"); @@ -81,6 +81,7 @@ async fn test_diff_staged() { pathspec: vec![String::from("file1.txt")], all: false, update: false, + refresh: false, verbose: false, dry_run: false, ignore_errors: false, @@ -89,14 +90,14 @@ async fn test_diff_staged() { // Create initial commit commit::execute(CommitArgs { - message: Some("Initial commit".to_string()), + message: "Initial commit".to_string(), allow_empty: false, - all: false, + conventional: false, amend: false, signoff: false, + disable_pre: false, }) - .await - .unwrap(); + .await; // Modify the file and stage it modify_file("file1.txt", "Modified content\nLine 2\nLine 3 changed\n"); @@ -105,6 +106,7 @@ async fn test_diff_staged() { pathspec: vec![String::from("file1.txt")], all: false, update: false, + refresh: false, verbose: false, dry_run: false, ignore_errors: false, @@ -143,6 +145,7 @@ async fn test_diff_between_commits() { pathspec: vec![String::from("file1.txt")], all: false, update: false, + refresh: false, verbose: false, dry_run: false, ignore_errors: false, @@ -150,14 +153,14 @@ async fn test_diff_between_commits() { .await; commit::execute(CommitArgs { - message: Some("Initial commit".to_string()), + message: "Initial commit".to_string(), allow_empty: false, - all: false, + conventional: false, amend: false, signoff: false, + disable_pre: false, }) - .await - .unwrap(); + .await; // Get the first commit hash let first_commit = Head::current_commit().await.unwrap(); @@ -169,6 +172,7 @@ async fn test_diff_between_commits() { pathspec: vec![String::from("file1.txt")], all: false, update: false, + refresh: false, verbose: false, dry_run: false, ignore_errors: false, @@ -176,14 +180,14 @@ async fn test_diff_between_commits() { .await; commit::execute(CommitArgs { - message: Some("Second commit".to_string()), + message: "Second commit".to_string(), allow_empty: false, - all: false, + conventional: false, amend: false, signoff: false, + disable_pre: false, }) - .await - .unwrap(); + .await; // Get the second commit hash let second_commit = Head::current_commit().await.unwrap(); @@ -218,6 +222,7 @@ async fn test_diff_with_pathspec() { pathspec: vec![String::from(".")], all: false, update: false, + refresh: false, verbose: false, dry_run: false, ignore_errors: false, @@ -225,14 +230,14 @@ async fn test_diff_with_pathspec() { .await; commit::execute(CommitArgs { - message: Some("Initial commit".to_string()), + message: "Initial commit".to_string(), allow_empty: false, - all: false, + conventional: false, amend: false, signoff: false, + disable_pre: false, }) - .await - .unwrap(); + .await; // Modify both files modify_file("file1.txt", "File 1 modified\nLine 2\nLine 3 changed\n"); @@ -267,6 +272,7 @@ async fn test_diff_output_to_file() { pathspec: vec![String::from("file1.txt")], all: false, update: false, + refresh: false, verbose: false, dry_run: false, ignore_errors: false, @@ -274,14 +280,14 @@ async fn test_diff_output_to_file() { .await; commit::execute(CommitArgs { - message: Some("Initial commit".to_string()), + message: "Initial commit".to_string(), allow_empty: false, - all: false, + conventional: false, amend: false, signoff: false, + disable_pre: false, }) - .await - .unwrap(); + .await; // Modify the file modify_file("file1.txt", "Modified content\nLine 2\nLine 3 changed\n"); @@ -326,6 +332,7 @@ async fn test_diff_algorithms() { pathspec: vec![String::from("file1.txt")], all: false, update: false, + refresh: false, verbose: false, dry_run: false, ignore_errors: false, @@ -333,14 +340,14 @@ async fn test_diff_algorithms() { .await; commit::execute(CommitArgs { - message: Some("Initial commit".to_string()), + message: "Initial commit".to_string(), allow_empty: false, - all: false, + conventional: false, amend: false, signoff: false, + disable_pre: false, }) - .await - .unwrap(); + .await; // Make complex changes to test different algorithms modify_file( diff --git a/libra/tests/command/remove_test.rs b/libra/tests/command/remove_test.rs index 67302a3ab..2bca0e6fd 100644 --- a/libra/tests/command/remove_test.rs +++ b/libra/tests/command/remove_test.rs @@ -31,6 +31,7 @@ async fn test_remove_single_file() { pathspec: vec![String::from("test_file.txt")], all: false, update: false, + refresh: false, verbose: false, dry_run: false, ignore_errors: false, @@ -76,6 +77,7 @@ async fn test_remove_cached() { pathspec: vec![String::from("test_file.txt")], all: false, update: false, + refresh: false, verbose: false, dry_run: false, ignore_errors: false, @@ -120,6 +122,7 @@ async fn test_remove_directory_recursive() { pathspec: vec![String::from("test_dir")], all: false, update: false, + refresh: false, verbose: false, dry_run: false, ignore_errors: false, @@ -176,6 +179,7 @@ async fn test_remove_directory_without_recursive() { pathspec: vec![String::from("test_dir")], all: false, update: false, + refresh: false, verbose: false, dry_run: false, ignore_errors: false, @@ -242,6 +246,7 @@ async fn test_remove_modified_file() { pathspec: vec![String::from("test_file.txt")], all: false, update: false, + refresh: false, verbose: false, dry_run: false, ignore_errors: false, @@ -291,6 +296,7 @@ async fn test_remove_multiple_files() { pathspec: vec![String::from(".")], all: false, update: false, + refresh: false, verbose: false, dry_run: false, ignore_errors: false,