1603 lines · 48.0 KB · Rust
RawDownload
// 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::ffi::OsStr; use clap_complete::Shell;use itertools::Itertools as _;use test_case::test_case; use crate::common::CommandOutput;use crate::common::TestEnvironment;use crate::common::TestWorkDir; impl TestEnvironment {    /// Runs `jj` as if shell completion had been triggered for the argument of    /// the given index.    #[must_use = "either snapshot the output or assert the exit status with .success()"]    fn complete_at<I>(&self, shell: Shell, index: usize, args: I) -> CommandOutput    where        I: IntoIterator,        I::IntoIter: ExactSizeIterator,        I::Item: AsRef<OsStr>,    {        self.work_dir("").complete_at(shell, index, args)    }     /// Run `jj` as if fish shell completion had been triggered at the last item    /// in `args`.    #[must_use = "either snapshot the output or assert the exit status with .success()"]    fn complete_fish<I>(&self, args: I) -> CommandOutput    where        I: IntoIterator,        I::IntoIter: ExactSizeIterator,        I::Item: AsRef<OsStr>,    {        self.work_dir("").complete_fish(args)    }} impl TestWorkDir<'_> {    /// Runs `jj` as if shell completion had been triggered for the argument of    /// the given index.    #[must_use = "either snapshot the output or assert the exit status with .success()"]    fn complete_at<I>(&self, shell: Shell, index: usize, args: I) -> CommandOutput    where        I: IntoIterator,        I::IntoIter: ExactSizeIterator,        I::Item: AsRef<OsStr>,    {        let args = args.into_iter();        assert!(            index <= args.len(),            "index out of bounds: append empty string to complete after the last argument"        );        self.run_jj_with(|cmd| {            let cmd = cmd.env("COMPLETE", shell.to_string()).args(["--", "jj"]);             match shell {                // Bash passes the whole command line except for intermediate empty tokens but                // preserves trailing empty tokens:                // `jj log <CURSOR>`            => ["--", "jj", "log", ""]                //                                 with _CLAP_COMPLETE_INDEX=2                // `jj log <CURSOR> --no-graph` => ["--", "jj", "log", "--no-graph"]                //                                 with _CLAP_COMPLETE_INDEX=2                // `jj log --no-graph<CURSOR>`  => ["--", "jj", "log", "--no-graph"]                //                                 with _CLAP_COMPLETE_INDEX=2                //                                 (indistinguishable from the above)                // `jj log --no-graph <CURSOR>` => ["--", "jj", "log", "--no-graph", ""]                //                                 with _CLAP_COMPLETE_INDEX=3                Shell::Bash => {                    cmd.env("_CLAP_COMPLETE_INDEX", index.to_string())                        .args(args.coalesce(|a, b| {                            if a.as_ref().is_empty() {                                Ok(b)                            } else {                                Err((a, b))                            }                        }))                }                 // Zsh passes the whole command line except for empty tokens, including trailing                // empty tokens:                // `jj log <CURSOR>`            => ["--", "jj", "log"]                //                                 with _CLAP_COMPLETE_INDEX=2                // `jj log <CURSOR> --no-graph` => ["--", "jj", "log", "--no-graph"]                //                                 with _CLAP_COMPLETE_INDEX=2                // `jj log --no-graph<CURSOR>`  => ["--", "jj", "log", "--no-graph"]                //                                 with _CLAP_COMPLETE_INDEX=2                //                                 (indistinguishable from the above)                // `jj log --no-graph <CURSOR>` => ["--", "jj", "log", "--no-graph"]                //                                 with _CLAP_COMPLETE_INDEX=3                Shell::Zsh => cmd                    .env("_CLAP_COMPLETE_INDEX", index.to_string())                    .args(args.filter(|a| !a.as_ref().is_empty())),                 // Fish truncates the command line at the cursor; empty tokens are preserved:                // `jj log <CURSOR>`            => ["--", "jj", "log", ""]                // `jj log <CURSOR> --no-graph` => ["--", "jj", "log", ""]                // `jj log --no-graph<CURSOR>`  => ["--", "jj", "log", "--no-graph"]                // `jj log --no-graph <CURSOR>` => ["--", "jj", "log", "--no-graph", ""]                Shell::Fish => cmd.args(args.take(index)),                 _ => todo!("{shell} completion behavior not implemented yet"),            }        })    }     /// Run `jj` as if fish shell completion had been triggered at the last item    /// in `args`.    #[must_use = "either snapshot the output or assert the exit status with .success()"]    fn complete_fish<I>(&self, args: I) -> CommandOutput    where        I: IntoIterator,        I::IntoIter: ExactSizeIterator,        I::Item: AsRef<OsStr>,    {        let args = args.into_iter();        self.complete_at(Shell::Fish, args.len(), args)    }} #[test]fn test_bookmark_names() {    let test_env = TestEnvironment::default();    test_env.run_jj_in(".", ["git", "init", "repo"]).success();    let work_dir = test_env.work_dir("repo");     test_env.run_jj_in(".", ["git", "init", "origin"]).success();    let origin_dir = test_env.work_dir("origin");    let origin_git_repo_path = origin_dir        .root()        .join(".jj")        .join("repo")        .join("store")        .join("git");     work_dir        .run_jj(["bookmark", "create", "-r@", "aaa-local"])        .success();    work_dir        .run_jj(["bookmark", "create", "-r@", "bbb-local"])        .success();     // add various remote branches    work_dir        .run_jj([            "git",            "remote",            "add",            "origin",            origin_git_repo_path.to_str().unwrap(),        ])        .success();    work_dir        .run_jj(["bookmark", "create", "-r@", "aaa-tracked"])        .success();    work_dir        .run_jj(["desc", "-r", "aaa-tracked", "-m", "x"])        .success();    work_dir        .run_jj(["bookmark", "create", "-r@", "bbb-tracked"])        .success();    work_dir        .run_jj(["desc", "-r", "bbb-tracked", "-m", "x"])        .success();    work_dir        .run_jj(["git", "push", "--allow-new", "--bookmark", "glob:*-tracked"])        .success();     origin_dir        .run_jj(["bookmark", "create", "-r@", "aaa-untracked"])        .success();    origin_dir        .run_jj(["desc", "-r", "aaa-untracked", "-m", "x"])        .success();    origin_dir        .run_jj(["bookmark", "create", "-r@", "bbb-untracked"])        .success();    origin_dir        .run_jj(["desc", "-r", "bbb-untracked", "-m", "x"])        .success();    origin_dir.run_jj(["git", "export"]).success();    work_dir.run_jj(["git", "fetch"]).success();     // Every shell hook is a little different, e.g. the zsh hooks add some    // additional environment variables. But this is irrelevant for the purpose    // of testing our own logic, so it's fine to test a single shell only.    let work_dir = test_env.work_dir("repo");     let output = work_dir.complete_fish(["bookmark", "rename", ""]);    insta::assert_snapshot!(output, @r"    aaa-local	x    aaa-tracked	x    bbb-local	x    bbb-tracked	x    --repository	Path to repository to operate on    --ignore-working-copy	Don't snapshot the working copy, and don't update it    --ignore-immutable	Allow rewriting immutable commits    --at-operation	Operation to load the repo at    --debug	Enable debug logging    --color	When to colorize output    --quiet	Silence non-primary command output    --no-pager	Disable the pager    --config	Additional configuration options (can be repeated)    --config-file	Additional configuration files (can be repeated)    --help	Print help (see more with '--help')    [EOF]    ");     let output = work_dir.complete_fish(["bookmark", "rename", "a"]);    insta::assert_snapshot!(output, @r"    aaa-local	x    aaa-tracked	x    [EOF]    ");     let output = work_dir.complete_fish(["bookmark", "delete", "a"]);    insta::assert_snapshot!(output, @r"    aaa-local	x    aaa-tracked	x    [EOF]    ");     let output = work_dir.complete_fish(["bookmark", "forget", "a"]);    insta::assert_snapshot!(output, @r"    aaa-local	x    aaa-tracked	x    aaa-untracked    [EOF]    ");     let output = work_dir.complete_fish(["bookmark", "list", "--bookmark", "a"]);    insta::assert_snapshot!(output, @r"    aaa-local	x    aaa-tracked	x    aaa-untracked    [EOF]    ");     let output = work_dir.complete_fish(["bookmark", "move", "a"]);    insta::assert_snapshot!(output, @r"    aaa-local	x    aaa-tracked	x    [EOF]    ");     let output = work_dir.complete_fish(["bookmark", "set", "a"]);    insta::assert_snapshot!(output, @r"    aaa-local	x    aaa-tracked	x    [EOF]    ");     let output = work_dir.complete_fish(["bookmark", "track", "a"]);    insta::assert_snapshot!(output, @r"    aaa-local@origin	 x    aaa-untracked@origin	 x    [EOF]    ");     let output = work_dir.complete_fish(["bookmark", "untrack", "a"]);    insta::assert_snapshot!(output, @r"    aaa-tracked@origin	x    [EOF]    ");     let output = work_dir.complete_fish(["git", "push", "-b", "a"]);    insta::assert_snapshot!(output, @r"    aaa-local	x    aaa-tracked	x    [EOF]    ");     let output = work_dir.complete_fish(["git", "fetch", "-b", "a"]);    insta::assert_snapshot!(output, @r"    aaa-local	x    aaa-tracked	x    aaa-untracked    [EOF]    ");} #[test]fn test_tag_names() {    let test_env = TestEnvironment::default();    test_env.run_jj_in(".", ["git", "init", "repo"]).success();    let work_dir = test_env.work_dir("repo");     work_dir.run_jj(["commit", "-mcommit1"]).success();    work_dir        .run_jj(["tag", "set", "-r@-", "aaa-local"])        .success();    work_dir        .run_jj(["tag", "set", "-r@-", "bbb-local"])        .success();     let output = work_dir.complete_fish(["tag", "set", "a"]);    insta::assert_snapshot!(output, @r"    aaa-local	commit1    [EOF]    ");     let output = work_dir.complete_fish(["tag", "delete", "b"]);    insta::assert_snapshot!(output, @r"    bbb-local	commit1    [EOF]    ");} #[test]fn test_global_arg_repository_is_respected() {    let test_env = TestEnvironment::default();    test_env.run_jj_in(".", ["git", "init", "repo"]).success();    let work_dir = test_env.work_dir("repo");     work_dir        .run_jj(["bookmark", "create", "-r@", "aaa"])        .success();     let output = test_env.complete_fish(["--repository", "repo", "bookmark", "rename", "a"]);    insta::assert_snapshot!(output, @r"    aaa	(no description set)    [EOF]    ");} #[test_case(Shell::Bash; "bash")]#[test_case(Shell::Zsh; "zsh")]#[test_case(Shell::Fish; "fish")]fn test_aliases_are_resolved(shell: Shell) {    let test_env = TestEnvironment::default();    test_env.run_jj_in(".", ["git", "init", "repo"]).success();    let work_dir = test_env.work_dir("repo");     work_dir        .run_jj(["bookmark", "create", "-r@", "aaa"])        .success();     // user config alias    test_env.add_config(r#"aliases.b = ["bookmark"]"#);    test_env.add_config(r#"aliases.rlog = ["log", "--reversed"]"#);    // repo config alias    work_dir        .run_jj(["config", "set", "--repo", "aliases.b2", "['bookmark']"])        .success();     let output = work_dir.complete_at(shell, 3, ["b", "rename", "a"]);    match shell {        Shell::Bash => {            insta::assert_snapshot!(output, @"aaa[EOF]");        }        Shell::Zsh => {            insta::assert_snapshot!(output, @"aaa:(no description set)[EOF]");        }        Shell::Fish => {            insta::assert_snapshot!(output, @r"            aaa	(no description set)            [EOF]            ");        }        _ => unimplemented!("unexpected shell '{shell}'"),    }     let output = work_dir.complete_at(shell, 3, ["b2", "rename", "a"]);    match shell {        Shell::Bash => {            insta::assert_snapshot!(output, @"aaa[EOF]");        }        Shell::Zsh => {            insta::assert_snapshot!(output, @"aaa:(no description set)[EOF]");        }        Shell::Fish => {            insta::assert_snapshot!(output, @r"            aaa	(no description set)            [EOF]            ");        }        _ => unimplemented!("unexpected shell '{shell}'"),    }     let output = work_dir.complete_at(shell, 2, ["rlog", "--rev"]);    match shell {        Shell::Bash => {            insta::assert_snapshot!(output, @r"            --revisions            --reversed[EOF]            ");        }        Shell::Zsh => {            insta::assert_snapshot!(output, @r"            --revisions:Which revisions to show            --reversed:Show revisions in the opposite order (older revisions first)[EOF]            ");        }        Shell::Fish => {            insta::assert_snapshot!(output, @r"            --revisions	Which revisions to show            --reversed	Show revisions in the opposite order (older revisions first)            [EOF]            ");        }        _ => unimplemented!("unexpected shell '{shell}'"),    }} #[test]fn test_completions_are_generated() {    let mut test_env = TestEnvironment::default();    test_env.add_env_var("COMPLETE", "fish");    let mut insta_settings = insta::Settings::clone_current();    insta_settings.add_filter(r"(--arguments) .*", "$1 .."); // omit path to jj binary    let _guard = insta_settings.bind_to_scope();     let output = test_env.run_jj_in(".", [""; 0]);    insta::assert_snapshot!(output, @r"    complete --keep-order --exclusive --command jj --arguments ..    [EOF]    ");    let output = test_env.run_jj_in(".", ["--"]);    insta::assert_snapshot!(output, @r"    complete --keep-order --exclusive --command jj --arguments ..    [EOF]    ");} #[test_case(Shell::Bash; "bash")]#[test_case(Shell::Zsh; "zsh")]#[test_case(Shell::Fish; "fish")]fn test_default_command_is_resolved(shell: Shell) {    let test_env = TestEnvironment::default();     let output = test_env        .complete_at(shell, 1, ["--"])        .take_stdout_n_lines(2);    match shell {        Shell::Bash => {            insta::assert_snapshot!(output, @r"            --revisions            --limit            [EOF]            ");        }        Shell::Zsh => {            insta::assert_snapshot!(output, @r"            --revisions:Which revisions to show            --limit:Limit number of revisions to show            [EOF]            ");        }        Shell::Fish => {            insta::assert_snapshot!(output, @r"            --revisions	Which revisions to show            --limit	Limit number of revisions to show            [EOF]            ");        }        _ => unimplemented!("unexpected shell '{shell}'"),    }     test_env.add_config("ui.default-command = ['abandon']");    let output = test_env        .complete_at(shell, 1, ["--"])        .take_stdout_n_lines(2);    match shell {        Shell::Bash => {            insta::assert_snapshot!(output, @r"            --retain-bookmarks            --restore-descendants            [EOF]            ");        }        Shell::Zsh => {            insta::assert_snapshot!(output, @r"            --retain-bookmarks:Do not delete bookmarks pointing to the revisions to abandon            --restore-descendants:Do not modify the content of the children of the abandoned commits            [EOF]            ");        }        Shell::Fish => {            insta::assert_snapshot!(output, @r"            --retain-bookmarks	Do not delete bookmarks pointing to the revisions to abandon            --restore-descendants	Do not modify the content of the children of the abandoned commits            [EOF]            ");        }        _ => unimplemented!("unexpected shell '{shell}'"),    }     test_env.add_config("ui.default-command = ['bookmark', 'move']");

Lines 1–500 of 1603.