578 lines
21 KiB
Rust
578 lines
21 KiB
Rust
fn working_file_changes(
|
|
root: &Path,
|
|
buffers: &[(PathBuf, Arc<str>)],
|
|
cancelled: &dyn Fn() -> bool,
|
|
) -> Result<Vec<WorkingFileChange>, String> {
|
|
WorkingFileReader::default().read(root, buffers, cancelled)
|
|
}
|
|
|
|
use super::*;
|
|
|
|
fn fixture_command(root: &Path, program: &str, args: &[&str]) -> String {
|
|
let mut command = Command::new(program);
|
|
if program == "jj" {
|
|
command.args([
|
|
"--config=user.name=\"Picker Author\"",
|
|
"--config=user.email=\"picker@example.test\"",
|
|
"--config=debug.commit-timestamp=\"2001-01-01T00:00:00Z\"",
|
|
"--config=debug.operation-timestamp=\"2001-01-01T00:00:00Z\"",
|
|
]);
|
|
}
|
|
let output = command
|
|
.args(args)
|
|
.current_dir(root)
|
|
.env("GIT_CONFIG_GLOBAL", "/dev/null")
|
|
.env("GIT_CONFIG_SYSTEM", "/dev/null")
|
|
.env("GIT_AUTHOR_DATE", "2001-01-01T00:00:00Z")
|
|
.env("GIT_COMMITTER_DATE", "2001-01-01T00:00:00Z")
|
|
.env("JJ_CONFIG", "/dev/null")
|
|
.output()
|
|
.unwrap();
|
|
assert!(
|
|
output.status.success(),
|
|
"{}",
|
|
String::from_utf8_lossy(&output.stderr)
|
|
);
|
|
String::from_utf8(output.stdout).unwrap().trim().into()
|
|
}
|
|
|
|
fn picker_git_fixture() -> Temporary {
|
|
let temp = Temporary::new().unwrap();
|
|
fixture_command(&temp.0, "git", &["init", "--quiet", "--template="]);
|
|
fixture_command(&temp.0, "git", &["config", "user.name", "Picker Author"]);
|
|
fixture_command(
|
|
&temp.0,
|
|
"git",
|
|
&["config", "user.email", "picker@example.test"],
|
|
);
|
|
temp
|
|
}
|
|
|
|
fn changes_by_path(
|
|
root: &Path,
|
|
buffers: &[(PathBuf, Arc<str>)],
|
|
) -> BTreeMap<PathBuf, WorkingFileChange> {
|
|
working_file_changes(root, buffers, &|| false)
|
|
.unwrap()
|
|
.into_iter()
|
|
.map(|change| (change.path.clone(), change))
|
|
.collect()
|
|
}
|
|
|
|
#[test]
|
|
fn working_git_changes_cover_disk_index_buffers_binary_exact_renames_and_read_only_queries() {
|
|
use std::os::unix::{ffi::OsStringExt, fs::PermissionsExt};
|
|
let temp = picker_git_fixture();
|
|
let root = &temp.0;
|
|
for (name, contents) in [
|
|
(".gitignore", "ignored*\n"),
|
|
("modified", "original\r\n"),
|
|
("unchanged", "same\n"),
|
|
("removed", "deleted\n"),
|
|
("old", "rename content\n"),
|
|
("duplicate-one", "duplicate\n"),
|
|
("duplicate-two", "duplicate\n"),
|
|
("mode", "mode\n"),
|
|
("binary", "before\0binary"),
|
|
] {
|
|
fs::write(root.join(name), contents).unwrap();
|
|
}
|
|
let non_utf8 = PathBuf::from(OsString::from_vec(b"non-utf8-\xff".to_vec()));
|
|
let non_utf8 = match fs::write(root.join(&non_utf8), "before\n") {
|
|
Ok(()) => Some(non_utf8),
|
|
Err(error) if cfg!(target_os = "macos") && error.raw_os_error() == Some(92) => None,
|
|
Err(error) => panic!("cannot create byte filename: {error}"),
|
|
};
|
|
fixture_command(root, "git", &["add", "--all"]);
|
|
fixture_command(root, "git", &["commit", "--quiet", "-m", "Baseline"]);
|
|
fs::write(root.join("modified"), "staged\n").unwrap();
|
|
fixture_command(root, "git", &["add", "--", "modified"]);
|
|
fs::write(root.join("modified"), "unstaged\n").unwrap();
|
|
fs::write(root.join("binary"), "after\0binary").unwrap();
|
|
if let Some(name) = &non_utf8 {
|
|
fs::write(root.join(name), "after\n").unwrap();
|
|
}
|
|
fs::remove_file(root.join("removed")).unwrap();
|
|
let renamed = "-new [file]\n\t\".txt";
|
|
fs::rename(root.join("old"), root.join(renamed)).unwrap();
|
|
fs::remove_file(root.join("duplicate-one")).unwrap();
|
|
fs::remove_file(root.join("duplicate-two")).unwrap();
|
|
fs::write(root.join("new-duplicate-one"), "duplicate\n").unwrap();
|
|
fs::write(root.join("new-duplicate-two"), "duplicate\n").unwrap();
|
|
fs::set_permissions(root.join("mode"), fs::Permissions::from_mode(0o755)).unwrap();
|
|
fs::write(root.join("added"), "added\n").unwrap();
|
|
fs::write(root.join("ignored"), "ignored\n").unwrap();
|
|
fs::write(root.join("ignored-tracked"), "forced\n").unwrap();
|
|
fixture_command(root, "git", &["add", "-f", "--", "ignored-tracked"]);
|
|
std::os::unix::fs::symlink(root.join("modified"), root.join("link")).unwrap();
|
|
let index = fs::read(root.join(".git/index")).unwrap();
|
|
let head = fs::read(root.join(".git/HEAD")).unwrap();
|
|
let disk = changes_by_path(root, &[]);
|
|
for name in ["modified", "binary", "mode"] {
|
|
assert_eq!(disk[Path::new(name)].status, FileStatus::Modified);
|
|
}
|
|
if let Some(name) = &non_utf8 {
|
|
assert_eq!(disk[name].status, FileStatus::Modified);
|
|
}
|
|
assert_eq!(disk[Path::new("removed")].status, FileStatus::Removed);
|
|
assert_eq!(disk[Path::new(renamed)].status, FileStatus::Renamed);
|
|
assert_eq!(
|
|
disk[Path::new(renamed)].old_path.as_deref(),
|
|
Some(Path::new("old"))
|
|
);
|
|
for name in [
|
|
"added",
|
|
"ignored-tracked",
|
|
"new-duplicate-one",
|
|
"new-duplicate-two",
|
|
] {
|
|
assert_eq!(disk[Path::new(name)].status, FileStatus::Added);
|
|
}
|
|
for name in ["duplicate-one", "duplicate-two"] {
|
|
assert_eq!(disk[Path::new(name)].status, FileStatus::Removed);
|
|
}
|
|
for name in ["unchanged", "old", "ignored", "link"] {
|
|
assert!(!disk.contains_key(Path::new(name)), "{name}");
|
|
}
|
|
let overlays = changes_by_path(
|
|
root,
|
|
&[
|
|
(root.join("modified"), "original\n".into()),
|
|
(root.join("unchanged"), "dirty\n".into()),
|
|
(root.join("removed"), "deleted\n".into()),
|
|
(root.join(renamed), "dirty renamed\n".into()),
|
|
(root.join("mode"), "mode\n".into()),
|
|
(root.join("ignored-buffer"), "ignored\n".into()),
|
|
],
|
|
);
|
|
assert!(!overlays.contains_key(Path::new("modified")));
|
|
assert!(!overlays.contains_key(Path::new("ignored-buffer")));
|
|
assert_eq!(
|
|
overlays[Path::new("unchanged")].status,
|
|
FileStatus::Modified
|
|
);
|
|
assert_eq!(overlays[Path::new("removed")].status, FileStatus::Removed);
|
|
assert_eq!(overlays[Path::new(renamed)].status, FileStatus::Renamed);
|
|
assert_eq!(overlays[Path::new("mode")].status, FileStatus::Modified);
|
|
let aliases = Temporary::new().unwrap();
|
|
let alias = aliases.0.join("checkout");
|
|
std::os::unix::fs::symlink(root, &alias).unwrap();
|
|
let aliased = changes_by_path(root, &[(alias.join("unchanged"), "dirty\n".into())]);
|
|
assert_eq!(aliased[Path::new("unchanged")].status, FileStatus::Modified);
|
|
fixture_command(root, "git", &["config", "core.filemode", "0"]);
|
|
assert!(
|
|
!changes_by_path(root, &[(root.join("mode"), "mode\n".into())])
|
|
.contains_key(Path::new("mode"))
|
|
);
|
|
fixture_command(root, "git", &["config", "core.autocrlf", "1"]);
|
|
fs::write(root.join("unchanged"), "same\r\n").unwrap();
|
|
assert!(!changes_by_path(root, &[]).contains_key(Path::new("unchanged")));
|
|
fixture_command(root, "git", &["config", "--unset", "core.autocrlf"]);
|
|
fixture_command(root, "git", &["config", "core.filemode", "true"]);
|
|
fs::write(root.join("unchanged"), "same\n").unwrap();
|
|
assert_eq!(fs::read(root.join(".git/index")).unwrap(), index);
|
|
assert_eq!(fs::read(root.join(".git/HEAD")).unwrap(), head);
|
|
assert_eq!(
|
|
fs::read_to_string(root.join("modified")).unwrap(),
|
|
"unstaged\n"
|
|
);
|
|
assert!(!root.join("removed").exists());
|
|
}
|
|
|
|
#[test]
|
|
fn working_git_subdirectory_cross_boundary_moves_worktrees_and_unsafe_roots() {
|
|
let temp = picker_git_fixture();
|
|
let root = &temp.0;
|
|
fs::create_dir(root.join("sub")).unwrap();
|
|
fs::write(root.join("sub/outgoing"), "out\n").unwrap();
|
|
fs::write(root.join("incoming"), "in\n").unwrap();
|
|
fixture_command(root, "git", &["add", "--all"]);
|
|
fixture_command(root, "git", &["commit", "--quiet", "-m", "Baseline"]);
|
|
fs::rename(root.join("sub/outgoing"), root.join("outgoing")).unwrap();
|
|
fs::rename(root.join("incoming"), root.join("sub/incoming")).unwrap();
|
|
let changes = changes_by_path(&root.join("sub"), &[]);
|
|
assert_eq!(changes.len(), 2);
|
|
assert_eq!(changes[Path::new("outgoing")].status, FileStatus::Removed);
|
|
assert_eq!(changes[Path::new("incoming")].status, FileStatus::Added);
|
|
assert_eq!(changes[Path::new("incoming")].old_path, None);
|
|
std::os::unix::fs::symlink(root.join("sub"), root.join("linked")).unwrap();
|
|
assert!(changes_by_path(&root.join("linked"), &[]).is_empty());
|
|
assert!(changes_by_path(&root.join(".git"), &[]).is_empty());
|
|
let unrelated = Temporary::new().unwrap();
|
|
assert!(changes_by_path(&unrelated.0, &[]).is_empty());
|
|
let worktree = temp.0.join("worktree");
|
|
fixture_command(
|
|
root,
|
|
"git",
|
|
&["worktree", "add", "--quiet", "--detach", "worktree", "HEAD"],
|
|
);
|
|
fs::write(worktree.join("sub/outgoing"), "worktree edit\n").unwrap();
|
|
let changes = changes_by_path(&worktree.join("sub"), &[]);
|
|
assert_eq!(changes[Path::new("outgoing")].status, FileStatus::Modified);
|
|
}
|
|
|
|
#[test]
|
|
fn working_unborn_git_never_runs_filters_external_diff_textconv_or_hooks() {
|
|
let temp = picker_git_fixture();
|
|
let root = &temp.0;
|
|
fs::write(root.join(".gitignore"), "ignored\n").unwrap();
|
|
fs::write(root.join("tracked"), "tracked\n").unwrap();
|
|
fs::write(root.join("untracked"), "untracked\n").unwrap();
|
|
fs::write(root.join("ignored"), "ignored\n").unwrap();
|
|
fixture_command(root, "git", &["add", "--", ".gitignore", "tracked"]);
|
|
let changes = changes_by_path(root, &[(root.join("named"), "not saved\n".into())]);
|
|
for name in [".gitignore", "tracked", "untracked", "named"] {
|
|
assert_eq!(changes[Path::new(name)].status, FileStatus::Added);
|
|
}
|
|
assert!(!changes.contains_key(Path::new("ignored")));
|
|
assert!(!root.join("named").exists());
|
|
fixture_command(root, "git", &["commit", "--quiet", "-m", "Baseline"]);
|
|
fs::write(
|
|
root.join(".gitattributes"),
|
|
"tracked filter=evil diff=evil\n",
|
|
)
|
|
.unwrap();
|
|
let evil = "sh -c 'touch executed; cat'";
|
|
for key in [
|
|
"filter.evil.clean",
|
|
"filter.evil.process",
|
|
"diff.evil.command",
|
|
"diff.evil.textconv",
|
|
] {
|
|
fixture_command(root, "git", &["config", key, evil]);
|
|
}
|
|
fixture_command(root, "git", &["config", "filter.evil.required", "true"]);
|
|
fs::write(root.join("tracked"), "changed\n").unwrap();
|
|
assert_eq!(
|
|
changes_by_path(root, &[])[Path::new("tracked")].status,
|
|
FileStatus::Modified
|
|
);
|
|
assert_eq!(
|
|
changes_by_path(root, &[(root.join("tracked"), "tracked\n".into())])
|
|
.get(Path::new("tracked")),
|
|
None
|
|
);
|
|
assert!(!root.join("executed").exists());
|
|
assert!(
|
|
working_file_changes(root, &[], &|| true)
|
|
.unwrap_err()
|
|
.contains("cancelled")
|
|
);
|
|
assert!(
|
|
working_file_changes(
|
|
root,
|
|
&[(root.join("tracked"), "x".repeat(FILE_LIMIT + 1).into())],
|
|
&|| false
|
|
)
|
|
.unwrap_err()
|
|
.contains("16 MiB")
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn working_jj_root_parent_colocation_and_merge_queries_do_not_snapshot() {
|
|
for colocate in ["--colocate", "--no-colocate"] {
|
|
let temp = Temporary::new().unwrap();
|
|
let root = &temp.0;
|
|
fixture_command(root, "jj", &["git", "init", colocate, "."]);
|
|
fs::write(root.join("file"), "baseline\n").unwrap();
|
|
assert_eq!(
|
|
changes_by_path(root, &[])[Path::new("file")].status,
|
|
FileStatus::Added
|
|
);
|
|
fs::write(root.join(".gitignore"), "ignored\n").unwrap();
|
|
fixture_command(root, "jj", &["describe", "-m", "Baseline"]);
|
|
fixture_command(root, "jj", &["new"]);
|
|
let operation = fixture_command(
|
|
root,
|
|
"jj",
|
|
&[
|
|
"--ignore-working-copy",
|
|
"op",
|
|
"log",
|
|
"--no-graph",
|
|
"-n",
|
|
"1",
|
|
"-T",
|
|
"id",
|
|
],
|
|
);
|
|
let parent = fixture_command(
|
|
root,
|
|
"jj",
|
|
&[
|
|
"--ignore-working-copy",
|
|
"log",
|
|
"--no-graph",
|
|
"-r",
|
|
"@-",
|
|
"-T",
|
|
"commit_id",
|
|
],
|
|
);
|
|
let store = store_for_jj(root).unwrap();
|
|
let head = fs::read(store.join("HEAD")).unwrap();
|
|
let index = fs::read(store.join("index")).ok();
|
|
fs::write(root.join("file"), "disk\n").unwrap();
|
|
fs::write(root.join("new"), "new\n").unwrap();
|
|
fs::write(root.join("ignored"), "ignored\n").unwrap();
|
|
let changes = changes_by_path(root, &[]);
|
|
assert_eq!(changes[Path::new("file")].status, FileStatus::Modified);
|
|
assert_eq!(changes[Path::new("new")].status, FileStatus::Added);
|
|
assert!(!changes.contains_key(Path::new("ignored")));
|
|
assert!(
|
|
!changes_by_path(root, &[(root.join("file"), "baseline\n".into())])
|
|
.contains_key(Path::new("file"))
|
|
);
|
|
assert_eq!(fs::read(store.join("HEAD")).unwrap(), head);
|
|
assert_eq!(fs::read(store.join("index")).ok(), index);
|
|
assert_eq!(
|
|
fixture_command(
|
|
root,
|
|
"jj",
|
|
&[
|
|
"--ignore-working-copy",
|
|
"op",
|
|
"log",
|
|
"--no-graph",
|
|
"-n",
|
|
"1",
|
|
"-T",
|
|
"id"
|
|
]
|
|
),
|
|
operation
|
|
);
|
|
fixture_command(root, "jj", &["new", "root()"]);
|
|
fs::write(root.join("sibling"), "sibling\n").unwrap();
|
|
fixture_command(root, "jj", &["describe", "-m", "Sibling"]);
|
|
fixture_command(root, "jj", &["new", &parent, "@"]);
|
|
assert!(changes_by_path(root, &[]).is_empty());
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn working_jj_conflicted_parent_uses_cli_projection_without_snapshotting() {
|
|
for colocate in ["--colocate", "--no-colocate"] {
|
|
let temp = Temporary::new().unwrap();
|
|
let root = &temp.0;
|
|
fixture_command(root, "jj", &["git", "init", colocate, "."]);
|
|
fs::write(root.join("conflicted"), "base\n").unwrap();
|
|
fixture_command(root, "jj", &["describe", "-m", "Base"]);
|
|
let base = fixture_command(
|
|
root,
|
|
"jj",
|
|
&[
|
|
"--ignore-working-copy",
|
|
"log",
|
|
"--no-graph",
|
|
"-r",
|
|
"@",
|
|
"-T",
|
|
"commit_id",
|
|
],
|
|
);
|
|
fixture_command(root, "jj", &["new"]);
|
|
fs::write(root.join("conflicted"), "left\n").unwrap();
|
|
fixture_command(root, "jj", &["describe", "-m", "Left"]);
|
|
let left = fixture_command(
|
|
root,
|
|
"jj",
|
|
&[
|
|
"--ignore-working-copy",
|
|
"log",
|
|
"--no-graph",
|
|
"-r",
|
|
"@",
|
|
"-T",
|
|
"commit_id",
|
|
],
|
|
);
|
|
fixture_command(root, "jj", &["new", &base]);
|
|
fs::write(root.join("conflicted"), "right\n").unwrap();
|
|
fixture_command(root, "jj", &["describe", "-m", "Right"]);
|
|
let right = fixture_command(
|
|
root,
|
|
"jj",
|
|
&[
|
|
"--ignore-working-copy",
|
|
"log",
|
|
"--no-graph",
|
|
"-r",
|
|
"@",
|
|
"-T",
|
|
"commit_id",
|
|
],
|
|
);
|
|
fixture_command(root, "jj", &["new", &left, &right]);
|
|
fixture_command(root, "jj", &["new"]);
|
|
let projected = fs::read_to_string(root.join("conflicted")).unwrap();
|
|
assert!(projected.contains("<<<<<<<"));
|
|
let operation = fixture_command(
|
|
root,
|
|
"jj",
|
|
&[
|
|
"--ignore-working-copy",
|
|
"op",
|
|
"log",
|
|
"--no-graph",
|
|
"-n",
|
|
"1",
|
|
"-T",
|
|
"id",
|
|
],
|
|
);
|
|
let mut reader = WorkingFileReader::default();
|
|
assert!(reader.read(root, &[], &|| false).unwrap().is_empty());
|
|
fs::write(root.join("conflicted"), "resolved in working file\n").unwrap();
|
|
let changes = reader.read(root, &[], &|| false).unwrap();
|
|
assert_eq!(changes.len(), 1);
|
|
assert_eq!(changes[0].path, Path::new("conflicted"));
|
|
assert_eq!(changes[0].status, FileStatus::Modified);
|
|
assert!(
|
|
reader
|
|
.read(
|
|
root,
|
|
&[(root.join("conflicted"), projected.into())],
|
|
&|| false
|
|
)
|
|
.unwrap()
|
|
.is_empty()
|
|
);
|
|
assert_eq!(
|
|
fs::read_to_string(root.join("conflicted")).unwrap(),
|
|
"resolved in working file\n"
|
|
);
|
|
assert_eq!(
|
|
fixture_command(
|
|
root,
|
|
"jj",
|
|
&[
|
|
"--ignore-working-copy",
|
|
"op",
|
|
"log",
|
|
"--no-graph",
|
|
"-n",
|
|
"1",
|
|
"-T",
|
|
"id"
|
|
]
|
|
),
|
|
operation
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn jj_catalog_exposes_fixed_metadata_and_merge_parents_without_snapshotting() {
|
|
let temp = Temporary::new().unwrap();
|
|
let root = &temp.0;
|
|
fixture_command(root, "jj", &["git", "init", "--no-colocate", "."]);
|
|
fixture_command(root, "jj", &["describe", "-m", "Base"]);
|
|
let id = |root: &Path| {
|
|
fixture_command(
|
|
root,
|
|
"jj",
|
|
&[
|
|
"--ignore-working-copy",
|
|
"log",
|
|
"--no-graph",
|
|
"-r",
|
|
"@",
|
|
"-T",
|
|
"commit_id",
|
|
],
|
|
)
|
|
};
|
|
let base = id(root);
|
|
fixture_command(root, "jj", &["bookmark", "create", "base"]);
|
|
fixture_command(root, "jj", &["new", &base, "-m", "Left"]);
|
|
let left = id(root);
|
|
fixture_command(root, "jj", &["new", &base, "-m", "Right"]);
|
|
let right = id(root);
|
|
fixture_command(root, "jj", &["new", &left, &right, "-m", "Merge"]);
|
|
let current = id(root);
|
|
let operation = fixture_command(
|
|
root,
|
|
"jj",
|
|
&[
|
|
"--ignore-working-copy",
|
|
"op",
|
|
"log",
|
|
"--no-graph",
|
|
"-n",
|
|
"1",
|
|
"-T",
|
|
"id",
|
|
],
|
|
);
|
|
let mut repository = Repository::open(root, Arc::new(AtomicBool::new(false))).unwrap();
|
|
let catalog = repository.catalog().unwrap();
|
|
let merge = &catalog.graph[¤t];
|
|
assert_eq!(merge.parents, [left, right]);
|
|
assert_eq!(merge.author, "Picker Author");
|
|
assert_eq!(merge.timestamp, "2001-01-01 00:00");
|
|
assert_eq!(merge.description, "Merge");
|
|
assert!(!merge.change_id.is_empty());
|
|
assert!(merge.current);
|
|
assert_eq!(catalog.graph[&base].bookmarks, "base");
|
|
assert!(!catalog.graph[&base].current);
|
|
assert!(catalog.graph[&"0".repeat(40)].immutable);
|
|
assert_eq!(
|
|
fixture_command(
|
|
root,
|
|
"jj",
|
|
&[
|
|
"--ignore-working-copy",
|
|
"op",
|
|
"log",
|
|
"--no-graph",
|
|
"-n",
|
|
"1",
|
|
"-T",
|
|
"id"
|
|
]
|
|
),
|
|
operation
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn cancellation_kills_pipe_holding_children_and_drains_both_streams() {
|
|
let temp = Temporary::new().unwrap();
|
|
let ready = temp.0.join("ready");
|
|
let cancelled = Arc::new(AtomicBool::new(false));
|
|
let worker_cancelled = Arc::clone(&cancelled);
|
|
let gate = ready.clone();
|
|
let (sent, received) = mpsc::channel();
|
|
let worker = thread::spawn(move || {
|
|
let mut command = Command::new("sh");
|
|
command
|
|
.args([
|
|
"-c",
|
|
"sleep 600 & printf ready > \"$1\"; wait",
|
|
"vamp-diff-test",
|
|
])
|
|
.arg(gate);
|
|
let result = run(command, vec![], &worker_cancelled).map(|_| ());
|
|
sent.send(result).unwrap();
|
|
});
|
|
// File handshake, not a wall-clock scheduling assumption.
|
|
while !ready.exists() {
|
|
if let Ok(result) = received.try_recv() {
|
|
panic!("Process exited before the child was ready: {result:?}");
|
|
}
|
|
thread::yield_now();
|
|
}
|
|
cancelled.store(true, Ordering::Release);
|
|
assert!(received.recv().unwrap().unwrap_err().contains("cancelled"));
|
|
worker.join().unwrap();
|
|
|
|
let mut command = Command::new("sh");
|
|
command.args(["-c", "i=0; while [ \"$i\" -lt 10000 ]; do printf 'line\\n'; printf 'error\\n' >&2; i=$((i+1)); done"]);
|
|
let result = run(command, vec![], &AtomicBool::new(false)).unwrap();
|
|
assert_eq!(result.code, Some(0));
|
|
assert_eq!(result.stdout.len(), 50_000);
|
|
assert_eq!(result.stderr.len(), 60_000);
|
|
}
|