Builtins fixed

The builtins wasnt protected, now all data received is protected, the hashmap addition is protected and added functionality of env, export and unset (not implemented in this version). Added fixed details documentation in docs/builtins_fixes.md generated by codex and created tests/builtins_edge_cases.sh to test all the builtins to work correctly
This commit is contained in:
marcnava-42cursus
2026-02-09 22:08:45 +01:00
parent 280fa51f94
commit 778e0c0481
15 changed files with 843 additions and 74 deletions

204
tests/builtins_edge_cases.sh Executable file
View File

@@ -0,0 +1,204 @@
#!/usr/bin/env bash
set -u
ROOT_DIR="$(cd -- "$(dirname -- "$0")/.." && pwd)"
MSH_BIN="$ROOT_DIR/minishell"
TMP_DIR="$(mktemp -d /tmp/minishell-builtins-tests.XXXXXX)"
PASS_COUNT=0
FAIL_COUNT=0
CASE_OUT=""
CASE_ERR=""
CASE_EC=0
CASE_NAME=""
CASE_FAILED=0
cleanup() {
rm -rf "$TMP_DIR"
}
pass() {
printf "PASS: %s\n" "$1"
PASS_COUNT=$((PASS_COUNT + 1))
}
fail() {
printf "FAIL: %s\n" "$1"
FAIL_COUNT=$((FAIL_COUNT + 1))
CASE_FAILED=1
}
assert_exit_code() {
local expected="$1"
if [ "$CASE_EC" -ne "$expected" ]; then
fail "$CASE_NAME -> exit code esperado $expected, obtenido $CASE_EC"
fi
}
assert_err_contains() {
local text="$1"
if ! grep -Fq -- "$text" "$CASE_ERR"; then
fail "$CASE_NAME -> stderr no contiene: $text"
fi
}
assert_out_contains() {
local text="$1"
if ! grep -Fq -- "$text" "$CASE_OUT"; then
fail "$CASE_NAME -> stdout no contiene: $text"
fi
}
assert_out_regex_count() {
local regex="$1"
local expected="$2"
local count
count="$(grep -Ec -- "$regex" "$CASE_OUT" || true)"
if [ "$count" -ne "$expected" ]; then
fail "$CASE_NAME -> regex [$regex] esperado $expected, obtenido $count"
fi
}
run_case() {
local name="$1"
local input="$2"
local expected_exit="$3"
local clean_env="${4:-0}"
CASE_NAME="$name"
CASE_OUT="$TMP_DIR/$name.out"
CASE_ERR="$TMP_DIR/$name.err"
CASE_EC=0
CASE_FAILED=0
if [ "$clean_env" -eq 1 ]; then
printf "%b" "$input" | env -i PATH="$PATH" "$MSH_BIN" >"$CASE_OUT" 2>"$CASE_ERR"
else
printf "%b" "$input" | "$MSH_BIN" >"$CASE_OUT" 2>"$CASE_ERR"
fi
CASE_EC=$?
assert_exit_code "$expected_exit"
if [ "$CASE_FAILED" -eq 0 ]; then
pass "$CASE_NAME"
fi
}
run_pwd_deleted_dir_case() {
local tmpd
tmpd="$TMP_DIR/pwd_deleted_dir"
mkdir -p "$tmpd"
run_case "pwd_deleted_dir" \
"cd $tmpd\n/bin/rmdir $tmpd\npwd\nexit\n" \
1
assert_err_contains "minishell: pwd:"
if [ "$CASE_FAILED" -eq 0 ]; then
pass "$CASE_NAME assertions"
fi
}
trap cleanup EXIT
if [ ! -x "$MSH_BIN" ]; then
printf "Compilando minishell...\n"
if ! make -C "$ROOT_DIR" >/dev/null; then
printf "No se pudo compilar minishell\n"
exit 1
fi
fi
run_case "exit_non_numeric" "exit abc\n" 2
assert_err_contains "numeric argument required"
if [ "$CASE_FAILED" -eq 0 ]; then
pass "$CASE_NAME assertions"
fi
run_case "exit_sign_only" "exit +\n" 2
assert_err_contains "numeric argument required"
if [ "$CASE_FAILED" -eq 0 ]; then
pass "$CASE_NAME assertions"
fi
run_case "exit_negative_wrap" "exit -1\n" 255
run_case "exit_too_many_args" "exit 7 8\npwd\nexit\n" 0
assert_err_contains "too many arguments"
assert_out_contains "$ROOT_DIR"
if [ "$CASE_FAILED" -eq 0 ]; then
pass "$CASE_NAME assertions"
fi
run_case "cd_home_not_set" "cd\nexit\n" 1 1
assert_err_contains "HOME not set"
if [ "$CASE_FAILED" -eq 0 ]; then
pass "$CASE_NAME assertions"
fi
run_case "cd_invalid_path" "cd /definitely/not/here\nexit\n" 1
assert_err_contains "No such file or directory"
if [ "$CASE_FAILED" -eq 0 ]; then
pass "$CASE_NAME assertions"
fi
run_case "cd_dash_roundtrip" "cd /tmp\ncd -\npwd\nexit\n" 0
assert_out_contains "$ROOT_DIR"
if [ "$CASE_FAILED" -eq 0 ]; then
pass "$CASE_NAME assertions"
fi
run_pwd_deleted_dir_case
run_case "echo_flags" "echo -nn hi\necho hi-there\nexit\n" 0
assert_out_contains "himinishell >"
assert_out_contains "hi-there"
if [ "$CASE_FAILED" -eq 0 ]; then
pass "$CASE_NAME assertions"
fi
run_case "env_builtin_with_arg" "env EXTRA\nexit\n" 1
assert_err_contains "too many arguments"
if [ "$CASE_FAILED" -eq 0 ]; then
pass "$CASE_NAME assertions"
fi
run_case "export_unset_cycle" \
"export FOO=bar\n/usr/bin/env\nunset FOO\n/usr/bin/env\nexit\n" \
0
assert_out_regex_count "^FOO=bar$" 1
if [ "$CASE_FAILED" -eq 0 ]; then
pass "$CASE_NAME assertions"
fi
run_case "env_builtin_lists_exported" \
"export FOO=bar\nenv\nunset FOO\nenv\nexit\n" \
0
assert_out_regex_count "^FOO=bar$" 1
if [ "$CASE_FAILED" -eq 0 ]; then
pass "$CASE_NAME assertions"
fi
run_case "export_invalid_identifier" "export 1A=2\nexit\n" 1
assert_err_contains "not a valid identifier"
if [ "$CASE_FAILED" -eq 0 ]; then
pass "$CASE_NAME assertions"
fi
run_case "unset_invalid_identifier" "unset 1A\nexit\n" 1
assert_err_contains "not a valid identifier"
if [ "$CASE_FAILED" -eq 0 ]; then
pass "$CASE_NAME assertions"
fi
run_case "export_without_value" "unset ZZZ\nexport ZZZ\n/usr/bin/env\nexit\n" 0
assert_out_regex_count "^ZZZ=$" 1
if [ "$CASE_FAILED" -eq 0 ]; then
pass "$CASE_NAME assertions"
fi
printf "\nResumen tests builtins: PASS=%d FAIL=%d\n" "$PASS_COUNT" "$FAIL_COUNT"
if [ "$FAIL_COUNT" -ne 0 ]; then
exit 1
fi
exit 0