Skip to content

Commit 0d1ac8e

Browse files
committed
Test "$0" and "$@" in script files
This adds regression tests for the values the `$0` command name special parameter, and the `$@` positional parameters special parameter, when `gix-command` runs a shell script file whose path is specified as the value of a `Prepare` instance's `command` field: - `"$0"` expands to that path. - `"$@"` expands to the arguments passed to the script. - Neither contain any interpreter name, added quotes, placeholders. That holds whether the script is run directly, as would typically be the case, as well as when it is run indirectly through an intermediate shell instance, regardless of how that is done. The latter, while presumably uncommon, must also not affect what `"$0"` and `"$@"` expand to in the script. These regression tests already pass on Unix-like systems. If they don't pass on Windows, then it *may* be that these new tests should be adjusted, rather than the code under test. But it is important that the behavior of the code under test still confirms to the above description, modulo path style transformations. In particular, even on Windows, even if a shell is invoked differently, `$0` should expand to a path to the script, and *not* to the interpreter or anything else. But the path might potentially be altered, such as by changing `\` to `/`, otherwise normalizing or even canonicalizing it differently, and maybe even turning into a Cygwin/MSYS/MSYS2-style path. If necessary, the `$0` assertions on Windows could be weakened to assert, when it is parsed as a path, that the last path component is the script filename. Even more than to check that we already have reasonable `$0` behavior even on Windows, the motivation for these tests relates to a forthcoming change to an altogether *different* case of using a shell: when a command like `sh -c` is used. There, the command name argument that follows `-c` is currently `--`, which is misleading because it does not relate to the usual meanings of `--`, and which is not very informative (when `$0` is expanded for error or warning messages). As discussed in #1840, this shall be changed, probably to give the shell name. These regression tests for behavior that must *not* be affected by that forthcoming change serve three purposes related to it: - Clearly express to human readers that `$0` inside a script file expands to a path to the file that was used to run the script, regardless of how that file is being run: directly (relying on its shebang), by being passed explicitly to an interpreter, or by appearing as the operand to `-c`. The latter case must *still* behave this way under the forthcoming change, because that change will affect what `$0` appearing directly in the operand of `-c` expands to, *not* what it expands to in a subprocess created by `sh -c` that opens a separate script file containing occurrences of `$0` that the shell expands. In particular, these tests can be compared to fortcoming tests covering the case that *will* be changed. - Check that this requirement is not broken under the forthcoming change. - Check that this requirement is not broken under later changs that are not currently anticipated.
1 parent b4fe425 commit 0d1ac8e

File tree

3 files changed

+107
-0
lines changed

3 files changed

+107
-0
lines changed

gix-command/tests/command.rs

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -559,4 +559,96 @@ mod spawn {
559559
Ok(())
560560
}
561561
}
562+
563+
mod script {
564+
use std::ffi::{OsStr, OsString};
565+
use std::path::{Path, PathBuf};
566+
567+
use gix_testtools::bstr::ByteSlice;
568+
569+
fn script_path(filename: impl AsRef<Path>) -> crate::Result<PathBuf> {
570+
let mut path = gix_testtools::scripted_fixture_read_only("scripts.sh")?;
571+
path.push(filename);
572+
Ok(path)
573+
}
574+
575+
fn script_stdout_lines(
576+
path: impl Into<OsString>,
577+
args: Option<&[&OsStr]>, // Let us test calling vs. not calling `args` (rather than calling with `[]`).
578+
indirect: bool,
579+
) -> crate::Result<Vec<OsString>> {
580+
let mut prep = gix_command::prepare(path);
581+
if indirect {
582+
prep.use_shell = true;
583+
prep.allow_manual_arg_splitting = false;
584+
prep.quote_command = true;
585+
}
586+
if let Some(extra_args) = args {
587+
prep = prep.args(extra_args);
588+
}
589+
let out = prep.spawn()?.wait_with_output()?;
590+
assert!(out.status.success());
591+
assert!(out.stderr.is_empty());
592+
let lines = out
593+
.stdout
594+
.lines()
595+
.map(|line| line.to_os_str().expect("valid UTF-8"))
596+
.map(ToOwned::to_owned)
597+
.collect();
598+
Ok(lines)
599+
}
600+
601+
fn do_trivial(indirect: bool) -> crate::Result {
602+
let path = script_path("trivial")?;
603+
let lines = script_stdout_lines(path, None, indirect)?;
604+
assert_eq!(lines, ["Hello, world!"]);
605+
Ok(())
606+
}
607+
608+
#[test]
609+
fn trivial_direct() -> crate::Result {
610+
do_trivial(false)
611+
}
612+
613+
#[test]
614+
fn trivial_indirect() -> crate::Result {
615+
do_trivial(true)
616+
}
617+
618+
fn do_name_no_args(indirect: bool) -> crate::Result {
619+
let path = script_path("name-and-args")?;
620+
let lines = script_stdout_lines(&path, None, indirect)?;
621+
assert_eq!(lines, [path]);
622+
Ok(())
623+
}
624+
625+
#[test]
626+
fn name_no_args_direct() -> crate::Result {
627+
do_name_no_args(false)
628+
}
629+
630+
#[test]
631+
fn name_no_args_indirect() -> crate::Result {
632+
do_name_no_args(true)
633+
}
634+
635+
fn do_name_with_args(indirect: bool) -> crate::Result {
636+
let path = script_path("name-and-args")?;
637+
let args = ["foo", "bar baz", "quux"].map(OsStr::new);
638+
let expected: Vec<_> = std::iter::once(path.as_os_str()).chain(args).collect();
639+
let lines = script_stdout_lines(&path, Some(&args), indirect)?;
640+
assert_eq!(lines, expected);
641+
Ok(())
642+
}
643+
644+
#[test]
645+
fn name_with_args_direct() -> crate::Result {
646+
do_name_with_args(false)
647+
}
648+
649+
#[test]
650+
fn name_with_args_indirect() -> crate::Result {
651+
do_name_with_args(true)
652+
}
653+
}
562654
}
Binary file not shown.

gix-command/tests/fixtures/scripts.sh

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#!/usr/bin/env bash
2+
set -eu -o pipefail
3+
4+
cat >trivial <<'EOF'
5+
#!/bin/sh
6+
printf 'Hello, world!\n'
7+
EOF
8+
9+
cat >name-and-args <<'EOF'
10+
#!/bin/sh
11+
set -u
12+
printf '%s\n' "$0" "$@"
13+
EOF
14+
15+
chmod +x trivial name-and-args

0 commit comments

Comments
 (0)