jj/cli/tests/test_concurrent_operations.rs
Evan Mesterhazy e1fd402d39 Fix the ContentHash implementations for std::Option, MergedTreeId, and RemoteRefState
The `ContentHash` documentation specifies that implementations for enums should
hash the ordinal number of the variant contained in the enum as a 32-bit
little-endian number and then hash the contents of the variant, if any.

The current implementations for `std::Option`, `MergedTreeId`, and
`RemoteRefState` are non-conformant since they hash the ordinal number as a u8
with platform specific endianness.


Fixes #3051
2024-02-16 09:27:32 -05:00

244 lines
10 KiB
Rust

// Copyright 2022 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::path::Path;
use itertools::Itertools as _;
use crate::common::TestEnvironment;
#[test]
fn test_concurrent_operation_divergence() {
let test_env = TestEnvironment::default();
test_env.jj_cmd_ok(test_env.env_root(), &["init", "repo", "--git"]);
let repo_path = test_env.env_root().join("repo");
test_env.jj_cmd_ok(&repo_path, &["describe", "-m", "message 1"]);
test_env.jj_cmd_ok(
&repo_path,
&["describe", "-m", "message 2", "--at-op", "@-"],
);
// "op log" doesn't merge the concurrent operations
let stdout = test_env.jj_cmd_success(&repo_path, &["op", "log"]);
insta::assert_snapshot!(stdout, @r###"
◉ 1e346ac76e7c test-username@host.example.com 2001-02-03 04:05:09.000 +07:00 - 2001-02-03 04:05:09.000 +07:00
│ describe commit 230dd059e1b059aefc0da06a2e5a7dbf22362f22
│ args: jj describe -m 'message 2' --at-op @-
│ ◉ 1fb59888922c test-username@host.example.com 2001-02-03 04:05:08.000 +07:00 - 2001-02-03 04:05:08.000 +07:00
├─╯ describe commit 230dd059e1b059aefc0da06a2e5a7dbf22362f22
│ args: jj describe -m 'message 1'
◉ 6ac4339ad699 test-username@host.example.com 2001-02-03 04:05:07.000 +07:00 - 2001-02-03 04:05:07.000 +07:00
│ add workspace 'default'
◉ 1b0049c19762 test-username@host.example.com 2001-02-03 04:05:07.000 +07:00 - 2001-02-03 04:05:07.000 +07:00
│ initialize repo
◉ 000000000000 root()
"###);
// We should be informed about the concurrent modification
let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["log", "-T", "description"]);
insta::assert_snapshot!(stdout, @r###"
◉ message 2
│ @ message 1
├─╯
"###);
insta::assert_snapshot!(stderr, @r###"
Concurrent modification detected, resolving automatically.
"###);
}
#[test]
fn test_concurrent_operations_auto_rebase() {
let test_env = TestEnvironment::default();
test_env.jj_cmd_ok(test_env.env_root(), &["init", "repo", "--git"]);
let repo_path = test_env.env_root().join("repo");
std::fs::write(repo_path.join("file"), "contents").unwrap();
test_env.jj_cmd_ok(&repo_path, &["describe", "-m", "initial"]);
let stdout = test_env.jj_cmd_success(&repo_path, &["op", "log"]);
insta::assert_snapshot!(stdout, @r###"
@ d5b4f16ef469 test-username@host.example.com 2001-02-03 04:05:08.000 +07:00 - 2001-02-03 04:05:08.000 +07:00
│ describe commit 123ed18e4c4c0d77428df41112bc02ffc83fb935
│ args: jj describe -m initial
◉ e632e64d7fa1 test-username@host.example.com 2001-02-03 04:05:08.000 +07:00 - 2001-02-03 04:05:08.000 +07:00
│ snapshot working copy
│ args: jj describe -m initial
◉ 6ac4339ad699 test-username@host.example.com 2001-02-03 04:05:07.000 +07:00 - 2001-02-03 04:05:07.000 +07:00
│ add workspace 'default'
◉ 1b0049c19762 test-username@host.example.com 2001-02-03 04:05:07.000 +07:00 - 2001-02-03 04:05:07.000 +07:00
│ initialize repo
◉ 000000000000 root()
"###);
let op_id_hex = stdout[3..15].to_string();
test_env.jj_cmd_ok(&repo_path, &["describe", "-m", "rewritten"]);
test_env.jj_cmd_ok(
&repo_path,
&["new", "--at-op", &op_id_hex, "-m", "new child"],
);
// We should be informed about the concurrent modification
let (stdout, stderr) = get_log_output_with_stderr(&test_env, &repo_path);
insta::assert_snapshot!(stdout, @r###"
◉ 3f06323826b4a293a9ee6d24cc0e07ad2961b5d5 new child
@ d91437157468ec86bbbc9e6a14a60d3e8d1790ac rewritten
◉ 0000000000000000000000000000000000000000
"###);
insta::assert_snapshot!(stderr, @r###"
Concurrent modification detected, resolving automatically.
Rebased 1 descendant commits onto commits rewritten by other operation
"###);
}
#[test]
fn test_concurrent_operations_wc_modified() {
let test_env = TestEnvironment::default();
test_env.jj_cmd_ok(test_env.env_root(), &["init", "repo", "--git"]);
let repo_path = test_env.env_root().join("repo");
std::fs::write(repo_path.join("file"), "contents\n").unwrap();
test_env.jj_cmd_ok(&repo_path, &["describe", "-m", "initial"]);
let stdout = test_env.jj_cmd_success(&repo_path, &["op", "log"]);
let op_id_hex = stdout[3..15].to_string();
test_env.jj_cmd_ok(
&repo_path,
&["new", "--at-op", &op_id_hex, "-m", "new child1"],
);
test_env.jj_cmd_ok(
&repo_path,
&["new", "--at-op", &op_id_hex, "-m", "new child2"],
);
std::fs::write(repo_path.join("file"), "modified\n").unwrap();
// We should be informed about the concurrent modification
let (stdout, stderr) = get_log_output_with_stderr(&test_env, &repo_path);
insta::assert_snapshot!(stdout, @r###"
@ 4eb0610031b7cd148ff9f729a673a3f815033170 new child1
│ ◉ 4b20e61d23ee7d7c4d5e61e11e97c26e716f9c30 new child2
├─╯
◉ 52c893bf3cd201e215b23e084e8a871244ca14d5 initial
◉ 0000000000000000000000000000000000000000
"###);
insta::assert_snapshot!(stderr, @r###"
Concurrent modification detected, resolving automatically.
"###);
let stdout = test_env.jj_cmd_success(&repo_path, &["diff", "--git"]);
insta::assert_snapshot!(stdout, @r###"
diff --git a/file b/file
index 12f00e90b6...2e0996000b 100644
--- a/file
+++ b/file
@@ -1,1 +1,1 @@
-contents
+modified
"###);
// The working copy should be committed after merging the operations
let stdout = test_env.jj_cmd_success(&repo_path, &["op", "log", "-Tdescription"]);
insta::assert_snapshot!(stdout, @r###"
@ snapshot working copy
◉ resolve concurrent operations
├─╮
◉ │ new empty commit
│ ◉ new empty commit
├─╯
◉ describe commit cf911c223d3e24e001fc8264d6dbf0610804fc40
◉ snapshot working copy
◉ add workspace 'default'
◉ initialize repo
"###);
}
#[test]
fn test_concurrent_snapshot_wc_reloadable() {
let test_env = TestEnvironment::default();
test_env.jj_cmd_ok(test_env.env_root(), &["init", "repo", "--git"]);
let repo_path = test_env.env_root().join("repo");
let op_heads_dir = repo_path
.join(".jj")
.join("repo")
.join("op_heads")
.join("heads");
std::fs::write(repo_path.join("base"), "").unwrap();
test_env.jj_cmd_ok(&repo_path, &["commit", "-m", "initial"]);
// Create new commit and checkout it.
std::fs::write(repo_path.join("child1"), "").unwrap();
test_env.jj_cmd_ok(&repo_path, &["commit", "-m", "new child1"]);
let template = r#"id ++ "\n" ++ description ++ "\n" ++ tags"#;
let op_log_stdout = test_env.jj_cmd_success(&repo_path, &["op", "log", "-T", template]);
insta::assert_snapshot!(op_log_stdout, @r###"
@ 1578600dd63556a22abef7cf6e7054a7e07468187ba31f79d0aa6a197b17004b7cd3e19d2fab1e6a00f2520b48d41969dbbb562c60d4c4af9436224f7f14ab83
│ commit 323b414dd255b51375d7f4392b7b2641ffe4289f
│ args: jj commit -m 'new child1'
◉ 90bb10893e980b606939a1f45f2aadf7de1eef65589ac5cd70e20dc20dfd0073c989b5ba0de70ce79a52d27aab5f5699eba66649b531530be5d13bc12c6bd926
│ snapshot working copy
│ args: jj commit -m 'new child1'
◉ 6104865e95226d46d8c6f5bf43ab025e67f88da6e27f8d8cc598c6d058e333126380c4cb25ea49c841480efee82ce2c602d87b4d3f53b85b4e704af5e83cbdc9
│ commit 3d918700494a9895696e955b85fa05eb0d314cc6
│ args: jj commit -m initial
◉ 76137fc212ef44c53db04be2010ba0419db1fe30e31289bed7d1d0410bee7c3c93d8fd5f6d1b03d93801a2517c436cc1bc4cc512c740e2d88979e771a6fb3730
│ snapshot working copy
│ args: jj commit -m initial
◉ 6ac4339ad6999058dd1806653ec37fc0091c1cc17419c750fddc5e8c1a6a77829e6dd70b3408403fb2c0b9839cf6bfd1c270f980674f7f89d4d78dc54082a8ef
│ add workspace 'default'
◉ 1b0049c19762e43499f2499a45afc9f72b3004d75a2863d41d8867cfafb9bbc8e16aa447107e460d58a5c1462429f032d806f7487836c66c6f351df45746c218
│ initialize repo
◉ 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
"###);
let op_log_lines = op_log_stdout.lines().collect_vec();
let current_op_id = op_log_lines[0].split_once(" ").unwrap().1;
let previous_op_id = op_log_lines[6].split_once(" ").unwrap().1;
// Another process started from the "initial" operation, but snapshots after
// the "child1" checkout has been completed.
std::fs::rename(
op_heads_dir.join(current_op_id),
op_heads_dir.join(previous_op_id),
)
.unwrap();
std::fs::write(repo_path.join("child2"), "").unwrap();
let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["describe", "-m", "new child2"]);
insta::assert_snapshot!(stdout, @"");
insta::assert_snapshot!(stderr, @r###"
Working copy now at: kkmpptxz 4011424e new child2
Parent commit : rlvkpnrz e08863ee new child1
"###);
// Since the repo can be reloaded before snapshotting, "child2" should be
// a child of "child1", not of "initial".
let template = r#"commit_id ++ " " ++ description"#;
let stdout = test_env.jj_cmd_success(&repo_path, &["log", "-T", template, "-s"]);
insta::assert_snapshot!(stdout, @r###"
@ 4011424ea0a210a914f869ea3c47d76931598d1d new child2
│ A child2
◉ e08863ee7a0df688755d3d3126498afdf4f580ad new child1
│ A child1
◉ 79989e62f8331e69a803058b57bacc264405cb65 initial
│ A base
◉ 0000000000000000000000000000000000000000
"###);
}
fn get_log_output_with_stderr(test_env: &TestEnvironment, cwd: &Path) -> (String, String) {
let template = r#"commit_id ++ " " ++ description"#;
test_env.jj_cmd_ok(cwd, &["log", "-T", template])
}