Split shell tests by profile and fix C builtin status

This commit is contained in:
Sami Samhuri 2026-02-07 15:16:56 -08:00
parent 850b2c23e2
commit 050353412c
No known key found for this signature in database
4 changed files with 49 additions and 13 deletions

View file

@ -10,7 +10,7 @@ a1: $(OBJS)
$(CC) $(CFLAGS) -o a1 $(OBJS) $(LDFLAGS) -lreadline -lhistory -ltermcap
test: a1
cd ../ruby && A1_PATH=../c/a1 bundle exec rake test
cd ../ruby && A1_PATH=../c/a1 A1_TEST_PROFILE=compat bundle exec rake test
clean:
rm -rf $(OBJS) a1

View file

@ -26,8 +26,8 @@
int builtin_bg(int argc, char **argv) {
if (argc < 2) {
fprintf(stderr, "bg: usage 'bg <command>'\n");
fprintf(stderr, " runs <command> in the background\n");
fprintf(stderr, "Usage: bg <command>\n");
fprintf(stderr, "Runs <command> in the background\n");
return -1;
}

View file

@ -155,6 +155,8 @@ int handle_wordexp_result(int result, char *cmd) {
int process_command(char *line, options_t options) {
wordexp_t words;
int builtin_result = 0;
bool builtin_executed = false;
int result = wordexp(line, &words, WRDE_SHOWERR | WRDE_UNDEF);
if (handle_wordexp_result(result, line) && words.we_wordc > 0) {
if (options->verbose) {
@ -165,18 +167,26 @@ int process_command(char *line, options_t options) {
fprintf(stderr, "}\n");
}
/* try the built-in commands */
if (cmd_matches("bg", words.we_wordv[0]))
builtin_bg(words.we_wordc, words.we_wordv);
else if (cmd_matches("bgkill", words.we_wordv[0]))
builtin_bgkill(words.we_wordc, words.we_wordv);
else if (cmd_matches("bglist", words.we_wordv[0]))
builtin_bglist();
else if (cmd_matches("cd", words.we_wordv[0]))
builtin_cd(words.we_wordc, words.we_wordv);
else if (cmd_matches("clear", words.we_wordv[0]))
if (cmd_matches("bg", words.we_wordv[0])) {
builtin_result = builtin_bg(words.we_wordc, words.we_wordv);
builtin_executed = true;
} else if (cmd_matches("bgkill", words.we_wordv[0])) {
builtin_result = builtin_bgkill(words.we_wordc, words.we_wordv);
builtin_executed = true;
} else if (cmd_matches("bglist", words.we_wordv[0])) {
builtin_result = builtin_bglist();
builtin_executed = true;
} else if (cmd_matches("cd", words.we_wordv[0])) {
builtin_result = builtin_cd(words.we_wordc, words.we_wordv);
builtin_executed = true;
} else if (cmd_matches("clear", words.we_wordv[0])) {
builtin_clear();
else if (cmd_matches("pwd", words.we_wordv[0]))
builtin_executed = true;
} else if (cmd_matches("pwd", words.we_wordv[0])) {
builtin_pwd();
builtin_executed = true;
builtin_result = 0;
}
else if (cmd_matches("exit", words.we_wordv[0])) {
exit(0);
} else {
@ -189,6 +199,9 @@ int process_command(char *line, options_t options) {
}
add_history(line); /* add to the readline history */
wordfree(&words);
if (builtin_executed && builtin_result < 0) {
return builtin_result;
}
return 0;
} else {
return -2;

View file

@ -10,6 +10,7 @@ class ShellTest < Minitest::Test
TRIVIAL_SHELL_SCRIPT = "#!/bin/sh\ntrue".freeze
A1_PATH = ENV.fetch("A1_PATH", "./a1").freeze
COMPAT_PROFILE = ENV["A1_TEST_PROFILE"] == "compat"
def setup
FileUtils.mkdir_p("test_bin")
@ -23,6 +24,12 @@ class ShellTest < Minitest::Test
"#!/bin/sh\necho '#{code}'"
end
def requires_extended_shell!(feature)
return unless COMPAT_PROFILE
skip "requires extended shell feature: #{feature}"
end
def test_expands_environment_variables
assert_equal Dir.home, `#{A1_PATH} -c 'echo $HOME'`.chomp
assert_equal Dir.home, `#{A1_PATH} -c 'echo ${HOME}'`.chomp
@ -82,6 +89,7 @@ class ShellTest < Minitest::Test
end
def test_expands_brace_expansion
requires_extended_shell!("brace expansion")
assert_equal "a b", `#{A1_PATH} -c 'echo {a,b}'`.chomp
end
@ -94,6 +102,7 @@ class ShellTest < Minitest::Test
end
def test_keeps_control_operators_inside_command_substitution
requires_extended_shell!("nested command parsing in substitutions")
semicolon_stdout, semicolon_stderr, semicolon_status = Open3.capture3(A1_PATH, "-c", "echo $(echo hi; echo bye)")
assert semicolon_status.success?, semicolon_stderr
assert_equal "hi bye\n", semicolon_stdout
@ -104,6 +113,7 @@ class ShellTest < Minitest::Test
end
def test_expands_command_substitution_with_escaped_quote
requires_extended_shell!("escaped quote handling in substitutions")
assert_equal "a\"b", `#{A1_PATH} -c 'echo $(printf \"%s\" \"a\\\"b\")'`.chomp
end
@ -112,6 +122,7 @@ class ShellTest < Minitest::Test
end
def test_expands_arithmetic_with_variables
requires_extended_shell!("arithmetic variable lookup")
assert_equal "3", `A1_NUM=2 #{A1_PATH} -c 'echo $((A1_NUM + 1))'`.chomp
end
@ -122,18 +133,22 @@ class ShellTest < Minitest::Test
end
def test_expands_parameter_default_value
requires_extended_shell!("${var:-fallback}")
assert_equal "fallback", `#{A1_PATH} -c 'echo ${A1_UNSET_VAR:-fallback}'`.chomp
end
def test_expands_parameter_default_value_with_variable_reference
requires_extended_shell!("${var:-$OTHER}")
assert_equal Dir.home, `#{A1_PATH} -c 'echo ${A1_UNSET_VAR:-$HOME}'`.chomp
end
def test_expands_parameter_default_value_with_command_substitution
requires_extended_shell!("${var:-$(...)}")
assert_equal "hi", `#{A1_PATH} -c 'echo ${A1_UNSET_VAR:-$(echo hi)}'`.chomp
end
def test_expands_glob_from_parameter_default_value
requires_extended_shell!("glob expansion from parameter defaults")
File.write("default_glob_a.txt", TRIVIAL_SHELL_SCRIPT)
File.write("default_glob_b.txt", TRIVIAL_SHELL_SCRIPT)
output = `#{A1_PATH} -c 'printf "%s\n" ${A1_UNSET_GLOB_VAR:-default_glob_*.txt}'`.lines.map(&:chomp).sort
@ -144,6 +159,7 @@ class ShellTest < Minitest::Test
end
def test_reports_command_substitution_failure_with_status
requires_extended_shell!("command substitution error propagation")
_stdout, stderr, status = Open3.capture3(A1_PATH, "-c", "echo $(exit 7)")
refute status.success?
assert_match(/command substitution failed/, stderr)
@ -152,6 +168,7 @@ class ShellTest < Minitest::Test
end
def test_expands_nested_defaults_with_substitution_and_arithmetic
requires_extended_shell!("nested defaults and arithmetic")
command = 'echo ${A1_OUTER_UNSET:-${A1_MIDDLE_UNSET:-${A1_INNER_UNSET:-$(printf "%s" "calc_$((2+3))")}}}'
assert_equal "calc_5", `#{A1_PATH} -c '#{command}'`.chomp
end
@ -185,6 +202,7 @@ class ShellTest < Minitest::Test
end
def test_combines_expansions_in_defaults_and_subcommands
requires_extended_shell!("composed substitutions and defaults")
File.write("combo_a.txt", TRIVIAL_SHELL_SCRIPT)
File.write("combo_b.txt", TRIVIAL_SHELL_SCRIPT)
@ -229,6 +247,7 @@ class ShellTest < Minitest::Test
end
def test_rejects_empty_command_around_and_operator
requires_extended_shell!("top-level && parsing")
_stdout1, stderr1, status1 = Open3.capture3(A1_PATH, "-c", "&& echo hi")
refute status1.success?
assert_match(/syntax/i, stderr1)
@ -303,18 +322,22 @@ class ShellTest < Minitest::Test
#########################
def test_builtin_cd_no_args
requires_extended_shell!("multi-command sequencing with ;")
assert_equal Dir.home, `#{A1_PATH} -c 'cd; echo $PWD'`.strip
end
def test_builtin_cd
requires_extended_shell!("multi-command sequencing with ;")
assert_equal File.join(Dir.pwd, "blah"), `#{A1_PATH} -c 'mkdir -p blah; cd blah; echo $PWD; cd ..; rm -rf blah'`.strip
end
def test_builtin_cd_dash
requires_extended_shell!("multi-command sequencing with ;")
assert_equal Dir.pwd, `#{A1_PATH} -c 'mkdir -p blah; cd blah; cd -; rm -rf blah; echo $PWD'`.strip
end
def test_builtin_cd_parent
requires_extended_shell!("multi-command sequencing with ;")
assert_equal Dir.pwd, `#{A1_PATH} -c 'mkdir -p blah; cd blah; cd ..; rm -rf blah; echo $PWD'`.strip
end