From 8d1cb1e1d7c7e6d9189a5b0c80fdc6658f0f7202 Mon Sep 17 00:00:00 2001 From: Martin von Zweigbergk Date: Mon, 24 Jul 2023 09:57:46 -0700 Subject: [PATCH] working_copy: add test of racy checkout followed by file write We don't seem to have any tests that our protection from undetected changes caused by writes happening right after checkout, so let's add one. The test case loops 100 times and each iteration fails slightly more than 80% of the time on my machine (if I remove the protection in `TreeState::update_file_state()`), so it seems quite good at triggering the race. --- lib/tests/test_working_copy_concurrent.rs | 44 ++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/lib/tests/test_working_copy_concurrent.rs b/lib/tests/test_working_copy_concurrent.rs index c10f555ff..bfb64d42b 100644 --- a/lib/tests/test_working_copy_concurrent.rs +++ b/lib/tests/test_working_copy_concurrent.rs @@ -16,11 +16,13 @@ use std::cmp::max; use std::thread; use assert_matches::assert_matches; +use jj_lib::backend::TreeId; +use jj_lib::op_store::OperationId; use jj_lib::repo::{Repo, StoreFactories}; use jj_lib::repo_path::RepoPath; use jj_lib::working_copy::{CheckoutError, SnapshotOptions}; use jj_lib::workspace::Workspace; -use testutils::TestWorkspace; +use testutils::{write_working_copy_file, TestWorkspace}; #[test] fn test_concurrent_checkout() { @@ -143,3 +145,43 @@ fn test_checkout_parallel() { } }); } + +#[test] +fn test_racy_checkout() { + let settings = testutils::user_settings(); + let mut test_workspace = TestWorkspace::init(&settings, true); + let repo = &test_workspace.repo; + let workspace_root = test_workspace.workspace.workspace_root().clone(); + + let path = RepoPath::from_internal_string("file"); + let tree = testutils::create_tree(repo, &[(&path, "1")]); + + fn snapshot(workspace: &mut Workspace) -> TreeId { + let mut locked_wc = workspace.working_copy_mut().start_mutation().unwrap(); + let tree_id = locked_wc + .snapshot(SnapshotOptions::empty_for_test()) + .unwrap(); + locked_wc.finish(OperationId::from_hex("abc123")).unwrap(); + tree_id + } + + let mut num_matches = 0; + for _ in 0..100 { + let wc = test_workspace.workspace.working_copy_mut(); + wc.check_out(repo.op_id().clone(), None, &tree).unwrap(); + assert_eq!( + std::fs::read(path.to_fs_path(&workspace_root)).unwrap(), + b"1".to_vec() + ); + // A file written right after checkout (hopefully, from the test's perspective, + // within the file system timestamp granularity) is detected as changed. + write_working_copy_file(&workspace_root, &path, "x"); + let modified_tree_id = snapshot(&mut test_workspace.workspace); + if modified_tree_id == *tree.id() { + num_matches += 1; + } + // Reset the state for the next round + write_working_copy_file(&workspace_root, &path, "1"); + } + assert_eq!(num_matches, 0); +}