diff --git a/lib/tests/runner.rs b/lib/tests/runner.rs index eb75298cd3..f56376142a 100644 --- a/lib/tests/runner.rs +++ b/lib/tests/runner.rs @@ -6,6 +6,7 @@ fn test_no_forgotten_test_files() { testutils::assert_no_forgotten_test_files(&test_dir); } +mod test_annotate; mod test_bad_locking; mod test_commit_builder; mod test_commit_concurrent; diff --git a/lib/tests/test_annotate.rs b/lib/tests/test_annotate.rs new file mode 100644 index 0000000000..7e8f637285 --- /dev/null +++ b/lib/tests/test_annotate.rs @@ -0,0 +1,248 @@ +// Copyright 2024 The Jujutsu Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::fmt::Write as _; + +use jj_lib::annotate::get_annotation_for_file; +use jj_lib::backend::CommitId; +use jj_lib::backend::MergedTreeId; +use jj_lib::backend::MillisSinceEpoch; +use jj_lib::backend::Signature; +use jj_lib::backend::Timestamp; +use jj_lib::commit::Commit; +use jj_lib::repo::MutableRepo; +use jj_lib::repo::Repo; +use jj_lib::repo_path::RepoPath; +use jj_lib::settings::UserSettings; +use testutils::create_tree; +use testutils::TestRepo; + +fn create_commit_fn<'a>( + mut_repo: &'a mut MutableRepo, + settings: &'a UserSettings, +) -> impl FnMut(&str, &[&CommitId], MergedTreeId) -> Commit + 'a { + // stabilize commit IDs for ease of debugging + let signature = Signature { + name: "Some One".to_owned(), + email: "some.one@example.com".to_owned(), + timestamp: Timestamp { + timestamp: MillisSinceEpoch(0), + tz_offset: 0, + }, + }; + move |description, parent_ids, tree_id| { + let parent_ids = parent_ids.iter().map(|&id| id.clone()).collect(); + mut_repo + .new_commit(settings, parent_ids, tree_id) + .set_author(signature.clone()) + .set_committer(signature.clone()) + .set_description(description) + .write() + .unwrap() + } +} + +fn annotate(repo: &dyn Repo, commit: &Commit, file_path: &RepoPath) -> String { + let annotation = get_annotation_for_file(repo, commit, file_path).unwrap(); + let mut output = String::new(); + for (commit_id, line) in &annotation.file_annotations { + let commit = repo.store().get_commit(commit_id).unwrap(); + let desc = commit.description().trim_end(); + write!(output, "{desc}: {line}").unwrap(); + } + output +} + +#[test] +fn test_annotate_linear() { + let settings = testutils::user_settings(); + let test_repo = TestRepo::init(); + let repo = &test_repo.repo; + + let root_commit_id = repo.store().root_commit_id(); + let file_path = RepoPath::from_internal_string("file"); + + let mut tx = repo.start_transaction(&settings); + let mut create_commit = create_commit_fn(tx.repo_mut(), &settings); + let content1 = ""; + let content2 = "2a\n2b\n"; + let content3 = "2b\n3\n"; + let tree1 = create_tree(repo, &[(file_path, content1)]); + let tree2 = create_tree(repo, &[(file_path, content2)]); + let tree3 = create_tree(repo, &[(file_path, content3)]); + let commit1 = create_commit("commit1", &[root_commit_id], tree1.id()); + let commit2 = create_commit("commit2", &[commit1.id()], tree2.id()); + let commit3 = create_commit("commit3", &[commit2.id()], tree3.id()); + let commit4 = create_commit("commit4", &[commit3.id()], tree3.id()); // empty commit + drop(create_commit); + + insta::assert_snapshot!(annotate(tx.repo(), &commit1, file_path), @""); + insta::assert_snapshot!(annotate(tx.repo(), &commit2, file_path), @r#" + commit2: 2a + commit2: 2b + "#); + insta::assert_snapshot!(annotate(tx.repo(), &commit3, file_path), @r#" + commit2: 2b + commit3: 3 + "#); + insta::assert_snapshot!(annotate(tx.repo(), &commit4, file_path), @r#" + commit2: 2b + commit3: 3 + "#); +} + +#[test] +fn test_annotate_merge_simple() { + let settings = testutils::user_settings(); + let test_repo = TestRepo::init(); + let repo = &test_repo.repo; + + let root_commit_id = repo.store().root_commit_id(); + let file_path = RepoPath::from_internal_string("file"); + + // 4 "2 1 3" + // |\ + // | 3 "1 3" + // | | + // 2 | "2 1" + // |/ + // 1 "1" + let mut tx = repo.start_transaction(&settings); + let mut create_commit = create_commit_fn(tx.repo_mut(), &settings); + let content1 = "1\n"; + let content2 = "2\n1\n"; + let content3 = "1\n3\n"; + let content4 = "2\n1\n3\n"; + let tree1 = create_tree(repo, &[(file_path, content1)]); + let tree2 = create_tree(repo, &[(file_path, content2)]); + let tree3 = create_tree(repo, &[(file_path, content3)]); + let tree4 = create_tree(repo, &[(file_path, content4)]); + let commit1 = create_commit("commit1", &[root_commit_id], tree1.id()); + let commit2 = create_commit("commit2", &[commit1.id()], tree2.id()); + let commit3 = create_commit("commit3", &[commit1.id()], tree3.id()); + let commit4 = create_commit("commit4", &[commit2.id(), commit3.id()], tree4.id()); + drop(create_commit); + + insta::assert_snapshot!(annotate(tx.repo(), &commit4, file_path), @r#" + commit2: 2 + commit1: 1 + commit3: 3 + "#); +} + +#[test] +fn test_annotate_merge_split() { + let settings = testutils::user_settings(); + let test_repo = TestRepo::init(); + let repo = &test_repo.repo; + + let root_commit_id = repo.store().root_commit_id(); + let file_path = RepoPath::from_internal_string("file"); + + // 4 "2 1a 1b 3 4" + // |\ + // | 3 "1b 3" + // | | + // 2 | "2 1a" + // |/ + // 1 "1a 1b" + let mut tx = repo.start_transaction(&settings); + let mut create_commit = create_commit_fn(tx.repo_mut(), &settings); + let content1 = "1a\n1b\n"; + let content2 = "2\n1a\n"; + let content3 = "1b\n3\n"; + let content4 = "2\n1a\n1b\n3\n4\n"; + let tree1 = create_tree(repo, &[(file_path, content1)]); + let tree2 = create_tree(repo, &[(file_path, content2)]); + let tree3 = create_tree(repo, &[(file_path, content3)]); + let tree4 = create_tree(repo, &[(file_path, content4)]); + let commit1 = create_commit("commit1", &[root_commit_id], tree1.id()); + let commit2 = create_commit("commit2", &[commit1.id()], tree2.id()); + let commit3 = create_commit("commit3", &[commit1.id()], tree3.id()); + let commit4 = create_commit("commit4", &[commit2.id(), commit3.id()], tree4.id()); + drop(create_commit); + + insta::assert_snapshot!(annotate(tx.repo(), &commit4, file_path), @r#" + commit2: 2 + commit1: 1a + commit1: 1b + commit3: 3 + commit4: 4 + "#); +} + +#[test] +#[should_panic] // FIXME: shouldn't panic because of duplicated "1"s +fn test_annotate_merge_dup() { + let settings = testutils::user_settings(); + let test_repo = TestRepo::init(); + let repo = &test_repo.repo; + + let root_commit_id = repo.store().root_commit_id(); + let file_path = RepoPath::from_internal_string("file"); + + // 4 "2 1 1 3 4" + // |\ + // | 3 "1 3" + // | | + // 2 | "2 1" + // |/ + // 1 "1" + let mut tx = repo.start_transaction(&settings); + let mut create_commit = create_commit_fn(tx.repo_mut(), &settings); + let content1 = "1\n"; + let content2 = "2\n1\n"; + let content3 = "1\n3\n"; + let content4 = "2\n1\n1\n3\n4\n"; + let tree1 = create_tree(repo, &[(file_path, content1)]); + let tree2 = create_tree(repo, &[(file_path, content2)]); + let tree3 = create_tree(repo, &[(file_path, content3)]); + let tree4 = create_tree(repo, &[(file_path, content4)]); + let commit1 = create_commit("commit1", &[root_commit_id], tree1.id()); + let commit2 = create_commit("commit2", &[commit1.id()], tree2.id()); + let commit3 = create_commit("commit3", &[commit1.id()], tree3.id()); + let commit4 = create_commit("commit4", &[commit2.id(), commit3.id()], tree4.id()); + drop(create_commit); + + insta::assert_snapshot!(annotate(tx.repo(), &commit4, file_path), @r#" + commit2: 2 + commit1: 1 + commit1: 1 + commit3: 3 + commit4: 4 + "#); +} + +#[test] +fn test_annotate_file_directory_transition() { + let settings = testutils::user_settings(); + let test_repo = TestRepo::init(); + let repo = &test_repo.repo; + + let root_commit_id = repo.store().root_commit_id(); + let file_path1 = RepoPath::from_internal_string("file/was_dir"); + let file_path2 = RepoPath::from_internal_string("file"); + + let mut tx = repo.start_transaction(&settings); + let mut create_commit = create_commit_fn(tx.repo_mut(), &settings); + let tree1 = create_tree(repo, &[(file_path1, "1\n")]); + let tree2 = create_tree(repo, &[(file_path2, "2\n")]); + let commit1 = create_commit("commit1", &[root_commit_id], tree1.id()); + let commit2 = create_commit("commit2", &[commit1.id()], tree2.id()); + drop(create_commit); + + insta::assert_snapshot!(annotate(tx.repo(), &commit2, file_path2), @r#" + commit2: 2 + "#); +}