use std::fs::OpenOptions;
use std::io::Write;
#[cfg(unix)]
use std::os::unix::fs::PermissionsExt;
#[cfg(unix)]
use std::os::unix::net::UnixListener;
use std::sync::Arc;
use itertools::Itertools;
use jujutsu_lib::backend::{Conflict, ConflictPart, TreeValue};
use jujutsu_lib::gitignore::GitIgnoreFile;
#[cfg(unix)]
use jujutsu_lib::op_store::OperationId;
use jujutsu_lib::op_store::WorkspaceId;
use jujutsu_lib::repo::{ReadonlyRepo, Repo};
use jujutsu_lib::repo_path::{RepoPath, RepoPathComponent, RepoPathJoin};
use jujutsu_lib::settings::UserSettings;
use jujutsu_lib::tree_builder::TreeBuilder;
use jujutsu_lib::working_copy::WorkingCopy;
use test_case::test_case;
use testutils::{write_random_commit, TestWorkspace};
#[test_case(false ; "local backend")]
#[test_case(true ; "git backend")]
fn test_root(use_git: bool) {
let settings = testutils::user_settings();
let mut test_workspace = TestWorkspace::init(&settings, use_git);
let repo = &test_workspace.repo;
let wc = test_workspace.workspace.working_copy_mut();
assert_eq!(wc.sparse_patterns(), vec![RepoPath::root()]);
let mut locked_wc = wc.start_mutation();
let new_tree_id = locked_wc.snapshot(GitIgnoreFile::empty()).unwrap();
locked_wc.discard();
let wc_commit_id = repo
.view()
.get_wc_commit_id(&WorkspaceId::default())
.unwrap();
let wc_commit = repo.store().get_commit(wc_commit_id).unwrap();
assert_eq!(&new_tree_id, wc_commit.tree_id());
assert_eq!(&new_tree_id, repo.store().empty_tree_id());
}
#[test_case(false ; "local backend")]
#[test_case(true ; "git backend")]
fn test_checkout_file_transitions(use_git: bool) {
let settings = testutils::user_settings();
let mut test_workspace = TestWorkspace::init(&settings, use_git);
let repo = &test_workspace.repo;
let store = repo.store().clone();
let workspace_root = test_workspace.workspace.workspace_root().clone();
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
enum Kind {
Missing,
Normal,
Executable,
ExecutableNormalContent,
Conflict,
#[cfg_attr(windows, allow(dead_code))]
Symlink,
Tree,
GitSubmodule,
}
fn write_path(
settings: &UserSettings,
repo: &Arc<ReadonlyRepo>,
tree_builder: &mut TreeBuilder,
kind: Kind,
path: &RepoPath,
) {
let store = repo.store();
let value = match kind {
Kind::Missing => {
return;
}
Kind::Normal => {
let id = testutils::write_file(store, path, "normal file contents");
TreeValue::File {
id,
executable: false,
}
}
Kind::Executable => {
let id = testutils::write_file(store, path, "executable file contents");
TreeValue::File {
id,
executable: true,
}
}
Kind::ExecutableNormalContent => {
let id = testutils::write_file(store, path, "normal file contents");
TreeValue::File {
id,
executable: true,
}
}
Kind::Conflict => {
let base_file_id = testutils::write_file(store, path, "base file contents");
let left_file_id = testutils::write_file(store, path, "left file contents");
let right_file_id = testutils::write_file(store, path, "right file contents");
let conflict = Conflict {
removes: vec![ConflictPart {
value: TreeValue::File {
id: base_file_id,
executable: false,
},
}],
adds: vec![
ConflictPart {
value: TreeValue::File {
id: left_file_id,
executable: false,
},
},
ConflictPart {
value: TreeValue::File {
id: right_file_id,
executable: false,
},
},
],
};
let conflict_id = store.write_conflict(path, &conflict).unwrap();
TreeValue::Conflict(conflict_id)
}
Kind::Symlink => {
let id = store.write_symlink(path, "target").unwrap();
TreeValue::Symlink(id)
}
Kind::Tree => {
let mut sub_tree_builder = store.tree_builder(store.empty_tree_id().clone());
let file_path = path.join(&RepoPathComponent::from("file"));
write_path(
settings,
repo,
&mut sub_tree_builder,
Kind::Normal,
&file_path,
);
let id = sub_tree_builder.write_tree();
TreeValue::Tree(id)
}
Kind::GitSubmodule => {
let mut tx = repo.start_transaction(settings, "test");
let id = write_random_commit(tx.mut_repo(), settings).id().clone();
tx.commit();
TreeValue::GitSubmodule(id)
}
};
tree_builder.set(path.clone(), value);
}
let mut kinds = vec![
Kind::Missing,
Kind::Normal,
Kind::Executable,
Kind::ExecutableNormalContent,
Kind::Conflict,
Kind::Tree,
];
#[cfg(unix)]
kinds.push(Kind::Symlink);
if use_git {
kinds.push(Kind::GitSubmodule);
}
let mut left_tree_builder = store.tree_builder(store.empty_tree_id().clone());
let mut right_tree_builder = store.tree_builder(store.empty_tree_id().clone());
let mut files = vec![];
for left_kind in &kinds {
for right_kind in &kinds {
let path = RepoPath::from_internal_string(&format!("{left_kind:?}_{right_kind:?}"));
write_path(&settings, repo, &mut left_tree_builder, *left_kind, &path);
write_path(&settings, repo, &mut right_tree_builder, *right_kind, &path);
files.push((*left_kind, *right_kind, path));
}
}
let left_tree_id = left_tree_builder.write_tree();
let right_tree_id = right_tree_builder.write_tree();
let left_tree = store.get_tree(&RepoPath::root(), &left_tree_id).unwrap();
let right_tree = store.get_tree(&RepoPath::root(), &right_tree_id).unwrap();
let wc = test_workspace.workspace.working_copy_mut();
wc.check_out(repo.op_id().clone(), None, &left_tree)
.unwrap();
wc.check_out(repo.op_id().clone(), None, &right_tree)
.unwrap();
let mut locked_wc = wc.start_mutation();
let new_tree_id = locked_wc.snapshot(GitIgnoreFile::empty()).unwrap();
locked_wc.discard();
assert_eq!(new_tree_id, right_tree_id);
for (_left_kind, right_kind, path) in &files {
let wc_path = workspace_root.join(path.to_internal_file_string());
let maybe_metadata = wc_path.symlink_metadata();
match right_kind {
Kind::Missing => {
assert!(maybe_metadata.is_err(), "{path:?} should not exist");
}
Kind::Normal => {
assert!(maybe_metadata.is_ok(), "{path:?} should exist");
let metadata = maybe_metadata.unwrap();
assert!(metadata.is_file(), "{path:?} should be a file");
#[cfg(unix)]
assert_eq!(
metadata.permissions().mode() & 0o111,
0,
"{path:?} should not be executable"
);
}
Kind::Executable | Kind::ExecutableNormalContent => {
assert!(maybe_metadata.is_ok(), "{path:?} should exist");
let metadata = maybe_metadata.unwrap();
assert!(metadata.is_file(), "{path:?} should be a file");
#[cfg(unix)]
assert_ne!(
metadata.permissions().mode() & 0o111,
0,
"{path:?} should be executable"
);
}
Kind::Conflict => {
assert!(maybe_metadata.is_ok(), "{path:?} should exist");
let metadata = maybe_metadata.unwrap();
assert!(metadata.is_file(), "{path:?} should be a file");
#[cfg(unix)]
assert_eq!(
metadata.permissions().mode() & 0o111,
0,
"{path:?} should not be executable"
);
}
Kind::Symlink => {
assert!(maybe_metadata.is_ok(), "{path:?} should exist");
let metadata = maybe_metadata.unwrap();
assert!(
metadata.file_type().is_symlink(),
"{path:?} should be a symlink"
);
}
Kind::Tree => {
assert!(maybe_metadata.is_ok(), "{path:?} should exist");
let metadata = maybe_metadata.unwrap();
assert!(metadata.is_dir(), "{path:?} should be a directory");
}
Kind::GitSubmodule => {
assert!(maybe_metadata.is_err(), "{path:?} should not exist");
}
};
}
}
#[test]
fn test_reset() {
let settings = testutils::user_settings();
let mut test_workspace = TestWorkspace::init(&settings, false);
let repo = &test_workspace.repo;
let workspace_root = test_workspace.workspace.workspace_root().clone();
let ignored_path = RepoPath::from_internal_string("ignored");
let gitignore_path = RepoPath::from_internal_string(".gitignore");
let tree_without_file = testutils::create_tree(repo, &[(&gitignore_path, "ignored\n")]);
let tree_with_file = testutils::create_tree(
repo,
&[(&gitignore_path, "ignored\n"), (&ignored_path, "code")],
);
let wc = test_workspace.workspace.working_copy_mut();
wc.check_out(repo.op_id().clone(), None, &tree_with_file)
.unwrap();
assert!(ignored_path.to_fs_path(&workspace_root).is_file());
assert!(wc.file_states().contains_key(&ignored_path));
let mut locked_wc = wc.start_mutation();
locked_wc.reset(&tree_without_file).unwrap();
locked_wc.finish(repo.op_id().clone());
assert!(ignored_path.to_fs_path(&workspace_root).is_file());
assert!(!wc.file_states().contains_key(&ignored_path));
let mut locked_wc = wc.start_mutation();
let new_tree_id = locked_wc.snapshot(GitIgnoreFile::empty()).unwrap();
assert_eq!(new_tree_id, *tree_without_file.id());
locked_wc.discard();
let mut locked_wc = wc.start_mutation();
locked_wc.reset(&tree_without_file).unwrap();
locked_wc.finish(repo.op_id().clone());
assert!(ignored_path.to_fs_path(&workspace_root).is_file());
assert!(!wc.file_states().contains_key(&ignored_path));
let mut locked_wc = wc.start_mutation();
let new_tree_id = locked_wc.snapshot(GitIgnoreFile::empty()).unwrap();
assert_eq!(new_tree_id, *tree_without_file.id());
locked_wc.discard();
let mut locked_wc = wc.start_mutation();
locked_wc.reset(&tree_with_file).unwrap();
locked_wc.finish(repo.op_id().clone());
assert!(ignored_path.to_fs_path(&workspace_root).is_file());
assert!(wc.file_states().contains_key(&ignored_path));
let mut locked_wc = wc.start_mutation();
let new_tree_id = locked_wc.snapshot(GitIgnoreFile::empty()).unwrap();
assert_eq!(new_tree_id, *tree_with_file.id());
locked_wc.discard();
}
#[test]
fn test_checkout_discard() {
let settings = testutils::user_settings();
let mut test_workspace = TestWorkspace::init(&settings, false);
let repo = test_workspace.repo.clone();
let workspace_root = test_workspace.workspace.workspace_root().clone();
let file1_path = RepoPath::from_internal_string("file1");
let file2_path = RepoPath::from_internal_string("file2");
let store = repo.store();
let tree1 = testutils::create_tree(&repo, &[(&file1_path, "contents")]);
let tree2 = testutils::create_tree(&repo, &[(&file2_path, "contents")]);
let wc = test_workspace.workspace.working_copy_mut();
let state_path = wc.state_path().to_path_buf();
wc.check_out(repo.op_id().clone(), None, &tree1).unwrap();
assert!(file1_path.to_fs_path(&workspace_root).is_file());
assert!(wc.file_states().contains_key(&file1_path));
let mut locked_wc = wc.start_mutation();
locked_wc.check_out(&tree2).unwrap();
assert!(!file1_path.to_fs_path(&workspace_root).is_file());
assert!(file2_path.to_fs_path(&workspace_root).is_file());
let reloaded_wc = WorkingCopy::load(store.clone(), workspace_root.clone(), state_path.clone());
assert!(reloaded_wc.file_states().contains_key(&file1_path));
assert!(!reloaded_wc.file_states().contains_key(&file2_path));
locked_wc.discard();
assert!(wc.file_states().contains_key(&file1_path));
assert!(!wc.file_states().contains_key(&file2_path));
assert!(!file1_path.to_fs_path(&workspace_root).is_file());
assert!(file2_path.to_fs_path(&workspace_root).is_file());
let reloaded_wc = WorkingCopy::load(store.clone(), workspace_root, state_path);
assert!(reloaded_wc.file_states().contains_key(&file1_path));
assert!(!reloaded_wc.file_states().contains_key(&file2_path));
}
#[test_case(false ; "local backend")]
#[test_case(true ; "git backend")]
fn test_snapshot_racy_timestamps(use_git: bool) {
let settings = testutils::user_settings();
let mut test_workspace = TestWorkspace::init(&settings, use_git);
let repo = &test_workspace.repo;
let workspace_root = test_workspace.workspace.workspace_root().clone();
let file_path = workspace_root.join("file");
let mut previous_tree_id = repo.store().empty_tree_id().clone();
let wc = test_workspace.workspace.working_copy_mut();
for i in 0..100 {
{
#[allow(clippy::needless_borrow)]
let mut file = OpenOptions::new()
.create(true)
.write(true)
.open(&file_path)
.unwrap();
file.write_all(format!("contents {i}").as_bytes()).unwrap();
}
let mut locked_wc = wc.start_mutation();
let new_tree_id = locked_wc.snapshot(GitIgnoreFile::empty()).unwrap();
locked_wc.discard();
assert_ne!(new_tree_id, previous_tree_id);
previous_tree_id = new_tree_id;
}
}
#[cfg(unix)]
#[test]
fn test_snapshot_special_file() {
let settings = testutils::user_settings();
let mut test_workspace = TestWorkspace::init(&settings, false);
let workspace_root = test_workspace.workspace.workspace_root().clone();
let store = test_workspace.repo.store();
let file1_path = RepoPath::from_internal_string("file1");
let file1_disk_path = file1_path.to_fs_path(&workspace_root);
std::fs::write(&file1_disk_path, "contents".as_bytes()).unwrap();
let file2_path = RepoPath::from_internal_string("file2");
let file2_disk_path = file2_path.to_fs_path(&workspace_root);
std::fs::write(file2_disk_path, "contents".as_bytes()).unwrap();
let socket_disk_path = workspace_root.join("socket");
UnixListener::bind(&socket_disk_path).unwrap();
assert!(socket_disk_path.exists());
assert!(!socket_disk_path.is_file());
let wc = test_workspace.workspace.working_copy_mut();
let mut locked_wc = wc.start_mutation();
let tree_id = locked_wc.snapshot(GitIgnoreFile::empty()).unwrap();
locked_wc.finish(OperationId::from_hex("abc123"));
let tree = store.get_tree(&RepoPath::root(), &tree_id).unwrap();
assert_eq!(
tree.entries().map(|(path, _value)| path).collect_vec(),
vec![file1_path.clone(), file2_path.clone()]
);
assert_eq!(
wc.file_states().keys().cloned().collect_vec(),
vec![file1_path, file2_path.clone()]
);
std::fs::remove_file(&file1_disk_path).unwrap();
UnixListener::bind(&file1_disk_path).unwrap();
let mut locked_wc = wc.start_mutation();
let tree_id = locked_wc.snapshot(GitIgnoreFile::empty()).unwrap();
locked_wc.finish(OperationId::from_hex("abc123"));
let tree = store.get_tree(&RepoPath::root(), &tree_id).unwrap();
assert_eq!(
tree.entries().map(|(path, _value)| path).collect_vec(),
vec![file2_path.clone()]
);
assert_eq!(
wc.file_states().keys().cloned().collect_vec(),
vec![file2_path]
);
}
#[test_case(false ; "local backend")]
#[test_case(true ; "git backend")]
fn test_gitignores(use_git: bool) {
let settings = testutils::user_settings();
let mut test_workspace = TestWorkspace::init(&settings, use_git);
let repo = &test_workspace.repo;
let workspace_root = test_workspace.workspace.workspace_root().clone();
let gitignore_path = RepoPath::from_internal_string(".gitignore");
let added_path = RepoPath::from_internal_string("added");
let modified_path = RepoPath::from_internal_string("modified");
let removed_path = RepoPath::from_internal_string("removed");
let ignored_path = RepoPath::from_internal_string("ignored");
let subdir_modified_path = RepoPath::from_internal_string("dir/modified");
let subdir_ignored_path = RepoPath::from_internal_string("dir/ignored");
testutils::write_working_copy_file(&workspace_root, &gitignore_path, "ignored\n");
testutils::write_working_copy_file(&workspace_root, &modified_path, "1");
testutils::write_working_copy_file(&workspace_root, &removed_path, "1");
std::fs::create_dir(workspace_root.join("dir")).unwrap();
testutils::write_working_copy_file(&workspace_root, &subdir_modified_path, "1");
let wc = test_workspace.workspace.working_copy_mut();
let mut locked_wc = wc.start_mutation();
let new_tree_id1 = locked_wc.snapshot(GitIgnoreFile::empty()).unwrap();
locked_wc.finish(repo.op_id().clone());
let tree1 = repo
.store()
.get_tree(&RepoPath::root(), &new_tree_id1)
.unwrap();
let files1 = tree1.entries().map(|(name, _value)| name).collect_vec();
assert_eq!(
files1,
vec![
gitignore_path.clone(),
subdir_modified_path.clone(),
modified_path.clone(),
removed_path.clone(),
]
);
testutils::write_working_copy_file(
&workspace_root,
&gitignore_path,
"ignored\nmodified\nremoved\n",
);
testutils::write_working_copy_file(&workspace_root, &added_path, "2");
testutils::write_working_copy_file(&workspace_root, &modified_path, "2");
std::fs::remove_file(removed_path.to_fs_path(&workspace_root)).unwrap();
testutils::write_working_copy_file(&workspace_root, &ignored_path, "2");
testutils::write_working_copy_file(&workspace_root, &subdir_modified_path, "2");
testutils::write_working_copy_file(&workspace_root, &subdir_ignored_path, "2");
let mut locked_wc = wc.start_mutation();
let new_tree_id2 = locked_wc.snapshot(GitIgnoreFile::empty()).unwrap();
locked_wc.discard();
let tree2 = repo
.store()
.get_tree(&RepoPath::root(), &new_tree_id2)
.unwrap();
let files2 = tree2.entries().map(|(name, _value)| name).collect_vec();
assert_eq!(
files2,
vec![
gitignore_path,
added_path,
subdir_modified_path,
modified_path,
]
);
}
#[test_case(false ; "local backend")]
#[test_case(true ; "git backend")]
fn test_gitignores_checkout_never_overwrites_ignored(use_git: bool) {
let settings = testutils::user_settings();
let mut test_workspace = TestWorkspace::init(&settings, use_git);
let repo = &test_workspace.repo;
let workspace_root = test_workspace.workspace.workspace_root().clone();
let gitignore_path = RepoPath::from_internal_string(".gitignore");
testutils::write_working_copy_file(&workspace_root, &gitignore_path, "modified\n");
let modified_path = RepoPath::from_internal_string("modified");
testutils::write_working_copy_file(&workspace_root, &modified_path, "garbage");
let mut tree_builder = repo
.store()
.tree_builder(repo.store().empty_tree_id().clone());
testutils::write_normal_file(&mut tree_builder, &modified_path, "contents");
let tree_id = tree_builder.write_tree();
let tree = repo.store().get_tree(&RepoPath::root(), &tree_id).unwrap();
let wc = test_workspace.workspace.working_copy_mut();
assert!(wc.check_out(repo.op_id().clone(), None, &tree).is_err());
let path = workspace_root.join("modified");
assert!(path.is_file());
assert_eq!(std::fs::read(&path).unwrap(), b"garbage");
}
#[test_case(false ; "local backend")]
#[test_case(true ; "git backend")]
fn test_gitignores_ignored_directory_already_tracked(use_git: bool) {
let settings = testutils::user_settings();
let mut test_workspace = TestWorkspace::init(&settings, use_git);
let repo = &test_workspace.repo;
let gitignore_path = RepoPath::from_internal_string(".gitignore");
testutils::write_working_copy_file(
test_workspace.workspace.workspace_root(),
&gitignore_path,
"/ignored/\n",
);
let file_path = RepoPath::from_internal_string("ignored/file");
let mut tree_builder = repo
.store()
.tree_builder(repo.store().empty_tree_id().clone());
testutils::write_normal_file(&mut tree_builder, &file_path, "contents");
let tree_id = tree_builder.write_tree();
let tree = repo.store().get_tree(&RepoPath::root(), &tree_id).unwrap();
let wc = test_workspace.workspace.working_copy_mut();
wc.check_out(repo.op_id().clone(), None, &tree).unwrap();
let mut locked_wc = wc.start_mutation();
let new_tree_id = locked_wc.snapshot(GitIgnoreFile::empty()).unwrap();
locked_wc.discard();
let new_tree = repo
.store()
.get_tree(&RepoPath::root(), &new_tree_id)
.unwrap();
assert!(new_tree.path_value(&file_path).is_some());
}
#[test_case(false ; "local backend")]
#[test_case(true ; "git backend")]
fn test_dotgit_ignored(use_git: bool) {
let settings = testutils::user_settings();
let mut test_workspace = TestWorkspace::init(&settings, use_git);
let repo = &test_workspace.repo;
let workspace_root = test_workspace.workspace.workspace_root().clone();
let dotgit_path = workspace_root.join(".git");
std::fs::create_dir(&dotgit_path).unwrap();
testutils::write_working_copy_file(
&workspace_root,
&RepoPath::from_internal_string(".git/file"),
"contents",
);
let mut locked_wc = test_workspace.workspace.working_copy_mut().start_mutation();
let new_tree_id = locked_wc.snapshot(GitIgnoreFile::empty()).unwrap();
assert_eq!(new_tree_id, *repo.store().empty_tree_id());
locked_wc.discard();
std::fs::remove_dir_all(&dotgit_path).unwrap();
testutils::write_working_copy_file(
&workspace_root,
&RepoPath::from_internal_string(".git"),
"contents",
);
let mut locked_wc = test_workspace.workspace.working_copy_mut().start_mutation();
let new_tree_id = locked_wc.snapshot(GitIgnoreFile::empty()).unwrap();
assert_eq!(new_tree_id, *repo.store().empty_tree_id());
locked_wc.discard();
}
#[test]
fn test_gitsubmodule() {
let settings = testutils::user_settings();
let mut test_workspace = TestWorkspace::init(&settings, true);
let repo = &test_workspace.repo;
let store = repo.store().clone();
let workspace_root = test_workspace.workspace.workspace_root().clone();
let mut tree_builder = store.tree_builder(store.empty_tree_id().clone());
let added_path = RepoPath::from_internal_string("added");
let submodule_path = RepoPath::from_internal_string("submodule");
let added_submodule_path = RepoPath::from_internal_string("submodule/added");
tree_builder.set(
added_path.clone(),
TreeValue::File {
id: testutils::write_file(repo.store(), &added_path, "added\n"),
executable: false,
},
);
let mut tx = repo.start_transaction(&settings, "create submodule commit");
let submodule_id = write_random_commit(tx.mut_repo(), &settings).id().clone();
tx.commit();
tree_builder.set(
submodule_path.clone(),
TreeValue::GitSubmodule(submodule_id),
);
let tree_id = tree_builder.write_tree();
let tree = store.get_tree(&RepoPath::root(), &tree_id).unwrap();
let wc = test_workspace.workspace.working_copy_mut();
wc.check_out(repo.op_id().clone(), None, &tree).unwrap();
std::fs::create_dir(submodule_path.to_fs_path(&workspace_root)).unwrap();
testutils::write_working_copy_file(
&workspace_root,
&added_submodule_path,
"i am a file in a submodule\n",
);
let mut locked_wc = wc.start_mutation();
let new_tree_id = locked_wc.snapshot(GitIgnoreFile::empty()).unwrap();
locked_wc.discard();
assert_eq!(new_tree_id, tree_id);
let file_in_submodule_path = added_submodule_path.to_fs_path(&workspace_root);
assert!(
file_in_submodule_path.metadata().is_ok(),
"{file_in_submodule_path:?} should exist"
);
}
#[cfg(unix)]
#[test_case(false ; "local backend")]
#[test_case(true ; "git backend")]
fn test_existing_directory_symlink(use_git: bool) {
let settings = testutils::user_settings();
let mut test_workspace = TestWorkspace::init(&settings, use_git);
let repo = &test_workspace.repo;
let workspace_root = test_workspace.workspace.workspace_root().clone();
std::os::unix::fs::symlink("..", workspace_root.join("parent")).unwrap();
let mut tree_builder = repo
.store()
.tree_builder(repo.store().empty_tree_id().clone());
testutils::write_normal_file(
&mut tree_builder,
&RepoPath::from_internal_string("parent/escaped"),
"contents",
);
let tree_id = tree_builder.write_tree();
let tree = repo.store().get_tree(&RepoPath::root(), &tree_id).unwrap();
let wc = test_workspace.workspace.working_copy_mut();
assert!(wc.check_out(repo.op_id().clone(), None, &tree).is_err());
assert!(!workspace_root.parent().unwrap().join("escaped").exists());
}