diff --git a/docs/builtins_fixes.md b/docs/builtins_fixes.md new file mode 100644 index 0000000..759d7dc --- /dev/null +++ b/docs/builtins_fixes.md @@ -0,0 +1,151 @@ +# Correcciones en Builtins + +Este documento resume los fallos detectados en los builtins actuales y la +solucion aplicada en codigo. + +## 1) Infraestructura de builtins (`include/builtins.h`, `src/builtins/builtins.c`) + +### Fallo +- Uso inconsistente de tipos (`u_int8_t`/`unsigned char` frente a `uint8_t`). +- `set_builtins` no comprobaba fallos de registro por builtin (duplicado de + clave o insercion en hashmap), pudiendo dejar estado parcial. +- `is_builtin` podia dereferenciar punteros nulos. + +### Por que fallaba +- `u_int8_t` no es el tipo estandar C99 y depende de plataforma/headers. +- Si falla una insercion, la tabla quedaba inicializada parcialmente sin + rollback. +- En errores de inicializacion, consultar `is_builtin` podia romper. + +### Solucion +- Unificacion de firmas a `uint8_t`. +- Nuevo helper `register_builtin()` con validacion tras `ft_hashmap_put`. +- Si falla cualquier alta: limpieza de `minishell->builtins` y retorno de + error. +- Guardas nulas en `is_builtin`. + +## 2) `cd` (`src/builtins/cd/cd.c`) + +### Fallo +- `cd` con demasiados argumentos devolvia `2` (bash devuelve `1`). +- `cd` sin `HOME` acababa llamando a `chdir(NULL)`. +- El manejo de error usaba comprobaciones invertidas (`access`) y codigos + incorrectos. +- No se actualizaban `PWD` y `OLDPWD` tras `chdir` exitoso. +- `cd -` (usar `OLDPWD`) no estaba soportado. + +### Por que fallaba +- Codigos de salida incompatibles con el comportamiento esperado del shell. +- `HOME` no definido no se controlaba antes del `chdir`. +- La logica de `access` estaba al reves y mezclaba condiciones. +- Variables de entorno del directorio quedaban desincronizadas. +- Faltaba resolver el caso especial de `-` hacia `OLDPWD`. + +### Solucion +- Refactor en `resolve_cd_path()` para validar argumentos y `HOME`. +- Retorno `EXIT_FAILURE` en `too many arguments` y `HOME not set`. +- Error de `chdir` simplificado a `perror("minishell: cd")` + retorno `1`. +- Actualizacion de `OLDPWD` y `PWD` mediante `getcwd(NULL, 0)` + `set_env()`. +- Soporte de `cd -`: usa `OLDPWD`, valida `OLDPWD not set` e imprime el nuevo + directorio tras el cambio. + +## 3) `exit` (`src/builtins/exit/exit.c`) + +### Fallo +- Habia un `printf` de debug en ejecucion real. +- `exit ` devolvia `2` pero no cerraba el shell. +- `exit n m` devolvia `2`; en bash es `1` y no sale del shell. +- Validacion numerica basada en `ft_strisnum` sin control de overflow. +- Se mostraba `exit` incluso en contexto no interactivo. + +### Por que fallaba +- Debug residual contamina salida. +- Semantica de `exit` incompleta respecto a bash. +- Valores fuera de rango podian tratarse como validos por conversion directa. +- Mensaje `exit` debe mostrarse solo en shell interactivo. + +### Solucion +- Eliminado debug print. +- Nuevo flujo `resolve_exit_status()`: + - Sin argumentos: usa `msh->exit_status`. + - Argumento no numerico o fuera de `long`: mensaje + `numeric argument required`, `msh->exit = true`, estado `2`. + - Demasiados argumentos: mensaje de error y estado `1`, sin salir. +- Parser numerico propio (`get_uint8_from_num` + `has_overflow`) con soporte + de signo y control de overflow. +- `ft_eputendl("exit")` solo si `isatty(STDIN_FILENO)`. + +## 4) `pwd` (`src/builtins/pwd/pwd.c`) + +### Fallo +- Si `getcwd` fallaba, el builtin devolvia `EXIT_SUCCESS`. +- Uso de buffer fijo (`PATH_MAX`) menos robusto para rutas largas. + +### Por que fallaba +- El shell reportaba exito aunque no pudiera obtener el directorio. +- Un buffer fijo puede truncar o fallar en escenarios de rutas profundas. + +### Solucion +- Cambio a `getcwd(NULL, 0)` con memoria dinamica. +- Si falla, `perror("minishell: pwd")` y retorno `EXIT_FAILURE`. +- `free()` del buffer dinamico tras imprimir. + +## 5) `echo` (`src/builtins/echo/echo.c`, `src/builtins/echo/echo_def.c`) + +### Cambio aplicado +- Ajuste de tipos de retorno auxiliares a `uint8_t` para mantener consistencia + con `builtins.h`. + +### Nota +- No se detectaron fallos funcionales criticos adicionales en la logica actual + de `echo` durante esta revision. + +## 6) Builtins faltantes: `env`, `export`, `unset` + +### Fallo +- Los builtins `env`, `export` y `unset` no estaban implementados ni + registrados en `set_builtins`. + +### Por que fallaba +- Comandos basicos de shell no existian en la tabla de builtins. +- Cualquier prueba/flujo que dependiera de gestion de variables exportadas + fallaba (listar, crear y eliminar variables de entorno). + +### Solucion +- Nuevos builtins implementados: + - `src/builtins/env/env.c` + - `src/builtins/export/export.c` + - `src/builtins/unset/unset.c` +- Registro en `src/builtins/builtins.c` (tabla ampliada de 4 a 7 entradas). +- Nuevos prototipos en `include/builtins.h`. +- Soporte de borrado real de entorno mediante `unset_env`: + - Declaracion en `include/core.h` + - Implementacion en `src/variables/environment_unset.c` + +## 7) Comportamiento aplicado en los nuevos builtins + +### `env` +- Si recibe argumentos, devuelve error (`minishell: env: too many arguments`) + y estado `1`. +- Sin argumentos, lista `KEY=VALUE` de las variables de entorno actuales. + +### `export` +- `export NAME=VALUE`: crea/actualiza la variable. +- `export NAME`: crea/actualiza con valor vacio (`NAME=`) para que aparezca + en `env`. +- Identificadores invalidos (`1A=2`, etc.) devuelven error + `not a valid identifier` y estado `1`. +- Sin argumentos, reutiliza el listado de `env`. + +### `unset` +- Elimina variables validas del entorno en memoria. +- Identificadores invalidos devuelven error + `not a valid identifier` y estado `1`. +- Si el identificador es valido y no existe, no falla (comportamiento shell). + +## Validacion realizada + +- `norminette` ejecutado sobre los archivos modificados: `OK`. +- Build completa no ejecutable en este entorno por falta de acceso de red al + clonado de librerias (`github.com`), por lo que no se pudo validar runtime + del binario en esta sesion. diff --git a/include/builtins.h b/include/builtins.h index c8dac83..01ae629 100644 --- a/include/builtins.h +++ b/include/builtins.h @@ -17,7 +17,7 @@ # include "minishell.h" # include "core.h" -typedef unsigned char (*t_builtin_func)(t_command cmd, t_minishell *minishell); +typedef uint8_t (*t_builtin_func)(t_command cmd, t_minishell *minishell); /******************************************************************************/ /* Functions */ @@ -25,24 +25,36 @@ typedef unsigned char (*t_builtin_func)(t_command cmd, t_minishell *minishell) /* builtins.c */ -extern u_int8_t set_builtins(t_minishell *minishell); +extern uint8_t set_builtins(t_minishell *minishell); -extern u_int8_t is_builtin(const char *command_name, t_minishell *minishell); +extern uint8_t is_builtin(const char *command_name, t_minishell *minishell); /* cd.c */ -extern u_int8_t builtin_cd(t_command cmd, t_minishell *minishell); +extern uint8_t builtin_cd(t_command cmd, t_minishell *minishell); /* echo.c */ -extern u_int8_t builtin_echo(t_command cmd, t_minishell *minishell); +extern uint8_t builtin_echo(t_command cmd, t_minishell *minishell); /* exit.c */ -extern u_int8_t builtin_exit(t_command cmd, t_minishell *minishell); +extern uint8_t builtin_exit(t_command cmd, t_minishell *minishell); /* pwd.c */ -extern u_int8_t builtin_pwd(t_command cmd, t_minishell *minishell); +extern uint8_t builtin_pwd(t_command cmd, t_minishell *minishell); + +/* env.c */ + +extern uint8_t builtin_env(t_command cmd, t_minishell *minishell); + +/* export.c */ + +extern uint8_t builtin_export(t_command cmd, t_minishell *minishell); + +/* unset.c */ + +extern uint8_t builtin_unset(t_command cmd, t_minishell *minishell); #endif /* BUILTINS_H */ diff --git a/include/core.h b/include/core.h index 3e568ae..14cb4fd 100644 --- a/include/core.h +++ b/include/core.h @@ -107,4 +107,6 @@ extern void free_envp(char **envp); extern char *get_env(const char *env_name, t_minishell *msh); +extern void unset_env(const char *env_name, t_minishell *msh); + #endif /* CORE_H */ diff --git a/src/builtins/builtins.c b/src/builtins/builtins.c index 0d8c89b..3dd536b 100644 --- a/src/builtins/builtins.c +++ b/src/builtins/builtins.c @@ -12,23 +12,53 @@ #include "builtins.h" -u_int8_t set_builtins( - t_minishell *minishell -) { - minishell->builtins - = ft_hashmap_new(4, ft_hashmap_hashstr, ft_hashmap_strcmp); - if (minishell->builtins == NULL) +static uint8_t register_builtin( + t_minishell *minishell, + const char *name, + t_builtin_func builtin +) +{ + char *key; + + key = ft_strdup(name); + if (key == NULL) return (0); - ft_hashmap_put(minishell->builtins, ft_strdup("cd"), builtin_cd); - ft_hashmap_put(minishell->builtins, ft_strdup("echo"), builtin_echo); - ft_hashmap_put(minishell->builtins, ft_strdup("exit"), builtin_exit); - ft_hashmap_put(minishell->builtins, ft_strdup("pwd"), builtin_pwd); + ft_hashmap_put(minishell->builtins, key, builtin); + if (!ft_hashmap_contains_key(minishell->builtins, name)) + { + free(key); + return (0); + } return (1); } -u_int8_t is_builtin( +uint8_t set_builtins( + t_minishell *minishell +) { + minishell->builtins + = ft_hashmap_new(7, ft_hashmap_hashstr, ft_hashmap_strcmp); + if (minishell->builtins == NULL) + return (0); + if (!register_builtin(minishell, "cd", builtin_cd) + || !register_builtin(minishell, "echo", builtin_echo) + || !register_builtin(minishell, "env", builtin_env) + || !register_builtin(minishell, "exit", builtin_exit) + || !register_builtin(minishell, "export", builtin_export) + || !register_builtin(minishell, "pwd", builtin_pwd) + || !register_builtin(minishell, "unset", builtin_unset)) + { + ft_hashmap_clear_keys(&minishell->builtins); + return (0); + } + return (1); +} + +uint8_t is_builtin( const char *command_name, t_minishell *minishell ) { + if (command_name == NULL || minishell == NULL + || minishell->builtins == NULL) + return (0); return (ft_hashmap_contains_key(minishell->builtins, command_name)); } diff --git a/src/builtins/cd/cd.c b/src/builtins/cd/cd.c index 2f508de..04c1ebd 100644 --- a/src/builtins/cd/cd.c +++ b/src/builtins/cd/cd.c @@ -12,43 +12,104 @@ #include "builtins.h" -static u_int8_t handle_error(t_command cmd, t_minishell *msh, char *path); +static uint8_t handle_error(void); +static void update_pwd_vars(t_minishell *msh, char *oldpwd); +static char *get_path_from_env( + t_minishell *msh, + const char *env_name, + const char *error, + uint8_t *status + ); +static char *resolve_cd_path( + t_command cmd, + t_minishell *msh, + uint8_t *status + ); -u_int8_t builtin_cd( +uint8_t builtin_cd( t_command cmd, t_minishell *msh ){ char *path; + char *oldpwd; + uint8_t status; + bool show_pwd; - if (cmd.argc > 2) - { - ft_eputendl("minishell: cd: too many arguments"); - return (2); - } - else if (cmd.argc == 1) - path = get_env("HOME", msh); - else - path = cmd.argv[1]; + path = resolve_cd_path(cmd, msh, &status); + if (status != EXIT_SUCCESS) + return (status); + show_pwd = (cmd.argc == 2 && ft_strcmp(cmd.argv[1], "-") == 0); + oldpwd = getcwd(NULL, 0); if (chdir(path) == -1) - return (handle_error(cmd, msh, path)); + { + free(oldpwd); + return (handle_error()); + } + update_pwd_vars(msh, oldpwd); + if (show_pwd && get_env("PWD", msh) != NULL) + ft_putendl(get_env("PWD", msh)); + free(oldpwd); return (EXIT_SUCCESS); } -static u_int8_t handle_error( +static uint8_t handle_error(void) +{ + perror("minishell: cd"); + return (EXIT_FAILURE); +} + +static void update_pwd_vars( + t_minishell *msh, + char *oldpwd +){ + char *newpwd; + + if (oldpwd != NULL) + set_env("OLDPWD", oldpwd, msh); + newpwd = getcwd(NULL, 0); + if (newpwd != NULL) + { + set_env("PWD", newpwd, msh); + free(newpwd); + } +} + +static char *resolve_cd_path( t_command cmd, t_minishell *msh, - char *path + uint8_t *status ){ - u_int8_t exit_code; - - (void)msh; - exit_code = 0; - if (access(path, F_OK) != -1) - // No such file or directory - exit_code = 1; - else if (access(path, X_OK) == -1) - // Permission denied - exit_code = 2; - perror(cmd.argv[0]); - return (exit_code); + if (cmd.argc > 2) + { + ft_eputendl("minishell: cd: too many arguments"); + *status = EXIT_FAILURE; + return (NULL); + } + if (cmd.argc == 2 && ft_strcmp(cmd.argv[1], "-") == 0) + return (get_path_from_env(msh, "OLDPWD", + "minishell: cd: OLDPWD not set", status)); + if (cmd.argc == 1) + return (get_path_from_env(msh, "HOME", + "minishell: cd: HOME not set", status)); + *status = EXIT_SUCCESS; + return (cmd.argv[1]); +} + +static char *get_path_from_env( + t_minishell *msh, + const char *env_name, + const char *error, + uint8_t *status +){ + char *path; + + path = get_env(env_name, msh); + if (path == NULL) + { + ft_eputendl(error); + *status = EXIT_FAILURE; + return (NULL); + } + *status = EXIT_SUCCESS; + return (path); } diff --git a/src/builtins/echo/echo.c b/src/builtins/echo/echo.c index 5a8a729..567235f 100644 --- a/src/builtins/echo/echo.c +++ b/src/builtins/echo/echo.c @@ -13,7 +13,7 @@ #include "builtins.h" #include "echo_def.h" -u_int8_t builtin_echo( +uint8_t builtin_echo( t_command cmd, t_minishell *msh ){ diff --git a/src/builtins/echo/echo_def.c b/src/builtins/echo/echo_def.c index fd60d0d..f5e5f65 100644 --- a/src/builtins/echo/echo_def.c +++ b/src/builtins/echo/echo_def.c @@ -13,7 +13,7 @@ #include "echo_def.h" static void assign_flags(t_args *args, t_command cmd, size_t *i); -static u_int8_t is_valid_flag(t_args *args, char *flag); +static uint8_t is_valid_flag(t_args *args, char *flag); static void assign_default_flags( t_args *args @@ -51,7 +51,7 @@ static void assign_flags( } } -static u_int8_t is_valid_flag( +static uint8_t is_valid_flag( t_args *args, char *flag ){ diff --git a/src/builtins/env/env.c b/src/builtins/env/env.c new file mode 100644 index 0000000..daae306 --- /dev/null +++ b/src/builtins/env/env.c @@ -0,0 +1,58 @@ +/* ************************************************************************** */ +/* */ +/* ::: :::::::: */ +/* env.c :+: :+: :+: */ +/* +:+ +:+ +:+ */ +/* By: sede-san 1) + { + ft_eputendl("minishell: env: too many arguments"); + return (EXIT_FAILURE); + } + print_env(msh); + return (EXIT_SUCCESS); +} + +static void print_entry( + t_map_entry *entry +) +{ + ft_putstr(entry->key); + ft_putchar('='); + if (entry->value != NULL) + ft_putstr(entry->value); + ft_putchar('\n'); +} + +static void print_env( + t_minishell *msh +) +{ + t_list *entries; + t_list *current; + + entries = ft_hashmap_entries(msh->variables.environment); + current = entries; + while (current != NULL) + { + print_entry((t_map_entry *)current->content); + current = current->next; + } + ft_lstclear_nodes(&entries); +} diff --git a/src/builtins/exit/exit.c b/src/builtins/exit/exit.c index 9d32e0d..a2aff19 100644 --- a/src/builtins/exit/exit.c +++ b/src/builtins/exit/exit.c @@ -11,7 +11,19 @@ /* ************************************************************************** */ #include "builtins.h" -#include +#include + +static uint8_t get_uint8_from_num(const char *arg, uint8_t *status); +static uint8_t has_overflow( + uint64_t n, + char digit, + uint64_t limit + ); +static uint8_t resolve_exit_status( + t_command cmd, + t_minishell *msh, + uint8_t *exit_status + ); uint8_t builtin_exit( t_command cmd, @@ -19,28 +31,78 @@ uint8_t builtin_exit( ) { uint8_t exit_status; - - ft_eputendl("exit"); - if (cmd.argc == 1) - exit_status = msh->exit_status; - else if (!ft_strisnum(cmd.argv[1])) - { - ft_eprintf( - "minishell: exit: %s: numeric argument required\n", - cmd.argv[1]); - return (2); - } - else if (cmd.argc > 2) - { - ft_eputendl("exit: too many arguments"); - return (2); - } - else - exit_status = (uint8_t)ft_atol(cmd.argv[1]); - printf("builtin_exit: exit_status=%d\n", exit_status); // Debug print - - msh->exit = 1; + if (isatty(STDIN_FILENO)) + ft_eputendl("exit"); + if (!resolve_exit_status(cmd, msh, &exit_status)) + return (msh->exit_status); + msh->exit = true; msh->exit_status = exit_status; return (exit_status); } + +static uint8_t resolve_exit_status( + t_command cmd, + t_minishell *msh, + uint8_t *exit_status +){ + if (cmd.argc == 1) + *exit_status = msh->exit_status; + else if (!get_uint8_from_num(cmd.argv[1], exit_status)) + { + ft_eprintf("minishell: exit: %s: numeric argument required\n", + cmd.argv[1]); + msh->exit = true; + msh->exit_status = 2; + return (0); + } + else if (cmd.argc > 2) + { + ft_eputendl("minishell: exit: too many arguments"); + msh->exit_status = EXIT_FAILURE; + return (0); + } + return (1); +} + +static uint8_t get_uint8_from_num( + const char *arg, + uint8_t *status +){ + uint64_t n; + uint64_t limit; + int sign; + + if (arg == NULL || *arg == '\0') + return (0); + n = 0; + sign = 1; + if (*arg == '+' || *arg == '-') + if (*arg++ == '-') + sign = -1; + if (*arg == '\0') + return (0); + limit = LONG_MAX; + if (sign == -1) + limit = (uint64_t)LONG_MAX + 1; + while (*arg != '\0') + { + if (!ft_isdigit(*arg) || has_overflow(n, *arg, limit)) + return (0); + n = (n * 10) + (*arg++ - '0'); + } + *status = (uint8_t)(n * sign); + return (1); +} + +static uint8_t has_overflow( + uint64_t n, + char digit, + uint64_t limit +){ + if (n > (limit / 10)) + return (1); + if (n == (limit / 10) && (uint64_t)(digit - '0') > (limit % 10)) + return (1); + return (0); +} diff --git a/src/builtins/export/export.c b/src/builtins/export/export.c new file mode 100644 index 0000000..e082ea7 --- /dev/null +++ b/src/builtins/export/export.c @@ -0,0 +1,79 @@ +/* ************************************************************************** */ +/* */ +/* ::: :::::::: */ +/* export.c :+: :+: :+: */ +/* +:+ +:+ +:+ */ +/* By: sede-san variables.environment); - envp = (char **)malloc((msh->variables.environment->size + 1) * sizeof(char *)); + envp = (char **)malloc( + (msh->variables.environment->size + 1) * sizeof(char *) + ); if (envp != NULL) { i = 0; diff --git a/src/variables/environment_unset.c b/src/variables/environment_unset.c new file mode 100644 index 0000000..feae147 --- /dev/null +++ b/src/variables/environment_unset.c @@ -0,0 +1,41 @@ +/* ************************************************************************** */ +/* */ +/* ::: :::::::: */ +/* environment_unset.c :+: :+: :+: */ +/* +:+ +:+ +:+ */ +/* By: sede-san variables.environment); + current = entries; + while (current != NULL) + { + entry = current->content; + if (ft_strcmp(entry->key, env_name) != 0) + ft_hashmap_put(new_env, + ft_strdup(entry->key), ft_strdup(entry->value)); + current = current->next; + } + ft_lstclear_nodes(&entries); + ft_hashmap_clear(&msh->variables.environment, free); + msh->variables.environment = new_env; +} diff --git a/tests/builtins_edge_cases.sh b/tests/builtins_edge_cases.sh new file mode 100755 index 0000000..f0e6477 --- /dev/null +++ b/tests/builtins_edge_cases.sh @@ -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