diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..95ec3db --- /dev/null +++ b/.clang-format @@ -0,0 +1,9 @@ +BasedOnStyle: LLVM +IndentWidth: 4 +ColumnLimit: 100 +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +BreakBeforeBraces: Attach +PointerAlignment: Right +SpaceAfterCStyleCast: true +SpaceBeforeParens: ControlStatements diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 0000000..0269e54 --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,20 @@ +Checks: > + bugprone-*, + clang-analyzer-*, + performance-*, + portability-*, + readability-*, + -readability-identifier-length, + -readability-magic-numbers, + -readability-isolate-declaration, + -readability-function-cognitive-complexity, + -bugprone-multi-level-implicit-pointer-conversion, + -bugprone-easily-swappable-parameters, + -bugprone-branch-clone, + -bugprone-implicit-widening-of-multiplication-result, + -clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling, + -clang-analyzer-core.NonNullParamChecker, + -clang-analyzer-security.ArrayBound, + -clang-analyzer-unix.StdCLibraryFunctions, + -clang-analyzer-core.NullDereference +WarningsAsErrors: '' diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..641f661 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,35 @@ +name: CI + +on: + pull_request: + push: + branches: [main] + +jobs: + build-and-test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install cmake + run: | + sudo apt-get update + sudo apt-get install -y cmake + - name: Build and test + run: ./run_tests.sh + + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install lint tools + run: | + sudo apt-get update + sudo apt-get install -y clang-format clang-tidy + - name: clang-format (dry-run) + run: | + clang-format --dry-run --Werror $(find src include tests -name '*.c' -o -name '*.h') + - name: Configure for clang-tidy + run: cmake -B cmake-build-debug -S . -DCMAKE_EXPORT_COMPILE_COMMANDS=ON + - name: clang-tidy + run: | + clang-tidy -p cmake-build-debug -warnings-as-errors=* $(find src include -name '*.c') diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..5e19c74 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,28 @@ +name: Release + +on: + push: + tags: + - 'v*.*.*' + +permissions: + contents: write + +jobs: + release: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install cmake + run: | + sudo apt-get update + sudo apt-get install -y cmake + - name: Build (Release) + run: | + cmake -B build -S . -DCMAKE_BUILD_TYPE=Release + cmake --build build + - name: Create release + uses: softprops/action-gh-release@v2 + with: + files: build/shell + generate_release_notes: true diff --git a/README.md b/README.md index 7b817a5..4019209 100644 --- a/README.md +++ b/README.md @@ -1,56 +1,58 @@ -# Trigger - -## Overview - -This project aims to create a clean, efficient shell. - -## Building - -```bash -cmake -B cmake-build-debug -S . -cmake --build cmake-build-debug -``` - -## Running - -```bash -./cmake-build-debug/shell -``` - -## Testing - -The project includes comprehensive unit tests for all modules. To run the tests: - -```bash -# Quick way -./run_tests.sh - -# Or manually -cd cmake-build-debug -ctest --verbose -``` - -All tests use a custom lightweight testing framework that provides colored output and clear assertion messages. - -## Goals - -- Develop a functional, well‑structured shell. -- Ensure the shell is performant and easy to use. -- Continuously improve the project as I learn more. -- Maintain high code quality with unit tests. - -## Inspiration - -I’ll start by adapting the simple shell tutorial from Brennan Baker: - - - -This will serve as a solid foundation, which I'll later expand and customize. The resulting shell, named **Trigger**, will combine the tutorial's core ideas with additional features and optimizations. - -## Development - -Feature development is a collaboration between the project author and AI tooling: the -author reviews, directs, and integrates AI-generated contributions alongside -hand-written logic. All changes undergo code review before landing. - -Feel free to explore the repository, submit issues, or contribute improvements. +# Trigger + +[![CI](https://github.com/melogtm/trigger/actions/workflows/ci.yml/badge.svg)](https://github.com/melogtm/trigger/actions/workflows/ci.yml) + +## Overview + +This project aims to create a clean, efficient shell. + +## Building + +```bash +cmake -B cmake-build-debug -S . +cmake --build cmake-build-debug +``` + +## Running + +```bash +./cmake-build-debug/shell +``` + +## Testing + +The project includes comprehensive unit tests for all modules. To run the tests: + +```bash +# Quick way +./run_tests.sh + +# Or manually +cd cmake-build-debug +ctest --verbose +``` + +All tests use a custom lightweight testing framework that provides colored output and clear assertion messages. + +## Goals + +- Develop a functional, well‑structured shell. +- Ensure the shell is performant and easy to use. +- Continuously improve the project as I learn more. +- Maintain high code quality with unit tests. + +## Inspiration + +I’ll start by adapting the simple shell tutorial from Brennan Baker: + + + +This will serve as a solid foundation, which I'll later expand and customize. The resulting shell, named **Trigger**, will combine the tutorial's core ideas with additional features and optimizations. + +## Development + +Feature development is a collaboration between the project author and AI tooling: the +author reviews, directs, and integrates AI-generated contributions alongside +hand-written logic. All changes undergo code review before landing. + +Feel free to explore the repository, submit issues, or contribute improvements. diff --git a/include/builtins.h b/include/builtins.h index a6e6465..8242cab 100644 --- a/include/builtins.h +++ b/include/builtins.h @@ -1,16 +1,16 @@ -#ifndef BUILTINS_H -#define BUILTINS_H - -int trigger_cd(char **args); -int trigger_help(char **args); -int trigger_exit(char **args); -int trigger_pwd(char **args); -int trigger_echo(char **args); -int trigger_export(char **args); -int trigger_unset(char **args); -int trigger_num_builtins(void); - -extern char *builtin_str[]; -extern int (*builtin_func[])(char **); - -#endif +#ifndef BUILTINS_H +#define BUILTINS_H + +int trigger_cd(char **args); +int trigger_help(char **args); +int trigger_exit(char **args); +int trigger_pwd(char **args); +int trigger_echo(char **args); +int trigger_export(char **args); +int trigger_unset(char **args); +int trigger_num_builtins(void); + +extern char *builtin_str[]; +extern int (*builtin_func[])(char **); + +#endif diff --git a/include/execute.h b/include/execute.h index f50d220..14b16b2 100644 --- a/include/execute.h +++ b/include/execute.h @@ -1,11 +1,10 @@ -#ifndef EXECUTE_H -#define EXECUTE_H - -#define CHILD_PROCESS_EXITED 0 -#define EXEC_RETURNED_FAILURE (-1) - -int trigger_launch(char **args); -int trigger_execute(char ***args_ptr, int **glob_eligible_ptr); - -#endif - +#ifndef EXECUTE_H +#define EXECUTE_H + +#define CHILD_PROCESS_EXITED 0 +#define EXEC_RETURNED_FAILURE (-1) + +int trigger_launch(char **args); +int trigger_execute(char ***args_ptr, int **glob_eligible_ptr); + +#endif diff --git a/include/glob_expand.h b/include/glob_expand.h index e63e21e..b490e8a 100644 --- a/include/glob_expand.h +++ b/include/glob_expand.h @@ -1,6 +1,6 @@ -#ifndef GLOB_EXPAND_H -#define GLOB_EXPAND_H - -char **expand_globs(char **argv, int **glob_eligible_ptr); - -#endif +#ifndef GLOB_EXPAND_H +#define GLOB_EXPAND_H + +char **expand_globs(char **argv, int **glob_eligible_ptr); + +#endif diff --git a/include/input.h b/include/input.h index 4fdfb2d..67dfa2a 100644 --- a/include/input.h +++ b/include/input.h @@ -1,13 +1,12 @@ -#ifndef INPUT_H -#define INPUT_H - -#define TRIGGER_AUTOMATIC_BUFFER_SIZE 0 -#define GET_LINE_REACHED_END_OF_FILE_OR_ERROR (-1) -#define TRIGGER_TOK_DELIM " \t\r\n\a" - -char *trigger_read_line(void); -char **trigger_split_line(const char *line); -char **trigger_split_line_ex(const char *line, int **out_glob_eligible); - -#endif - +#ifndef INPUT_H +#define INPUT_H + +#define TRIGGER_AUTOMATIC_BUFFER_SIZE 0 +#define GET_LINE_REACHED_END_OF_FILE_OR_ERROR (-1) +#define TRIGGER_TOK_DELIM " \t\r\n\a" + +char *trigger_read_line(void); +char **trigger_split_line(const char *line); +char **trigger_split_line_ex(const char *line, int **out_glob_eligible); + +#endif diff --git a/include/pipeline.h b/include/pipeline.h index 2e8e216..7d2105d 100644 --- a/include/pipeline.h +++ b/include/pipeline.h @@ -1,14 +1,14 @@ -#ifndef PIPELINE_H -#define PIPELINE_H - -typedef struct { - char **argv; - char *infile; - char *outfile; - int append; -} PipelineStage; - -PipelineStage *trigger_parse_pipeline(char **tokens, int *num_stages); -int trigger_execute_pipeline(PipelineStage *stages, int num_stages); - -#endif +#ifndef PIPELINE_H +#define PIPELINE_H + +typedef struct { + char **argv; + char *infile; + char *outfile; + int append; +} PipelineStage; + +PipelineStage *trigger_parse_pipeline(char **tokens, int *num_stages); +int trigger_execute_pipeline(PipelineStage *stages, int num_stages); + +#endif diff --git a/src/builtins.c b/src/builtins.c index 4bc4be8..26f5265 100644 --- a/src/builtins.c +++ b/src/builtins.c @@ -1,94 +1,91 @@ -#include -#include -#include -#include -#include "builtins.h" -#include "utils/utils.h" - -char *builtin_str[] = {"cd", "help", "exit", "pwd", "echo", "export", "unset"}; - -int (*builtin_func[])(char **) = {&trigger_cd, &trigger_help, &trigger_exit, &trigger_pwd, &trigger_echo, &trigger_export, &trigger_unset}; - -int trigger_num_builtins(void) { - return sizeof(builtin_str) / sizeof(char *); -} - -int trigger_cd(char **args) { - if (args[1] == NULL) { - fprintf(stderr, "trigger: expected argument to \"cd\"\n"); - } else { - if (chdir(args[1]) != EXIT_SUCCESS) { - perror("trigger"); - } - } - - return true; -} - -int trigger_help(char **args) { - printf("Trigger: A simple shell written in C\n"); - printf("Type program names and arguments, and hit enter.\n"); - printf("The following are built in:\n"); - - for (int i = 0; i < trigger_num_builtins(); i++) { - printf(" %s\n", builtin_str[i]); - } - - printf("Use the man command for information on other programs.\n"); - return true; -} - -int trigger_exit(char **args) { - return EXIT_SUCCESS; -} - -int trigger_pwd(char **args) { - char cwd[4096]; - - if (getcwd(cwd, sizeof(cwd)) == NULL) { - perror("trigger"); - return true; - } - - printf("%s\n", cwd); - return true; -} - -int trigger_echo(char **args) { - for (int i = 1; args[i] != NULL; i++) { - if (i > 1) { - printf(" "); - } - printf("%s", args[i]); - } - printf("\n"); - return true; -} - -int trigger_export(char **args) { - for (int i = 1; args[i] != NULL; i++) { - char *equals = strchr(args[i], '='); - - if (equals == NULL) { - continue; - } - - char *name = strndup(args[i], equals - args[i]); - - if (name == NULL) { - perror("trigger"); - continue; - } - - setenv(name, equals + 1, 1); - free(name); - } - return true; -} - -int trigger_unset(char **args) { - for (int i = 1; args[i] != NULL; i++) { - unsetenv(args[i]); - } - return true; -} +#include "builtins.h" +#include "utils/utils.h" +#include +#include +#include +#include + +char *builtin_str[] = {"cd", "help", "exit", "pwd", "echo", "export", "unset"}; + +int (*builtin_func[])(char **) = {&trigger_cd, &trigger_help, &trigger_exit, &trigger_pwd, + &trigger_echo, &trigger_export, &trigger_unset}; + +int trigger_num_builtins(void) { return sizeof(builtin_str) / sizeof(char *); } + +int trigger_cd(char **args) { + if (args[1] == NULL) { + fprintf(stderr, "trigger: expected argument to \"cd\"\n"); + } else { + if (chdir(args[1]) != EXIT_SUCCESS) { + perror("trigger"); + } + } + + return true; +} + +int trigger_help(char **args) { + printf("Trigger: A simple shell written in C\n"); + printf("Type program names and arguments, and hit enter.\n"); + printf("The following are built in:\n"); + + for (int i = 0; i < trigger_num_builtins(); i++) { + printf(" %s\n", builtin_str[i]); + } + + printf("Use the man command for information on other programs.\n"); + return true; +} + +int trigger_exit(char **args) { return EXIT_SUCCESS; } + +int trigger_pwd(char **args) { + char cwd[4096]; + + if (getcwd(cwd, sizeof(cwd)) == NULL) { + perror("trigger"); + return true; + } + + printf("%s\n", cwd); + return true; +} + +int trigger_echo(char **args) { + for (int i = 1; args[i] != NULL; i++) { + if (i > 1) { + printf(" "); + } + printf("%s", args[i]); + } + printf("\n"); + return true; +} + +int trigger_export(char **args) { + for (int i = 1; args[i] != NULL; i++) { + char *equals = strchr(args[i], '='); + + if (equals == NULL) { + continue; + } + + char *name = strndup(args[i], equals - args[i]); + + if (name == NULL) { + perror("trigger"); + continue; + } + + setenv(name, equals + 1, 1); + free(name); + } + return true; +} + +int trigger_unset(char **args) { + for (int i = 1; args[i] != NULL; i++) { + unsetenv(args[i]); + } + return true; +} diff --git a/src/execute.c b/src/execute.c index c20fa64..8bcc880 100644 --- a/src/execute.c +++ b/src/execute.c @@ -1,76 +1,75 @@ -#include -#include -#include -#include -#include -#include "execute.h" -#include "builtins.h" -#include "glob_expand.h" -#include "pipeline.h" -#include "utils/utils.h" - -int trigger_launch(char **args) { - int status; - - const pid_t pid = fork(); - - if (pid == CHILD_PROCESS_EXITED) { - if (execvp(args[0], args) == EXEC_RETURNED_FAILURE) { - perror("trigger"); - } - exit(EXIT_FAILURE); - } - - if (pid < 0) { - perror("trigger"); - } else { - do { - waitpid(pid, &status, WUNTRACED); - } while (!WIFEXITED(status) && !WIFSIGNALED(status)); - } - - return true; -} - -int trigger_execute(char ***args_ptr, int **glob_eligible_ptr) { - char **args = *args_ptr; - int *glob_eligible = (glob_eligible_ptr != NULL) ? *glob_eligible_ptr : NULL; - - if (args == NULL || args[0] == NULL) { - return true; - } - - args = expand_globs(args, glob_eligible_ptr); - *args_ptr = args; - glob_eligible = (glob_eligible_ptr != NULL) ? *glob_eligible_ptr : NULL; - - int has_operator = 0; - - for (int i = 0; args[i] != NULL; i++) { - if (glob_eligible == NULL || glob_eligible[i]) { - if (strcmp(args[i], "|") == 0 || strcmp(args[i], "<") == 0 - || strcmp(args[i], ">") == 0 || strcmp(args[i], ">>") == 0) { - has_operator = 1; - break; - } - } - } - - if (has_operator) { - int num_stages = 0; - PipelineStage *stages = trigger_parse_pipeline(args, &num_stages); - int result = trigger_execute_pipeline(stages, num_stages); - - free(args); - *args_ptr = NULL; - return result; - } - - for (int i = 0; i < trigger_num_builtins(); i++) { - if (strcmp(args[0], builtin_str[i]) == 0) { - return (*builtin_func[i])(args); - } - } - - return trigger_launch(args); -} +#include "execute.h" +#include "builtins.h" +#include "glob_expand.h" +#include "pipeline.h" +#include "utils/utils.h" +#include +#include +#include +#include +#include + +int trigger_launch(char **args) { + int status; + + const pid_t pid = fork(); + + if (pid == CHILD_PROCESS_EXITED) { + if (execvp(args[0], args) == EXEC_RETURNED_FAILURE) { + perror("trigger"); + } + exit(EXIT_FAILURE); + } + + if (pid < 0) { + perror("trigger"); + } else { + do { + waitpid(pid, &status, WUNTRACED); + } while (!WIFEXITED(status) && !WIFSIGNALED(status)); + } + + return true; +} + +int trigger_execute(char ***args_ptr, int **glob_eligible_ptr) { + char **args = *args_ptr; + + if (args == NULL || args[0] == NULL) { + return true; + } + + args = expand_globs(args, glob_eligible_ptr); + *args_ptr = args; + int *glob_eligible = (glob_eligible_ptr != NULL) ? *glob_eligible_ptr : NULL; + + int has_operator = 0; + + for (int i = 0; args[i] != NULL; i++) { + if (glob_eligible == NULL || glob_eligible[i]) { + if (strcmp(args[i], "|") == 0 || strcmp(args[i], "<") == 0 || + strcmp(args[i], ">") == 0 || strcmp(args[i], ">>") == 0) { + has_operator = 1; + break; + } + } + } + + if (has_operator) { + int num_stages = 0; + PipelineStage *stages = trigger_parse_pipeline(args, &num_stages); + int result = trigger_execute_pipeline(stages, num_stages); + + free(args); + *args_ptr = NULL; + return result; + } + + for (int i = 0; i < trigger_num_builtins(); i++) { + if (strcmp(args[0], builtin_str[i]) == 0) { + return (*builtin_func[i])(args); + } + } + + return trigger_launch(args); +} diff --git a/src/glob.c b/src/glob.c index c25db35..1f7688b 100644 --- a/src/glob.c +++ b/src/glob.c @@ -1,110 +1,110 @@ -#include -#include -#include -#include -#include "glob_expand.h" - -static int has_metachars(const char *s) { - return strchr(s, '*') != NULL || strchr(s, '?') != NULL || strchr(s, '[') != NULL; -} - -static void grow_if_needed(char ***argv, int **eligible, int *capacity, int count) { - if (count < *capacity) { - return; - } - - int new_cap = *capacity * 2; - - char **tmp_a = realloc(*argv, (new_cap + 1) * sizeof(char *)); - int *tmp_e = realloc(*eligible, (new_cap + 1) * sizeof(int)); - - if (tmp_a == NULL || tmp_e == NULL) { - fprintf(stderr, "allocation error\n"); - exit(EXIT_FAILURE); - } - - *argv = tmp_a; - *eligible = tmp_e; - *capacity = new_cap; -} - -char **expand_globs(char **argv, int **glob_eligible_ptr) { - if (glob_eligible_ptr == NULL) { - return argv; - } - - int *glob_eligible = *glob_eligible_ptr; - - if (glob_eligible == NULL) { - return argv; - } - - int has_any = 0; - - for (int i = 0; argv[i] != NULL; i++) { - if (glob_eligible[i] && has_metachars(argv[i])) { - has_any = 1; - break; - } - } - - if (!has_any) { - return argv; - } - - int capacity = 8; - int count = 0; - char **new_argv = malloc((capacity + 1) * sizeof(char *)); - - if (new_argv == NULL) { - fprintf(stderr, "allocation error\n"); - exit(EXIT_FAILURE); - } - - int *new_eligible = malloc((capacity + 1) * sizeof(int)); - - if (new_eligible == NULL) { - fprintf(stderr, "allocation error\n"); - exit(EXIT_FAILURE); - } - - for (int i = 0; argv[i] != NULL; i++) { - if (glob_eligible[i] && has_metachars(argv[i])) { - glob_t g; - int r = glob(argv[i], GLOB_NOCHECK, NULL, &g); - - if (r == GLOB_NOSPACE) { - fprintf(stderr, "trigger: glob out of memory\n"); - globfree(&g); - grow_if_needed(&new_argv, &new_eligible, &capacity, count); - new_argv[count] = argv[i]; - new_eligible[count] = 0; - count++; - continue; - } - - for (size_t j = 0; j < g.gl_pathc; j++) { - grow_if_needed(&new_argv, &new_eligible, &capacity, count); - new_argv[count] = strdup(g.gl_pathv[j]); - new_eligible[count] = 0; - count++; - } - - free(argv[i]); - globfree(&g); - } else { - grow_if_needed(&new_argv, &new_eligible, &capacity, count); - new_argv[count] = argv[i]; - new_eligible[count] = glob_eligible[i]; - count++; - } - } - - new_argv[count] = NULL; - - free(argv); - free(glob_eligible); - - *glob_eligible_ptr = new_eligible; - return new_argv; -} +#include "glob_expand.h" +#include +#include +#include +#include + +static int has_metachars(const char *s) { + return strchr(s, '*') != NULL || strchr(s, '?') != NULL || strchr(s, '[') != NULL; +} + +static void grow_if_needed(char ***argv, int **eligible, int *capacity, int count) { + if (count < *capacity) { + return; + } + + int new_cap = *capacity * 2; + + char **tmp_a = realloc(*argv, (new_cap + 1) * sizeof(char *)); + int *tmp_e = realloc(*eligible, (new_cap + 1) * sizeof(int)); + + if (tmp_a == NULL || tmp_e == NULL) { + fprintf(stderr, "allocation error\n"); + exit(EXIT_FAILURE); + } + + *argv = tmp_a; + *eligible = tmp_e; + *capacity = new_cap; +} + +char **expand_globs(char **argv, int **glob_eligible_ptr) { + if (glob_eligible_ptr == NULL) { + return argv; + } + + int *glob_eligible = *glob_eligible_ptr; + + if (glob_eligible == NULL) { + return argv; + } + + int has_any = 0; + + for (int i = 0; argv[i] != NULL; i++) { + if (glob_eligible[i] && has_metachars(argv[i])) { + has_any = 1; + break; + } + } + + if (!has_any) { + return argv; + } + + int capacity = 8; + int count = 0; + char **new_argv = malloc((capacity + 1) * sizeof(char *)); + + if (new_argv == NULL) { + fprintf(stderr, "allocation error\n"); + exit(EXIT_FAILURE); + } + + int *new_eligible = malloc((capacity + 1) * sizeof(int)); + + if (new_eligible == NULL) { + fprintf(stderr, "allocation error\n"); + exit(EXIT_FAILURE); + } + + for (int i = 0; argv[i] != NULL; i++) { + if (glob_eligible[i] && has_metachars(argv[i])) { + glob_t g; + int r = glob(argv[i], GLOB_NOCHECK, NULL, &g); + + if (r == GLOB_NOSPACE) { + fprintf(stderr, "trigger: glob out of memory\n"); + globfree(&g); + grow_if_needed(&new_argv, &new_eligible, &capacity, count); + new_argv[count] = argv[i]; + new_eligible[count] = 0; + count++; + continue; + } + + for (size_t j = 0; j < g.gl_pathc; j++) { + grow_if_needed(&new_argv, &new_eligible, &capacity, count); + new_argv[count] = strdup(g.gl_pathv[j]); + new_eligible[count] = 0; + count++; + } + + free(argv[i]); + globfree(&g); + } else { + grow_if_needed(&new_argv, &new_eligible, &capacity, count); + new_argv[count] = argv[i]; + new_eligible[count] = glob_eligible[i]; + count++; + } + } + + new_argv[count] = NULL; + + free(argv); + free(glob_eligible); + + *glob_eligible_ptr = new_eligible; + return new_argv; +} diff --git a/src/input.c b/src/input.c index ee1ff88..615be79 100644 --- a/src/input.c +++ b/src/input.c @@ -1,45 +1,44 @@ -#include -#include -#include -#include "input.h" -#include "utils/utils.h" - -char *trigger_read_line(void) { - char *line = NULL; - size_t buffer_size = TRIGGER_AUTOMATIC_BUFFER_SIZE; - - if (getline(&line, &buffer_size, stdin) == GET_LINE_REACHED_END_OF_FILE_OR_ERROR) { - if (feof(stdin)) { - exit(EXIT_SUCCESS); - } - - perror("readline"); - exit(EXIT_FAILURE); - } - - return line; -} - -char **trigger_split_line(const char *line) { - char** tokens = parse_line_with_quotes(line, NULL); - - if (tokens == NULL) { - return NULL; - } - - return tokens; -} - -char **trigger_split_line_ex(const char *line, int **out_glob_eligible) { - char** tokens = parse_line_with_quotes(line, out_glob_eligible); - - if (tokens == NULL) { - if (out_glob_eligible != NULL) { - *out_glob_eligible = NULL; - } - return NULL; - } - - return tokens; -} - +#include "input.h" +#include "utils/utils.h" +#include +#include +#include + +char *trigger_read_line(void) { + char *line = NULL; + size_t buffer_size = TRIGGER_AUTOMATIC_BUFFER_SIZE; + + if (getline(&line, &buffer_size, stdin) == GET_LINE_REACHED_END_OF_FILE_OR_ERROR) { + if (feof(stdin)) { + exit(EXIT_SUCCESS); + } + + perror("readline"); + exit(EXIT_FAILURE); + } + + return line; +} + +char **trigger_split_line(const char *line) { + char **tokens = parse_line_with_quotes(line, NULL); + + if (tokens == NULL) { + return NULL; + } + + return tokens; +} + +char **trigger_split_line_ex(const char *line, int **out_glob_eligible) { + char **tokens = parse_line_with_quotes(line, out_glob_eligible); + + if (tokens == NULL) { + if (out_glob_eligible != NULL) { + *out_glob_eligible = NULL; + } + return NULL; + } + + return tokens; +} diff --git a/src/main.c b/src/main.c index ef72c4c..a62da37 100644 --- a/src/main.c +++ b/src/main.c @@ -1,36 +1,36 @@ -#include -#include -#include "input.h" -#include "execute.h" -#include "utils/utils.h" - -void trigger_loop(void) { - int status; - - do { - printf("> "); - fflush(stdout); - char *line = trigger_read_line(); - int *glob_eligible = NULL; - char **args = trigger_split_line_ex(line, &glob_eligible); - - if (args == NULL) { - free(line); - free(glob_eligible); - status = true; - continue; - } - - status = trigger_execute(&args, &glob_eligible); - - free(line); - free(glob_eligible); - free_array_of_strings(args); - } while (status); -} - -int main(void) { - trigger_loop(); - - return EXIT_SUCCESS; -} +#include "execute.h" +#include "input.h" +#include "utils/utils.h" +#include +#include + +void trigger_loop(void) { + int status; + + do { + printf("> "); + fflush(stdout); + char *line = trigger_read_line(); + int *glob_eligible = NULL; + char **args = trigger_split_line_ex(line, &glob_eligible); + + if (args == NULL) { + free(line); + free(glob_eligible); + status = true; + continue; + } + + status = trigger_execute(&args, &glob_eligible); + + free(line); + free(glob_eligible); + free_array_of_strings(args); + } while (status); +} + +int main(void) { + trigger_loop(); + + return EXIT_SUCCESS; +} diff --git a/src/pipeline.c b/src/pipeline.c index 5fba32d..4e83bc0 100644 --- a/src/pipeline.c +++ b/src/pipeline.c @@ -1,360 +1,354 @@ -#include -#include -#include -#include -#include -#include -#include "pipeline.h" -#include "builtins.h" -#include "execute.h" -#include "utils/utils.h" - -PipelineStage *trigger_parse_pipeline(char **tokens, int *num_stages) { - int stage_count = 1; - int total_tokens = 0; - - for (int i = 0; tokens[i] != NULL; i++) { - total_tokens++; - if (strcmp(tokens[i], "|") == 0) { - stage_count++; - } - } - - PipelineStage *stages = malloc(stage_count * sizeof(PipelineStage)); - - if (stages == NULL) { - fprintf(stderr, "allocation error\n"); - exit(EXIT_FAILURE); - } - - int si = 0; - int stage_start = 0; - - while (si < stage_count) { - stages[si].argv = NULL; - stages[si].infile = NULL; - stages[si].outfile = NULL; - stages[si].append = 0; - - int argv_count = 0; - char *infile_val = NULL; - char *outfile_val = NULL; - int outfile_append = 0; - - int j = stage_start; - - while (tokens[j] != NULL && strcmp(tokens[j], "|") != 0) { - if (strcmp(tokens[j], "<") == 0) { - if (tokens[j + 1] != NULL && strcmp(tokens[j + 1], "|") != 0 - && strcmp(tokens[j + 1], "<") != 0 - && strcmp(tokens[j + 1], ">") != 0 - && strcmp(tokens[j + 1], ">>") != 0) { - infile_val = tokens[j + 1]; - j++; - } else { - fprintf(stderr, "trigger: syntax error: expected filename after '<'\n"); - } - } else if (strcmp(tokens[j], ">") == 0) { - if (tokens[j + 1] != NULL && strcmp(tokens[j + 1], "|") != 0 - && strcmp(tokens[j + 1], "<") != 0 - && strcmp(tokens[j + 1], ">") != 0 - && strcmp(tokens[j + 1], ">>") != 0) { - outfile_val = tokens[j + 1]; - outfile_append = 0; - j++; - } else { - fprintf(stderr, "trigger: syntax error: expected filename after '>'\n"); - } - } else if (strcmp(tokens[j], ">>") == 0) { - if (tokens[j + 1] != NULL && strcmp(tokens[j + 1], "|") != 0 - && strcmp(tokens[j + 1], "<") != 0 - && strcmp(tokens[j + 1], ">") != 0 - && strcmp(tokens[j + 1], ">>") != 0) { - outfile_val = tokens[j + 1]; - outfile_append = 1; - j++; - } else { - fprintf(stderr, "trigger: syntax error: expected filename after '>>'\n"); - } - } else { - argv_count++; - } - j++; - } - - stages[si].argv = malloc((argv_count + 1) * sizeof(char *)); - - if (stages[si].argv == NULL) { - fprintf(stderr, "allocation error\n"); - exit(EXIT_FAILURE); - } - - int ai = 0; - - j = stage_start; - - while (tokens[j] != NULL && strcmp(tokens[j], "|") != 0) { - if (strcmp(tokens[j], "<") == 0) { - if (tokens[j + 1] != NULL && strcmp(tokens[j + 1], "|") != 0 - && strcmp(tokens[j + 1], "<") != 0 - && strcmp(tokens[j + 1], ">") != 0 - && strcmp(tokens[j + 1], ">>") != 0) { - j++; - } - } else if (strcmp(tokens[j], ">") == 0) { - if (tokens[j + 1] != NULL && strcmp(tokens[j + 1], "|") != 0 - && strcmp(tokens[j + 1], "<") != 0 - && strcmp(tokens[j + 1], ">") != 0 - && strcmp(tokens[j + 1], ">>") != 0) { - j++; - } - } else if (strcmp(tokens[j], ">>") == 0) { - if (tokens[j + 1] != NULL && strcmp(tokens[j + 1], "|") != 0 - && strcmp(tokens[j + 1], "<") != 0 - && strcmp(tokens[j + 1], ">") != 0 - && strcmp(tokens[j + 1], ">>") != 0) { - j++; - } - } else { - stages[si].argv[ai++] = tokens[j]; - tokens[j] = NULL; - } - j++; - } - stages[si].argv[ai] = NULL; - - stages[si].infile = infile_val; - stages[si].outfile = outfile_val; - stages[si].append = outfile_append; - - if (infile_val != NULL) { - for (int k = 0; k < total_tokens; k++) { - if (tokens[k] == infile_val) { - tokens[k] = NULL; - } - } - } - - if (outfile_val != NULL) { - for (int k = 0; k < total_tokens; k++) { - if (tokens[k] == outfile_val) { - tokens[k] = NULL; - } - } - } - - if (tokens[j] != NULL) { - j++; - } - stage_start = j; - si++; - } - - for (int i = 0; i < total_tokens; i++) { - free(tokens[i]); - } - - *num_stages = stage_count; - return stages; -} - -static void free_pipeline(PipelineStage *stages, int num_stages) { - for (int i = 0; i < num_stages; i++) { - free(stages[i].infile); - free(stages[i].outfile); - if (stages[i].argv != NULL) { - free_array_of_strings(stages[i].argv); - } - } - free(stages); -} - -int trigger_execute_pipeline(PipelineStage *stages, int num_stages) { - if (num_stages == 1 && stages[0].infile == NULL && stages[0].outfile == NULL) { - char **args = stages[0].argv; - - if (args[0] != NULL) { - for (int i = 0; i < trigger_num_builtins(); i++) { - if (strcmp(args[0], builtin_str[i]) == 0) { - int r = (*builtin_func[i])(args); - free_pipeline(stages, num_stages); - return r; - } - } - - int r = trigger_launch(args); - free_pipeline(stages, num_stages); - return r; - } - - free_pipeline(stages, num_stages); - return true; - } - - if (num_stages == 1 && (stages[0].infile != NULL || stages[0].outfile != NULL)) { - char **args = stages[0].argv; - - if (args[0] != NULL) { - for (int i = 0; i < trigger_num_builtins(); i++) { - if (strcmp(args[0], builtin_str[i]) == 0) { - int saved_stdin = dup(STDIN_FILENO); - int saved_stdout = dup(STDOUT_FILENO); - - if (stages[0].infile != NULL) { - int fd = open(stages[0].infile, O_RDONLY); - - if (fd < 0) { - perror("trigger"); - } else { - dup2(fd, STDIN_FILENO); - close(fd); - } - } - - if (stages[0].outfile != NULL) { - int flags = O_WRONLY | O_CREAT; - - flags |= stages[0].append ? O_APPEND : O_TRUNC; - int fd = open(stages[0].outfile, flags, 0644); - - if (fd < 0) { - perror("trigger"); - } else { - dup2(fd, STDOUT_FILENO); - close(fd); - } - } - - int r = (*builtin_func[i])(args); - - dup2(saved_stdin, STDIN_FILENO); - close(saved_stdin); - dup2(saved_stdout, STDOUT_FILENO); - close(saved_stdout); - - free_pipeline(stages, num_stages); - return r; - } - } - } - } - - int pipe_count = num_stages - 1; - int *pipe_fds = NULL; - - if (pipe_count > 0) { - pipe_fds = malloc(2 * pipe_count * sizeof(int)); - - if (pipe_fds == NULL) { - fprintf(stderr, "allocation error\n"); - exit(EXIT_FAILURE); - } - - for (int i = 0; i < pipe_count; i++) { - if (pipe(pipe_fds + 2 * i) < 0) { - perror("trigger"); - - for (int j = 0; j < i; j++) { - close(pipe_fds[2 * j]); - close(pipe_fds[2 * j + 1]); - } - - free(pipe_fds); - free_pipeline(stages, num_stages); - return true; - } - } - } - - pid_t *pids = malloc(num_stages * sizeof(pid_t)); - - if (pids == NULL) { - fprintf(stderr, "allocation error\n"); - free(pipe_fds); - free_pipeline(stages, num_stages); - exit(EXIT_FAILURE); - } - - for (int i = 0; i < num_stages; i++) { - pids[i] = -1; - } - - for (int i = 0; i < num_stages; i++) { - pid_t pid = fork(); - - if (pid == CHILD_PROCESS_EXITED) { - if (i > 0) { - dup2(pipe_fds[2 * (i - 1)], STDIN_FILENO); - } else if (stages[i].infile != NULL) { - int fd = open(stages[i].infile, O_RDONLY); - - if (fd < 0) { - perror("trigger"); - exit(EXIT_FAILURE); - } - dup2(fd, STDIN_FILENO); - close(fd); - } - - if (i < num_stages - 1) { - dup2(pipe_fds[2 * i + 1], STDOUT_FILENO); - } else if (stages[i].outfile != NULL) { - int flags = O_WRONLY | O_CREAT; - - flags |= stages[i].append ? O_APPEND : O_TRUNC; - int fd = open(stages[i].outfile, flags, 0644); - - if (fd < 0) { - perror("trigger"); - exit(EXIT_FAILURE); - } - dup2(fd, STDOUT_FILENO); - close(fd); - } - - for (int j = 0; j < 2 * pipe_count; j++) { - close(pipe_fds[j]); - } - - char **args = stages[i].argv; - - if (args[0] != NULL) { - for (int j = 0; j < trigger_num_builtins(); j++) { - if (strcmp(args[0], builtin_str[j]) == 0) { - (*builtin_func[j])(args); - exit(EXIT_SUCCESS); - } - } - - if (execvp(args[0], args) < 0) { - perror("trigger"); - } - } - exit(EXIT_FAILURE); - } else if (pid < 0) { - perror("trigger"); - } else { - pids[i] = pid; - } - } - - for (int j = 0; j < 2 * pipe_count; j++) { - close(pipe_fds[j]); - } - free(pipe_fds); - - int status; - - for (int i = 0; i < num_stages; i++) { - if (pids[i] == -1) { - continue; - } - do { - waitpid(pids[i], &status, WUNTRACED); - } while (!WIFEXITED(status) && !WIFSIGNALED(status)); - } - - free(pids); - free_pipeline(stages, num_stages); - return true; -} +#include "pipeline.h" +#include "builtins.h" +#include "execute.h" +#include "utils/utils.h" +#include +#include +#include +#include +#include +#include + +PipelineStage *trigger_parse_pipeline(char **tokens, int *num_stages) { + int stage_count = 1; + int total_tokens = 0; + + for (int i = 0; tokens[i] != NULL; i++) { + total_tokens++; + if (strcmp(tokens[i], "|") == 0) { + stage_count++; + } + } + + PipelineStage *stages = malloc(stage_count * sizeof(PipelineStage)); + + if (stages == NULL) { + fprintf(stderr, "allocation error\n"); + exit(EXIT_FAILURE); + } + + int si = 0; + int stage_start = 0; + + while (si < stage_count) { + stages[si].argv = NULL; + stages[si].infile = NULL; + stages[si].outfile = NULL; + stages[si].append = 0; + + int argv_count = 0; + char *infile_val = NULL; + char *outfile_val = NULL; + int outfile_append = 0; + + int j = stage_start; + + while (tokens[j] != NULL && strcmp(tokens[j], "|") != 0) { + if (strcmp(tokens[j], "<") == 0) { + if (tokens[j + 1] != NULL && strcmp(tokens[j + 1], "|") != 0 && + strcmp(tokens[j + 1], "<") != 0 && strcmp(tokens[j + 1], ">") != 0 && + strcmp(tokens[j + 1], ">>") != 0) { + infile_val = tokens[j + 1]; + j++; + } else { + fprintf(stderr, "trigger: syntax error: expected filename after '<'\n"); + } + } else if (strcmp(tokens[j], ">") == 0) { + if (tokens[j + 1] != NULL && strcmp(tokens[j + 1], "|") != 0 && + strcmp(tokens[j + 1], "<") != 0 && strcmp(tokens[j + 1], ">") != 0 && + strcmp(tokens[j + 1], ">>") != 0) { + outfile_val = tokens[j + 1]; + outfile_append = 0; + j++; + } else { + fprintf(stderr, "trigger: syntax error: expected filename after '>'\n"); + } + } else if (strcmp(tokens[j], ">>") == 0) { + if (tokens[j + 1] != NULL && strcmp(tokens[j + 1], "|") != 0 && + strcmp(tokens[j + 1], "<") != 0 && strcmp(tokens[j + 1], ">") != 0 && + strcmp(tokens[j + 1], ">>") != 0) { + outfile_val = tokens[j + 1]; + outfile_append = 1; + j++; + } else { + fprintf(stderr, "trigger: syntax error: expected filename after '>>'\n"); + } + } else { + argv_count++; + } + j++; + } + + stages[si].argv = malloc((argv_count + 1) * sizeof(char *)); + + if (stages[si].argv == NULL) { + fprintf(stderr, "allocation error\n"); + exit(EXIT_FAILURE); + } + + int ai = 0; + + j = stage_start; + + while (tokens[j] != NULL && strcmp(tokens[j], "|") != 0) { + if (strcmp(tokens[j], "<") == 0) { + if (tokens[j + 1] != NULL && strcmp(tokens[j + 1], "|") != 0 && + strcmp(tokens[j + 1], "<") != 0 && strcmp(tokens[j + 1], ">") != 0 && + strcmp(tokens[j + 1], ">>") != 0) { + j++; + } + } else if (strcmp(tokens[j], ">") == 0) { + if (tokens[j + 1] != NULL && strcmp(tokens[j + 1], "|") != 0 && + strcmp(tokens[j + 1], "<") != 0 && strcmp(tokens[j + 1], ">") != 0 && + strcmp(tokens[j + 1], ">>") != 0) { + j++; + } + } else if (strcmp(tokens[j], ">>") == 0) { + if (tokens[j + 1] != NULL && strcmp(tokens[j + 1], "|") != 0 && + strcmp(tokens[j + 1], "<") != 0 && strcmp(tokens[j + 1], ">") != 0 && + strcmp(tokens[j + 1], ">>") != 0) { + j++; + } + } else { + stages[si].argv[ai++] = tokens[j]; + tokens[j] = NULL; + } + j++; + } + stages[si].argv[ai] = NULL; + + stages[si].infile = infile_val; + stages[si].outfile = outfile_val; + stages[si].append = outfile_append; + + if (infile_val != NULL) { + for (int k = 0; k < total_tokens; k++) { + if (tokens[k] == infile_val) { + tokens[k] = NULL; + } + } + } + + if (outfile_val != NULL) { + for (int k = 0; k < total_tokens; k++) { + if (tokens[k] == outfile_val) { + tokens[k] = NULL; + } + } + } + + if (tokens[j] != NULL) { + j++; + } + stage_start = j; + si++; + } + + for (int i = 0; i < total_tokens; i++) { + free(tokens[i]); + } + + *num_stages = stage_count; + return stages; +} + +static void free_pipeline(PipelineStage *stages, int num_stages) { + for (int i = 0; i < num_stages; i++) { + free(stages[i].infile); + free(stages[i].outfile); + if (stages[i].argv != NULL) { + free_array_of_strings(stages[i].argv); + } + } + free(stages); +} + +int trigger_execute_pipeline(PipelineStage *stages, int num_stages) { + if (num_stages == 1 && stages[0].infile == NULL && stages[0].outfile == NULL) { + char **args = stages[0].argv; + + if (args[0] != NULL) { + for (int i = 0; i < trigger_num_builtins(); i++) { + if (strcmp(args[0], builtin_str[i]) == 0) { + int r = (*builtin_func[i])(args); + free_pipeline(stages, num_stages); + return r; + } + } + + int r = trigger_launch(args); + free_pipeline(stages, num_stages); + return r; + } + + free_pipeline(stages, num_stages); + return true; + } + + if (num_stages == 1 && (stages[0].infile != NULL || stages[0].outfile != NULL)) { + char **args = stages[0].argv; + + if (args[0] != NULL) { + for (int i = 0; i < trigger_num_builtins(); i++) { + if (strcmp(args[0], builtin_str[i]) == 0) { + int saved_stdin = dup(STDIN_FILENO); + int saved_stdout = dup(STDOUT_FILENO); + + if (stages[0].infile != NULL) { + int fd = open(stages[0].infile, O_RDONLY); + + if (fd < 0) { + perror("trigger"); + } else { + dup2(fd, STDIN_FILENO); + close(fd); + } + } + + if (stages[0].outfile != NULL) { + int flags = O_WRONLY | O_CREAT; + + flags |= stages[0].append ? O_APPEND : O_TRUNC; + int fd = open(stages[0].outfile, flags, 0644); + + if (fd < 0) { + perror("trigger"); + } else { + dup2(fd, STDOUT_FILENO); + close(fd); + } + } + + int r = (*builtin_func[i])(args); + + dup2(saved_stdin, STDIN_FILENO); + close(saved_stdin); + dup2(saved_stdout, STDOUT_FILENO); + close(saved_stdout); + + free_pipeline(stages, num_stages); + return r; + } + } + } + } + + int pipe_count = num_stages - 1; + int *pipe_fds = NULL; + + if (pipe_count > 0) { + pipe_fds = malloc((unsigned long) (2 * pipe_count) * sizeof(int)); + + if (pipe_fds == NULL) { + fprintf(stderr, "allocation error\n"); + exit(EXIT_FAILURE); + } + + for (int i = 0; i < pipe_count; i++) { + if (pipe(pipe_fds + (2 * i)) < 0) { + perror("trigger"); + + for (int j = 0; j < i; j++) { + close(pipe_fds[2 * j]); + close(pipe_fds[(2 * j) + 1]); + } + + free(pipe_fds); + free_pipeline(stages, num_stages); + return true; + } + } + } + + pid_t *pids = malloc(num_stages * sizeof(pid_t)); + + if (pids == NULL) { + fprintf(stderr, "allocation error\n"); + free(pipe_fds); + free_pipeline(stages, num_stages); + exit(EXIT_FAILURE); + } + + for (int i = 0; i < num_stages; i++) { + pids[i] = -1; + } + + for (int i = 0; i < num_stages; i++) { + pid_t pid = fork(); + + if (pid == CHILD_PROCESS_EXITED) { + if (i > 0) { + dup2(pipe_fds[2 * (i - 1)], STDIN_FILENO); + } else if (stages[i].infile != NULL) { + int fd = open(stages[i].infile, O_RDONLY); + + if (fd < 0) { + perror("trigger"); + exit(EXIT_FAILURE); + } + dup2(fd, STDIN_FILENO); + close(fd); + } + + if (i < num_stages - 1) { + dup2(pipe_fds[(2 * i) + 1], STDOUT_FILENO); + } else if (stages[i].outfile != NULL) { + int flags = O_WRONLY | O_CREAT; + + flags |= stages[i].append ? O_APPEND : O_TRUNC; + int fd = open(stages[i].outfile, flags, 0644); + + if (fd < 0) { + perror("trigger"); + exit(EXIT_FAILURE); + } + dup2(fd, STDOUT_FILENO); + close(fd); + } + + for (int j = 0; j < 2 * pipe_count; j++) { + close(pipe_fds[j]); + } + + char **args = stages[i].argv; + + if (args[0] != NULL) { + for (int j = 0; j < trigger_num_builtins(); j++) { + if (strcmp(args[0], builtin_str[j]) == 0) { + (*builtin_func[j])(args); + exit(EXIT_SUCCESS); + } + } + + if (execvp(args[0], args) < 0) { + perror("trigger"); + } + } + exit(EXIT_FAILURE); + } else if (pid < 0) { + perror("trigger"); + } else { + pids[i] = pid; + } + } + + for (int j = 0; j < 2 * pipe_count; j++) { + close(pipe_fds[j]); + } + free(pipe_fds); + + int status; + + for (int i = 0; i < num_stages; i++) { + if (pids[i] == -1) { + continue; + } + do { + waitpid(pids[i], &status, WUNTRACED); + } while (!WIFEXITED(status) && !WIFSIGNALED(status)); + } + + free(pids); + free_pipeline(stages, num_stages); + return true; +} diff --git a/src/utils/utils.c b/src/utils/utils.c index 1368e07..ba877ad 100644 --- a/src/utils/utils.c +++ b/src/utils/utils.c @@ -1,259 +1,252 @@ -#include -#include -#include -#include "utils.h" - -void free_array_of_strings(char **array) { - if (array) { - for (int i = 0; array[i] != NULL; i++) { - free(array[i]); - } - free(array); - } -} - -int is_whitespace(const char c) { - return c == ' ' || c == '\t' || c == '\r' || c == '\n' || c == '\a'; -} - -void add_char_to_token(char **token_buffer, int *token_pos, int *token_size, const char c) { - if (*token_pos >= *token_size - 1) { - *token_size *= 2; - char *new_buffer = realloc(*token_buffer, *token_size); - - if (!new_buffer) { - fprintf(stderr, "allocation error\n"); - exit(EXIT_FAILURE); - } - - *token_buffer = new_buffer; - } - (*token_buffer)[(*token_pos)++] = c; -} - -void finalize_token(char ***tokens_ptr, int *position, int *buffer_size, - char **token_buffer, int *token_pos, int *token_size, int force, - int **out_glob_eligible, int glob_eligible) { - if (*token_pos > 0 || force) { - char **tokens = *tokens_ptr; - - (*token_buffer)[*token_pos] = '\0'; - - if (*position >= *buffer_size) { - *buffer_size += TRIGGER_TOK_BUFFER_SIZE; - char **new_tokens = realloc(tokens, *buffer_size * sizeof(char *)); - - if (!new_tokens) { - fprintf(stderr, "allocation error\n"); - exit(EXIT_FAILURE); - } - - tokens = new_tokens; - *tokens_ptr = tokens; - - if (out_glob_eligible != NULL) { - int *new_glob = realloc(*out_glob_eligible, *buffer_size * sizeof(int)); - - if (!new_glob) { - fprintf(stderr, "allocation error\n"); - exit(EXIT_FAILURE); - } - - *out_glob_eligible = new_glob; - } - } - - tokens[*position] = strdup(*token_buffer); - - if (out_glob_eligible != NULL) { - (*out_glob_eligible)[*position] = glob_eligible; - } - - (*position)++; - - *token_pos = 0; - } -} - -static void process_character(const char c, const char next_c, ParserState *state, - char **token_buffer, int *token_pos, int *token_size, - char ***tokens_ptr, int *position, int *buffer_size, - int *skip_next, int *glob_eligible, - int **out_glob_eligible) { - *skip_next = 0; - - switch (*state) { - case STATE_NORMAL: - if (c == '\\') { - *state = STATE_ESCAPED; - } else if (c == '\'') { - *state = STATE_IN_SINGLE_QUOTE; - } else if (c == '"') { - *state = STATE_IN_DOUBLE_QUOTE; - } else if (is_whitespace(c)) { - finalize_token(tokens_ptr, position, buffer_size, - token_buffer, token_pos, token_size, false, - out_glob_eligible, *glob_eligible); - *glob_eligible = 1; - } else { - add_char_to_token(token_buffer, token_pos, token_size, c); - } - break; - - case STATE_ESCAPED: - add_char_to_token(token_buffer, token_pos, token_size, c); - *glob_eligible = 0; - *state = STATE_NORMAL; - break; - - case STATE_IN_SINGLE_QUOTE: - if (c == '\'') { - *state = STATE_NORMAL; - if (next_c == '\0' || is_whitespace(next_c)) { - finalize_token(tokens_ptr, position, buffer_size, - token_buffer, token_pos, token_size, true, - out_glob_eligible, *glob_eligible); - *glob_eligible = 1; - } - } else { - add_char_to_token(token_buffer, token_pos, token_size, c); - *glob_eligible = 0; - } - break; - - case STATE_IN_DOUBLE_QUOTE: - if (c == '"') { - *state = STATE_NORMAL; - if (next_c == '\0' || is_whitespace(next_c)) { - finalize_token(tokens_ptr, position, buffer_size, - token_buffer, token_pos, token_size, true, - out_glob_eligible, *glob_eligible); - *glob_eligible = 1; - } - } else if (c == '\\' && next_c != '\0') { - if (next_c == '"' || next_c == '\\' || next_c == '$' || next_c == ' ') { - add_char_to_token(token_buffer, token_pos, token_size, next_c); - *glob_eligible = 0; - *skip_next = true; - } else { - add_char_to_token(token_buffer, token_pos, token_size, c); - *glob_eligible = 0; - } - } else { - add_char_to_token(token_buffer, token_pos, token_size, c); - *glob_eligible = 0; - } - break; - } -} - -char** parse_line_with_quotes(const char *line, int **out_glob_eligible) { - - int buffer_size = TRIGGER_TOK_BUFFER_SIZE; - int position = 0; - char **tokens = malloc(buffer_size * sizeof(char *)); - - if (!tokens) { - fprintf(stderr, "allocation error\n"); - exit(EXIT_FAILURE); - } - - int *glob_eligible_arr = NULL; - - if (out_glob_eligible != NULL) { - glob_eligible_arr = malloc(buffer_size * sizeof(int)); - - if (!glob_eligible_arr) { - fprintf(stderr, "allocation error\n"); - exit(EXIT_FAILURE); - } - - *out_glob_eligible = glob_eligible_arr; - } - - int token_size = 128; - int token_pos = 0; - char *token_buffer = malloc(token_size); - - if (!token_buffer) { - fprintf(stderr, "allocation error\n"); - exit(EXIT_FAILURE); - } - - ParserState state = STATE_NORMAL; - int glob_eligible = 1; - int **pc_out_glob = (out_glob_eligible != NULL) ? &glob_eligible_arr : NULL; - int i = 0; - - while (line[i] != '\0') { - int skip_next = false; - const char next_char = line[i + 1]; - - process_character(line[i], next_char, &state, - &token_buffer, &token_pos, &token_size, - &tokens, &position, &buffer_size, - &skip_next, &glob_eligible, pc_out_glob); - - if (skip_next) { - i++; - } - i++; - } - - if (state == STATE_IN_SINGLE_QUOTE) { - fprintf(stderr, "Error: Unclosed single quote\n"); - free(token_buffer); - tokens[position] = NULL; - free_array_of_strings(tokens); - free(glob_eligible_arr); - - if (out_glob_eligible != NULL) { - *out_glob_eligible = NULL; - } - - return NULL; - } - - if (state == STATE_IN_DOUBLE_QUOTE) { - fprintf(stderr, "Error: Unclosed double quote\n"); - free(token_buffer); - tokens[position] = NULL; - free_array_of_strings(tokens); - free(glob_eligible_arr); - - if (out_glob_eligible != NULL) { - *out_glob_eligible = NULL; - } - - return NULL; - } - - finalize_token(&tokens, &position, &buffer_size, - &token_buffer, &token_pos, &token_size, false, - pc_out_glob, glob_eligible); - - if (position >= buffer_size) { - buffer_size += TRIGGER_TOK_BUFFER_SIZE; - char **new_tokens = realloc(tokens, buffer_size * sizeof(char *)); - - if (!new_tokens) { - fprintf(stderr, "allocation error\n"); - exit(EXIT_FAILURE); - } - - tokens = new_tokens; - } - - tokens[position] = NULL; - - if (out_glob_eligible != NULL) { - *out_glob_eligible = glob_eligible_arr; - } else { - free(glob_eligible_arr); - } - - free(token_buffer); - - return tokens; -} - +#include "utils.h" +#include +#include +#include + +void free_array_of_strings(char **array) { + if (array) { + for (int i = 0; array[i] != NULL; i++) { + free(array[i]); + } + free(array); + } +} + +int is_whitespace(const char c) { + return c == ' ' || c == '\t' || c == '\r' || c == '\n' || c == '\a'; +} + +void add_char_to_token(char **token_buffer, int *token_pos, int *token_size, const char c) { + if (*token_pos >= *token_size - 1) { + *token_size *= 2; + char *new_buffer = realloc(*token_buffer, *token_size); + + if (!new_buffer) { + fprintf(stderr, "allocation error\n"); + exit(EXIT_FAILURE); + } + + *token_buffer = new_buffer; + } + (*token_buffer)[(*token_pos)++] = c; +} + +void finalize_token(char ***tokens_ptr, int *position, int *buffer_size, char **token_buffer, + int *token_pos, int *token_size, int force, int **out_glob_eligible, + int glob_eligible) { + if (*token_pos > 0 || force) { + char **tokens = *tokens_ptr; + + (*token_buffer)[*token_pos] = '\0'; + + if (*position >= *buffer_size) { + *buffer_size += TRIGGER_TOK_BUFFER_SIZE; + char **new_tokens = realloc(tokens, *buffer_size * sizeof(char *)); + + if (!new_tokens) { + fprintf(stderr, "allocation error\n"); + exit(EXIT_FAILURE); + } + + tokens = new_tokens; + *tokens_ptr = tokens; + + if (out_glob_eligible != NULL) { + int *new_glob = realloc(*out_glob_eligible, *buffer_size * sizeof(int)); + + if (!new_glob) { + fprintf(stderr, "allocation error\n"); + exit(EXIT_FAILURE); + } + + *out_glob_eligible = new_glob; + } + } + + tokens[*position] = strdup(*token_buffer); + + if (out_glob_eligible != NULL) { + (*out_glob_eligible)[*position] = glob_eligible; + } + + (*position)++; + + *token_pos = 0; + } +} + +static void process_character(const char c, const char next_c, ParserState *state, + char **token_buffer, int *token_pos, int *token_size, + char ***tokens_ptr, int *position, int *buffer_size, int *skip_next, + int *glob_eligible, int **out_glob_eligible) { + *skip_next = 0; + + switch (*state) { + case STATE_NORMAL: + if (c == '\\') { + *state = STATE_ESCAPED; + } else if (c == '\'') { + *state = STATE_IN_SINGLE_QUOTE; + } else if (c == '"') { + *state = STATE_IN_DOUBLE_QUOTE; + } else if (is_whitespace(c)) { + finalize_token(tokens_ptr, position, buffer_size, token_buffer, token_pos, token_size, + false, out_glob_eligible, *glob_eligible); + *glob_eligible = 1; + } else { + add_char_to_token(token_buffer, token_pos, token_size, c); + } + break; + + case STATE_ESCAPED: + add_char_to_token(token_buffer, token_pos, token_size, c); + *glob_eligible = 0; + *state = STATE_NORMAL; + break; + + case STATE_IN_SINGLE_QUOTE: + if (c == '\'') { + *state = STATE_NORMAL; + if (next_c == '\0' || is_whitespace(next_c)) { + finalize_token(tokens_ptr, position, buffer_size, token_buffer, token_pos, + token_size, true, out_glob_eligible, *glob_eligible); + *glob_eligible = 1; + } + } else { + add_char_to_token(token_buffer, token_pos, token_size, c); + *glob_eligible = 0; + } + break; + + case STATE_IN_DOUBLE_QUOTE: + if (c == '"') { + *state = STATE_NORMAL; + if (next_c == '\0' || is_whitespace(next_c)) { + finalize_token(tokens_ptr, position, buffer_size, token_buffer, token_pos, + token_size, true, out_glob_eligible, *glob_eligible); + *glob_eligible = 1; + } + } else if (c == '\\' && next_c != '\0') { + if (next_c == '"' || next_c == '\\' || next_c == '$' || next_c == ' ') { + add_char_to_token(token_buffer, token_pos, token_size, next_c); + *glob_eligible = 0; + *skip_next = true; + } else { + add_char_to_token(token_buffer, token_pos, token_size, c); + *glob_eligible = 0; + } + } else { + add_char_to_token(token_buffer, token_pos, token_size, c); + *glob_eligible = 0; + } + break; + } +} + +char **parse_line_with_quotes(const char *line, int **out_glob_eligible) { + + int buffer_size = TRIGGER_TOK_BUFFER_SIZE; + int position = 0; + char **tokens = malloc(buffer_size * sizeof(char *)); + + if (!tokens) { + fprintf(stderr, "allocation error\n"); + exit(EXIT_FAILURE); + } + + int *glob_eligible_arr = NULL; + + if (out_glob_eligible != NULL) { + glob_eligible_arr = malloc(buffer_size * sizeof(int)); + + if (!glob_eligible_arr) { + fprintf(stderr, "allocation error\n"); + exit(EXIT_FAILURE); + } + + *out_glob_eligible = glob_eligible_arr; + } + + int token_size = 128; + int token_pos = 0; + char *token_buffer = malloc(token_size); + + if (!token_buffer) { + fprintf(stderr, "allocation error\n"); + exit(EXIT_FAILURE); + } + + ParserState state = STATE_NORMAL; + int glob_eligible = 1; + int **pc_out_glob = (out_glob_eligible != NULL) ? &glob_eligible_arr : NULL; + int i = 0; + + while (line[i] != '\0') { + int skip_next = false; + const char next_char = line[i + 1]; + + process_character(line[i], next_char, &state, &token_buffer, &token_pos, &token_size, + &tokens, &position, &buffer_size, &skip_next, &glob_eligible, + pc_out_glob); + + if (skip_next) { + i++; + } + i++; + } + + if (state == STATE_IN_SINGLE_QUOTE) { + fprintf(stderr, "Error: Unclosed single quote\n"); + free(token_buffer); + tokens[position] = NULL; + free_array_of_strings(tokens); + free(glob_eligible_arr); + + if (out_glob_eligible != NULL) { + *out_glob_eligible = NULL; + } + + return NULL; + } + + if (state == STATE_IN_DOUBLE_QUOTE) { + fprintf(stderr, "Error: Unclosed double quote\n"); + free(token_buffer); + tokens[position] = NULL; + free_array_of_strings(tokens); + free(glob_eligible_arr); + + if (out_glob_eligible != NULL) { + *out_glob_eligible = NULL; + } + + return NULL; + } + + finalize_token(&tokens, &position, &buffer_size, &token_buffer, &token_pos, &token_size, false, + pc_out_glob, glob_eligible); + + if (position >= buffer_size) { + buffer_size += TRIGGER_TOK_BUFFER_SIZE; + char **new_tokens = realloc(tokens, buffer_size * sizeof(char *)); + + if (!new_tokens) { + fprintf(stderr, "allocation error\n"); + exit(EXIT_FAILURE); + } + + tokens = new_tokens; + } + + tokens[position] = NULL; + + if (out_glob_eligible != NULL) { + *out_glob_eligible = glob_eligible_arr; + } else { + free(glob_eligible_arr); + } + + free(token_buffer); + + return tokens; +} diff --git a/src/utils/utils.h b/src/utils/utils.h index c3d03f5..3fcbdd1 100644 --- a/src/utils/utils.h +++ b/src/utils/utils.h @@ -1,29 +1,28 @@ -#ifndef UTILS_H -#define UTILS_H - -#define true 1 -#define false 0 - -#define TRIGGER_TOK_BUFFER_SIZE 64 - -typedef enum { - STATE_NORMAL, - STATE_IN_SINGLE_QUOTE, - STATE_IN_DOUBLE_QUOTE, - STATE_ESCAPED -} ParserState; - -void free_array_of_strings(char **array); - -int is_whitespace(char c); - -void add_char_to_token(char **token_buffer, int *token_pos, int *token_size, char c); - -void finalize_token(char ***tokens_ptr, int *position, int *buffer_size, - char **token_buffer, int *token_pos, int *token_size, int force, - int **out_glob_eligible, int glob_eligible); - -char** parse_line_with_quotes(const char *line, int **out_glob_eligible); - -#endif - +#ifndef UTILS_H +#define UTILS_H + +#define true 1 +#define false 0 + +#define TRIGGER_TOK_BUFFER_SIZE 64 + +typedef enum { + STATE_NORMAL, + STATE_IN_SINGLE_QUOTE, + STATE_IN_DOUBLE_QUOTE, + STATE_ESCAPED +} ParserState; + +void free_array_of_strings(char **array); + +int is_whitespace(char c); + +void add_char_to_token(char **token_buffer, int *token_pos, int *token_size, char c); + +void finalize_token(char ***tokens_ptr, int *position, int *buffer_size, char **token_buffer, + int *token_pos, int *token_size, int force, int **out_glob_eligible, + int glob_eligible); + +char **parse_line_with_quotes(const char *line, int **out_glob_eligible); + +#endif diff --git a/tests/test_builtins.c b/tests/test_builtins.c index 7efe173..4ec8170 100644 --- a/tests/test_builtins.c +++ b/tests/test_builtins.c @@ -1,139 +1,145 @@ -#include -#include -#include -#include -#include "../include/builtins.h" -#include "test_framework.h" - -void test_builtin_arrays_match() { - int count = trigger_num_builtins(); - - ASSERT_TRUE(count == 7, "Should have 7 built-in commands"); - - int found_cd = 0, found_help = 0, found_exit = 0; - int found_pwd = 0, found_echo = 0, found_export = 0, found_unset = 0; - for (int i = 0; i < count; i++) { - if (strcmp(builtin_str[i], "cd") == 0) found_cd = 1; - if (strcmp(builtin_str[i], "help") == 0) found_help = 1; - if (strcmp(builtin_str[i], "exit") == 0) found_exit = 1; - if (strcmp(builtin_str[i], "pwd") == 0) found_pwd = 1; - if (strcmp(builtin_str[i], "echo") == 0) found_echo = 1; - if (strcmp(builtin_str[i], "export") == 0) found_export = 1; - if (strcmp(builtin_str[i], "unset") == 0) found_unset = 1; - } - - ASSERT_TRUE(found_cd, "Should have 'cd' built-in"); - ASSERT_TRUE(found_help, "Should have 'help' built-in"); - ASSERT_TRUE(found_exit, "Should have 'exit' built-in"); - ASSERT_TRUE(found_pwd, "Should have 'pwd' built-in"); - ASSERT_TRUE(found_echo, "Should have 'echo' built-in"); - ASSERT_TRUE(found_export, "Should have 'export' built-in"); - ASSERT_TRUE(found_unset, "Should have 'unset' built-in"); -} - -void test_cd_no_args() { - char *args[] = {"cd", NULL}; - int result = trigger_cd(args); - - // Should return true (1) even when no directory provided - ASSERT_TRUE(result, "cd with no args should return true"); -} - -void test_cd_to_tmp() { - char cwd_before[1024]; - char cwd_after[1024]; - - getcwd(cwd_before, sizeof(cwd_before)); - - char *args[] = {"cd", "/tmp", NULL}; - int result = trigger_cd(args); - - getcwd(cwd_after, sizeof(cwd_after)); - - ASSERT_TRUE(result, "cd to /tmp should return true"); - ASSERT_STR_EQUAL("/tmp", cwd_after, "Should be in /tmp directory"); - - // Restore original directory - chdir(cwd_before); -} - -void test_help_command() { - char *args[] = {"help", NULL}; - int result = trigger_help(args); - - ASSERT_TRUE(result, "help command should return true"); -} - -void test_exit_command() { - char *args[] = {"exit", NULL}; - int result = trigger_exit(args); - - ASSERT_EQUAL(EXIT_SUCCESS, result, "exit command should return EXIT_SUCCESS"); -} - -void test_pwd_command() { - char *args[] = {"pwd", NULL}; - - char cwd_before[4096]; - getcwd(cwd_before, sizeof(cwd_before)); - - int result = trigger_pwd(args); - ASSERT_TRUE(result, "pwd should return true"); - - char cwd_after[4096]; - getcwd(cwd_after, sizeof(cwd_after)); - ASSERT_STR_EQUAL(cwd_before, cwd_after, "pwd should not change current directory"); -} - -void test_echo_command() { - char *args[] = {"echo", "hello", "world", NULL}; - int result = trigger_echo(args); - ASSERT_TRUE(result, "echo should return true"); -} - -void test_echo_empty() { - char *args[] = {"echo", NULL}; - int result = trigger_echo(args); - ASSERT_TRUE(result, "echo with no args should return true"); -} - -void test_export_command() { - char *args[] = {"export", "TEST_VAR=hello", NULL}; - int result = trigger_export(args); - ASSERT_TRUE(result, "export should return true"); - ASSERT_STR_EQUAL("hello", getenv("TEST_VAR"), "exported variable should be available"); - unsetenv("TEST_VAR"); -} - -void test_export_no_value() { - char *args[] = {"export", "NOVAL", NULL}; - int result = trigger_export(args); - ASSERT_TRUE(result, "export of invalid format should still return true"); -} - -void test_unset_command() { - setenv("UNSET_VAR", "test_value", 1); - char *args[] = {"unset", "UNSET_VAR", NULL}; - int result = trigger_unset(args); - ASSERT_TRUE(result, "unset should return true"); - ASSERT_NULL(getenv("UNSET_VAR"), "unset variable should not be available"); -} - -int main() { - TEST_SUITE_START("Built-ins Module Tests"); - - test_builtin_arrays_match(); - test_cd_no_args(); - test_cd_to_tmp(); - test_help_command(); - test_exit_command(); - test_pwd_command(); - test_echo_command(); - test_echo_empty(); - test_export_command(); - test_export_no_value(); - test_unset_command(); - - TEST_SUITE_END(); -} - +#include "../include/builtins.h" +#include "test_framework.h" +#include +#include +#include +#include + +void test_builtin_arrays_match() { + int count = trigger_num_builtins(); + + ASSERT_TRUE(count == 7, "Should have 7 built-in commands"); + + int found_cd = 0, found_help = 0, found_exit = 0; + int found_pwd = 0, found_echo = 0, found_export = 0, found_unset = 0; + for (int i = 0; i < count; i++) { + if (strcmp(builtin_str[i], "cd") == 0) + found_cd = 1; + if (strcmp(builtin_str[i], "help") == 0) + found_help = 1; + if (strcmp(builtin_str[i], "exit") == 0) + found_exit = 1; + if (strcmp(builtin_str[i], "pwd") == 0) + found_pwd = 1; + if (strcmp(builtin_str[i], "echo") == 0) + found_echo = 1; + if (strcmp(builtin_str[i], "export") == 0) + found_export = 1; + if (strcmp(builtin_str[i], "unset") == 0) + found_unset = 1; + } + + ASSERT_TRUE(found_cd, "Should have 'cd' built-in"); + ASSERT_TRUE(found_help, "Should have 'help' built-in"); + ASSERT_TRUE(found_exit, "Should have 'exit' built-in"); + ASSERT_TRUE(found_pwd, "Should have 'pwd' built-in"); + ASSERT_TRUE(found_echo, "Should have 'echo' built-in"); + ASSERT_TRUE(found_export, "Should have 'export' built-in"); + ASSERT_TRUE(found_unset, "Should have 'unset' built-in"); +} + +void test_cd_no_args() { + char *args[] = {"cd", NULL}; + int result = trigger_cd(args); + + // Should return true (1) even when no directory provided + ASSERT_TRUE(result, "cd with no args should return true"); +} + +void test_cd_to_tmp() { + char cwd_before[1024]; + char cwd_after[1024]; + + getcwd(cwd_before, sizeof(cwd_before)); + + char *args[] = {"cd", "/tmp", NULL}; + int result = trigger_cd(args); + + getcwd(cwd_after, sizeof(cwd_after)); + + ASSERT_TRUE(result, "cd to /tmp should return true"); + ASSERT_STR_EQUAL("/tmp", cwd_after, "Should be in /tmp directory"); + + // Restore original directory + chdir(cwd_before); +} + +void test_help_command() { + char *args[] = {"help", NULL}; + int result = trigger_help(args); + + ASSERT_TRUE(result, "help command should return true"); +} + +void test_exit_command() { + char *args[] = {"exit", NULL}; + int result = trigger_exit(args); + + ASSERT_EQUAL(EXIT_SUCCESS, result, "exit command should return EXIT_SUCCESS"); +} + +void test_pwd_command() { + char *args[] = {"pwd", NULL}; + + char cwd_before[4096]; + getcwd(cwd_before, sizeof(cwd_before)); + + int result = trigger_pwd(args); + ASSERT_TRUE(result, "pwd should return true"); + + char cwd_after[4096]; + getcwd(cwd_after, sizeof(cwd_after)); + ASSERT_STR_EQUAL(cwd_before, cwd_after, "pwd should not change current directory"); +} + +void test_echo_command() { + char *args[] = {"echo", "hello", "world", NULL}; + int result = trigger_echo(args); + ASSERT_TRUE(result, "echo should return true"); +} + +void test_echo_empty() { + char *args[] = {"echo", NULL}; + int result = trigger_echo(args); + ASSERT_TRUE(result, "echo with no args should return true"); +} + +void test_export_command() { + char *args[] = {"export", "TEST_VAR=hello", NULL}; + int result = trigger_export(args); + ASSERT_TRUE(result, "export should return true"); + ASSERT_STR_EQUAL("hello", getenv("TEST_VAR"), "exported variable should be available"); + unsetenv("TEST_VAR"); +} + +void test_export_no_value() { + char *args[] = {"export", "NOVAL", NULL}; + int result = trigger_export(args); + ASSERT_TRUE(result, "export of invalid format should still return true"); +} + +void test_unset_command() { + setenv("UNSET_VAR", "test_value", 1); + char *args[] = {"unset", "UNSET_VAR", NULL}; + int result = trigger_unset(args); + ASSERT_TRUE(result, "unset should return true"); + ASSERT_NULL(getenv("UNSET_VAR"), "unset variable should not be available"); +} + +int main() { + TEST_SUITE_START("Built-ins Module Tests"); + + test_builtin_arrays_match(); + test_cd_no_args(); + test_cd_to_tmp(); + test_help_command(); + test_exit_command(); + test_pwd_command(); + test_echo_command(); + test_echo_empty(); + test_export_command(); + test_export_no_value(); + test_unset_command(); + + TEST_SUITE_END(); +} diff --git a/tests/test_execute.c b/tests/test_execute.c index df61f7e..9ffe1c0 100644 --- a/tests/test_execute.c +++ b/tests/test_execute.c @@ -1,56 +1,56 @@ -#include -#include -#include -#include "../include/execute.h" -#include "test_framework.h" - -void test_execute_empty_command() { - char *args[] = {NULL}; - char **a = args; - int result = trigger_execute(&a, NULL); - - ASSERT_TRUE(result, "Empty command should return true"); -} - -void test_execute_builtin_help() { - char *args[] = {"help", NULL}; - char **a = args; - int result = trigger_execute(&a, NULL); - - ASSERT_TRUE(result, "Execute 'help' should return true"); -} - -void test_execute_builtin_exit() { - char *args[] = {"exit", NULL}; - char **a = args; - int result = trigger_execute(&a, NULL); - - ASSERT_EQUAL(EXIT_SUCCESS, result, "Execute 'exit' should return EXIT_SUCCESS"); -} - -void test_execute_external_command() { - char *args[] = {"/bin/true", NULL}; - char **a = args; - int result = trigger_execute(&a, NULL); - - ASSERT_TRUE(result, "Execute '/bin/true' should return true"); -} - -void test_launch_simple_command() { - char *args[] = {"/bin/echo", "test", NULL}; - int result = trigger_launch(args); - - ASSERT_TRUE(result, "Launch '/bin/echo test' should return true"); -} - -int main() { - TEST_SUITE_START("Execute Module Tests"); - - test_execute_empty_command(); - test_execute_builtin_help(); - test_execute_builtin_exit(); - test_execute_external_command(); - test_launch_simple_command(); - - TEST_SUITE_END(); -} +#include "../include/execute.h" +#include "test_framework.h" +#include +#include +#include + +void test_execute_empty_command() { + char *args[] = {NULL}; + char **a = args; + int result = trigger_execute(&a, NULL); + + ASSERT_TRUE(result, "Empty command should return true"); +} + +void test_execute_builtin_help() { + char *args[] = {"help", NULL}; + char **a = args; + int result = trigger_execute(&a, NULL); + + ASSERT_TRUE(result, "Execute 'help' should return true"); +} + +void test_execute_builtin_exit() { + char *args[] = {"exit", NULL}; + char **a = args; + int result = trigger_execute(&a, NULL); + + ASSERT_EQUAL(EXIT_SUCCESS, result, "Execute 'exit' should return EXIT_SUCCESS"); +} + +void test_execute_external_command() { + char *args[] = {"/bin/true", NULL}; + char **a = args; + int result = trigger_execute(&a, NULL); + + ASSERT_TRUE(result, "Execute '/bin/true' should return true"); +} + +void test_launch_simple_command() { + char *args[] = {"/bin/echo", "test", NULL}; + int result = trigger_launch(args); + + ASSERT_TRUE(result, "Launch '/bin/echo test' should return true"); +} + +int main() { + TEST_SUITE_START("Execute Module Tests"); + + test_execute_empty_command(); + test_execute_builtin_help(); + test_execute_builtin_exit(); + test_execute_external_command(); + test_launch_simple_command(); + + TEST_SUITE_END(); +} diff --git a/tests/test_framework.h b/tests/test_framework.h index a40760c..6e8e360 100644 --- a/tests/test_framework.h +++ b/tests/test_framework.h @@ -16,49 +16,44 @@ static int tests_failed = 0; #define COLOR_RESET "\033[0m" // Assertion macros -#define ASSERT_TRUE(condition, message) \ - do { \ - tests_run++; \ - if (condition) { \ - tests_passed++; \ - printf(COLOR_GREEN "✓" COLOR_RESET " %s\n", message); \ - } else { \ - tests_failed++; \ - printf(COLOR_RED "✗" COLOR_RESET " %s (line %d)\n", message, __LINE__); \ - } \ - } while(0) - -#define ASSERT_FALSE(condition, message) \ - ASSERT_TRUE(!(condition), message) - -#define ASSERT_EQUAL(expected, actual, message) \ - ASSERT_TRUE((expected) == (actual), message) - -#define ASSERT_STR_EQUAL(expected, actual, message) \ +#define ASSERT_TRUE(condition, message) \ + do { \ + tests_run++; \ + if (condition) { \ + tests_passed++; \ + printf(COLOR_GREEN "✓" COLOR_RESET " %s\n", message); \ + } else { \ + tests_failed++; \ + printf(COLOR_RED "✗" COLOR_RESET " %s (line %d)\n", message, __LINE__); \ + } \ + } while (0) + +#define ASSERT_FALSE(condition, message) ASSERT_TRUE(!(condition), message) + +#define ASSERT_EQUAL(expected, actual, message) ASSERT_TRUE((expected) == (actual), message) + +#define ASSERT_STR_EQUAL(expected, actual, message) \ ASSERT_TRUE(strcmp((expected), (actual)) == 0, message) -#define ASSERT_NOT_NULL(ptr, message) \ - ASSERT_TRUE((ptr) != NULL, message) +#define ASSERT_NOT_NULL(ptr, message) ASSERT_TRUE((ptr) != NULL, message) -#define ASSERT_NULL(ptr, message) \ - ASSERT_TRUE((ptr) == NULL, message) +#define ASSERT_NULL(ptr, message) ASSERT_TRUE((ptr) == NULL, message) // Test suite management -#define TEST_SUITE_START(name) \ - printf("\n" COLOR_YELLOW "Running test suite: %s" COLOR_RESET "\n", name); \ - tests_run = 0; \ - tests_passed = 0; \ +#define TEST_SUITE_START(name) \ + printf("\n" COLOR_YELLOW "Running test suite: %s" COLOR_RESET "\n", name); \ + tests_run = 0; \ + tests_passed = 0; \ tests_failed = 0; -#define TEST_SUITE_END() \ - printf("\n"); \ - if (tests_failed == 0) { \ +#define TEST_SUITE_END() \ + printf("\n"); \ + if (tests_failed == 0) { \ printf(COLOR_GREEN "All tests passed! (%d/%d)" COLOR_RESET "\n", tests_passed, tests_run); \ - } else { \ + } else { \ printf(COLOR_RED "%d test(s) failed out of %d" COLOR_RESET "\n", tests_failed, tests_run); \ - } \ - printf("\n"); \ + } \ + printf("\n"); \ return tests_failed; #endif // TEST_FRAMEWORK_H - diff --git a/tests/test_glob.c b/tests/test_glob.c index f68f9ff..65edfa9 100644 --- a/tests/test_glob.c +++ b/tests/test_glob.c @@ -1,211 +1,227 @@ -#include -#include -#include -#include -#include -#include "../include/glob_expand.h" -#include "../include/input.h" -#include "../include/pipeline.h" -#include "../src/utils/utils.h" -#include "test_framework.h" - -static void setup_fixture() { - mkdir("/tmp/trigger_glob_test", 0755); - FILE *f = fopen("/tmp/trigger_glob_test/a.txt", "w"); if (f) fclose(f); - f = fopen("/tmp/trigger_glob_test/b.txt", "w"); if (f) fclose(f); - f = fopen("/tmp/trigger_glob_test/c.md", "w"); if (f) fclose(f); - f = fopen("/tmp/trigger_glob_test/d.dat", "w"); if (f) fclose(f); - f = fopen("/tmp/trigger_glob_test/ax.txt", "w"); if (f) fclose(f); -} - -static void teardown_fixture() { - unlink("/tmp/trigger_glob_test/a.txt"); - unlink("/tmp/trigger_glob_test/b.txt"); - unlink("/tmp/trigger_glob_test/c.md"); - unlink("/tmp/trigger_glob_test/d.dat"); - unlink("/tmp/trigger_glob_test/ax.txt"); - rmdir("/tmp/trigger_glob_test"); -} - -void test_glob_txt_files() { - setup_fixture(); - - int *glob_eligible = NULL; - char **args = trigger_split_line_ex("echo /tmp/trigger_glob_test/*.txt", &glob_eligible); - ASSERT_NOT_NULL(args, "args should not be NULL"); - ASSERT_NOT_NULL(glob_eligible, "glob_eligible should not be NULL"); - - ASSERT_TRUE(glob_eligible[1], "*.txt is glob-eligible (unquoted)"); - - char **expanded = expand_globs(args, &glob_eligible); - - int count = 0; - while (expanded[count] != NULL) count++; - ASSERT_EQUAL(4, count, "should have 4 entries: echo + a.txt + b.txt + ax.txt"); - - int found_a = 0, found_b = 0, found_ax = 0; - for (int i = 0; expanded[i] != NULL; i++) { - if (strstr(expanded[i], "a.txt")) found_a = 1; - if (strstr(expanded[i], "b.txt")) found_b = 1; - if (strstr(expanded[i], "ax.txt")) found_ax = 1; - } - ASSERT_TRUE(found_a, "should have a.txt in expansion"); - ASSERT_TRUE(found_b, "should have b.txt in expansion"); - ASSERT_TRUE(found_ax, "should have ax.txt in expansion"); - - ASSERT_NOT_NULL(glob_eligible, "glob_eligible should still be valid after expansion"); - - free(glob_eligible); - free_array_of_strings(expanded); - teardown_fixture(); -} - -void test_quoted_glob_not_expanded() { - setup_fixture(); - - int *glob_eligible = NULL; - char **args = trigger_split_line_ex("echo '/tmp/trigger_glob_test/*.txt'", &glob_eligible); - ASSERT_NOT_NULL(args, "args should not be NULL"); - - ASSERT_FALSE(glob_eligible[1], "quoted *.txt is NOT glob-eligible"); - - char **expanded = expand_globs(args, &glob_eligible); - - ASSERT_TRUE(expanded == args, "should return args unchanged (no eligible tokens)"); - - ASSERT_STR_EQUAL("/tmp/trigger_glob_test/*.txt", expanded[1], - "quoted glob should stay as literal"); - - free(glob_eligible); - free_array_of_strings(expanded); - teardown_fixture(); -} - -void test_no_match_pattern() { - setup_fixture(); - - int *glob_eligible = NULL; - char **args = trigger_split_line_ex("echo /tmp/trigger_glob_test/*.nonexistent", &glob_eligible); - ASSERT_NOT_NULL(args, "args should not be NULL"); - - char **expanded = expand_globs(args, &glob_eligible); - - ASSERT_STR_EQUAL("/tmp/trigger_glob_test/*.nonexistent", expanded[1], - "no-match pattern should pass through literally"); - - free(glob_eligible); - free_array_of_strings(expanded); - teardown_fixture(); -} - -void test_question_mark_glob() { - setup_fixture(); - - int *glob_eligible = NULL; - char **args = trigger_split_line_ex("echo /tmp/trigger_glob_test/?.txt", &glob_eligible); - ASSERT_NOT_NULL(args, "args should not be NULL"); - - char **expanded = expand_globs(args, &glob_eligible); - - ASSERT_STR_EQUAL("echo", expanded[0], "command is echo"); - ASSERT_STR_EQUAL("/tmp/trigger_glob_test/a.txt", expanded[1], "?.txt matches a.txt"); - ASSERT_STR_EQUAL("/tmp/trigger_glob_test/b.txt", expanded[2], "?.txt matches b.txt"); - ASSERT_NULL(expanded[3], "should not match ax.txt (two chars)"); - - free(glob_eligible); - free_array_of_strings(expanded); - teardown_fixture(); -} - -void test_no_glob_simple_args() { - int *glob_eligible = NULL; - char **args = trigger_split_line_ex("echo hello world", &glob_eligible); - ASSERT_NOT_NULL(args, "args should not be NULL"); - - char **expanded = expand_globs(args, &glob_eligible); - - ASSERT_TRUE(expanded == args, "args with no globs returns same pointer"); - - free(glob_eligible); - free_array_of_strings(expanded); -} - -void test_escaped_glob_not_expanded() { - setup_fixture(); - - int *glob_eligible = NULL; - char **args = trigger_split_line_ex("echo /tmp/trigger_glob_test/\\*.txt", &glob_eligible); - ASSERT_NOT_NULL(args, "args should not be NULL"); - - ASSERT_FALSE(glob_eligible[1], "backslash-escaped *.txt is NOT glob-eligible"); - - free(glob_eligible); - free_array_of_strings(args); - teardown_fixture(); -} - -void test_glob_eligible_after_expansion() { - setup_fixture(); - - int *glob_eligible = NULL; - char **args = trigger_split_line_ex("echo /tmp/trigger_glob_test/*.txt", &glob_eligible); - ASSERT_NOT_NULL(args, "args should not be NULL"); - - char **expanded = expand_globs(args, &glob_eligible); - - int count = 0; - while (expanded[count] != NULL) count++; - ASSERT_EQUAL(4, count, "should have 4 entries"); - - ASSERT_TRUE(glob_eligible[0], "echo should keep original eligibility=1"); - - for (int i = 1; i < 4; i++) { - ASSERT_FALSE(glob_eligible[i], "glob-expanded filenames should be eligible=0"); - } - - free(glob_eligible); - free_array_of_strings(expanded); - teardown_fixture(); -} - -void test_glob_with_pipe_operator() { - setup_fixture(); - - int *glob_eligible = NULL; - char **args = trigger_split_line_ex( - "echo /tmp/trigger_glob_test/*.txt | wc x", &glob_eligible); - ASSERT_NOT_NULL(args, "args should not be NULL"); - - char **expanded = expand_globs(args, &glob_eligible); - - int count = 0; - while (expanded[count] != NULL) count++; - - ASSERT_EQUAL(7, count, "should have 7 entries: echo a b ax | wc x"); - - ASSERT_STR_EQUAL("echo", expanded[0], "command is echo"); - ASSERT_STR_EQUAL("|", expanded[4], "pipe at index 4 after glob expansion"); - ASSERT_STR_EQUAL("wc", expanded[5], "wc at index 5"); - ASSERT_STR_EQUAL("x", expanded[6], "x at index 6"); - - ASSERT_TRUE(glob_eligible[4], "pipe operator after glob should be glob_eligible=1"); - - free(glob_eligible); - free_array_of_strings(expanded); - teardown_fixture(); -} - -int main() { - TEST_SUITE_START("Glob Expansion Tests"); - - test_glob_txt_files(); - test_quoted_glob_not_expanded(); - test_no_match_pattern(); - test_question_mark_glob(); - test_no_glob_simple_args(); - test_escaped_glob_not_expanded(); - test_glob_eligible_after_expansion(); - test_glob_with_pipe_operator(); - - TEST_SUITE_END(); -} +#include "../include/glob_expand.h" +#include "../include/input.h" +#include "../include/pipeline.h" +#include "../src/utils/utils.h" +#include "test_framework.h" +#include +#include +#include +#include +#include + +static void setup_fixture() { + mkdir("/tmp/trigger_glob_test", 0755); + FILE *f = fopen("/tmp/trigger_glob_test/a.txt", "w"); + if (f) + fclose(f); + f = fopen("/tmp/trigger_glob_test/b.txt", "w"); + if (f) + fclose(f); + f = fopen("/tmp/trigger_glob_test/c.md", "w"); + if (f) + fclose(f); + f = fopen("/tmp/trigger_glob_test/d.dat", "w"); + if (f) + fclose(f); + f = fopen("/tmp/trigger_glob_test/ax.txt", "w"); + if (f) + fclose(f); +} + +static void teardown_fixture() { + unlink("/tmp/trigger_glob_test/a.txt"); + unlink("/tmp/trigger_glob_test/b.txt"); + unlink("/tmp/trigger_glob_test/c.md"); + unlink("/tmp/trigger_glob_test/d.dat"); + unlink("/tmp/trigger_glob_test/ax.txt"); + rmdir("/tmp/trigger_glob_test"); +} + +void test_glob_txt_files() { + setup_fixture(); + + int *glob_eligible = NULL; + char **args = trigger_split_line_ex("echo /tmp/trigger_glob_test/*.txt", &glob_eligible); + ASSERT_NOT_NULL(args, "args should not be NULL"); + ASSERT_NOT_NULL(glob_eligible, "glob_eligible should not be NULL"); + + ASSERT_TRUE(glob_eligible[1], "*.txt is glob-eligible (unquoted)"); + + char **expanded = expand_globs(args, &glob_eligible); + + int count = 0; + while (expanded[count] != NULL) + count++; + ASSERT_EQUAL(4, count, "should have 4 entries: echo + a.txt + b.txt + ax.txt"); + + int found_a = 0, found_b = 0, found_ax = 0; + for (int i = 0; expanded[i] != NULL; i++) { + if (strstr(expanded[i], "a.txt")) + found_a = 1; + if (strstr(expanded[i], "b.txt")) + found_b = 1; + if (strstr(expanded[i], "ax.txt")) + found_ax = 1; + } + ASSERT_TRUE(found_a, "should have a.txt in expansion"); + ASSERT_TRUE(found_b, "should have b.txt in expansion"); + ASSERT_TRUE(found_ax, "should have ax.txt in expansion"); + + ASSERT_NOT_NULL(glob_eligible, "glob_eligible should still be valid after expansion"); + + free(glob_eligible); + free_array_of_strings(expanded); + teardown_fixture(); +} + +void test_quoted_glob_not_expanded() { + setup_fixture(); + + int *glob_eligible = NULL; + char **args = trigger_split_line_ex("echo '/tmp/trigger_glob_test/*.txt'", &glob_eligible); + ASSERT_NOT_NULL(args, "args should not be NULL"); + + ASSERT_FALSE(glob_eligible[1], "quoted *.txt is NOT glob-eligible"); + + char **expanded = expand_globs(args, &glob_eligible); + + ASSERT_TRUE(expanded == args, "should return args unchanged (no eligible tokens)"); + + ASSERT_STR_EQUAL("/tmp/trigger_glob_test/*.txt", expanded[1], + "quoted glob should stay as literal"); + + free(glob_eligible); + free_array_of_strings(expanded); + teardown_fixture(); +} + +void test_no_match_pattern() { + setup_fixture(); + + int *glob_eligible = NULL; + char **args = + trigger_split_line_ex("echo /tmp/trigger_glob_test/*.nonexistent", &glob_eligible); + ASSERT_NOT_NULL(args, "args should not be NULL"); + + char **expanded = expand_globs(args, &glob_eligible); + + ASSERT_STR_EQUAL("/tmp/trigger_glob_test/*.nonexistent", expanded[1], + "no-match pattern should pass through literally"); + + free(glob_eligible); + free_array_of_strings(expanded); + teardown_fixture(); +} + +void test_question_mark_glob() { + setup_fixture(); + + int *glob_eligible = NULL; + char **args = trigger_split_line_ex("echo /tmp/trigger_glob_test/?.txt", &glob_eligible); + ASSERT_NOT_NULL(args, "args should not be NULL"); + + char **expanded = expand_globs(args, &glob_eligible); + + ASSERT_STR_EQUAL("echo", expanded[0], "command is echo"); + ASSERT_STR_EQUAL("/tmp/trigger_glob_test/a.txt", expanded[1], "?.txt matches a.txt"); + ASSERT_STR_EQUAL("/tmp/trigger_glob_test/b.txt", expanded[2], "?.txt matches b.txt"); + ASSERT_NULL(expanded[3], "should not match ax.txt (two chars)"); + + free(glob_eligible); + free_array_of_strings(expanded); + teardown_fixture(); +} + +void test_no_glob_simple_args() { + int *glob_eligible = NULL; + char **args = trigger_split_line_ex("echo hello world", &glob_eligible); + ASSERT_NOT_NULL(args, "args should not be NULL"); + + char **expanded = expand_globs(args, &glob_eligible); + + ASSERT_TRUE(expanded == args, "args with no globs returns same pointer"); + + free(glob_eligible); + free_array_of_strings(expanded); +} + +void test_escaped_glob_not_expanded() { + setup_fixture(); + + int *glob_eligible = NULL; + char **args = trigger_split_line_ex("echo /tmp/trigger_glob_test/\\*.txt", &glob_eligible); + ASSERT_NOT_NULL(args, "args should not be NULL"); + + ASSERT_FALSE(glob_eligible[1], "backslash-escaped *.txt is NOT glob-eligible"); + + free(glob_eligible); + free_array_of_strings(args); + teardown_fixture(); +} + +void test_glob_eligible_after_expansion() { + setup_fixture(); + + int *glob_eligible = NULL; + char **args = trigger_split_line_ex("echo /tmp/trigger_glob_test/*.txt", &glob_eligible); + ASSERT_NOT_NULL(args, "args should not be NULL"); + + char **expanded = expand_globs(args, &glob_eligible); + + int count = 0; + while (expanded[count] != NULL) + count++; + ASSERT_EQUAL(4, count, "should have 4 entries"); + + ASSERT_TRUE(glob_eligible[0], "echo should keep original eligibility=1"); + + for (int i = 1; i < 4; i++) { + ASSERT_FALSE(glob_eligible[i], "glob-expanded filenames should be eligible=0"); + } + + free(glob_eligible); + free_array_of_strings(expanded); + teardown_fixture(); +} + +void test_glob_with_pipe_operator() { + setup_fixture(); + + int *glob_eligible = NULL; + char **args = trigger_split_line_ex("echo /tmp/trigger_glob_test/*.txt | wc x", &glob_eligible); + ASSERT_NOT_NULL(args, "args should not be NULL"); + + char **expanded = expand_globs(args, &glob_eligible); + + int count = 0; + while (expanded[count] != NULL) + count++; + + ASSERT_EQUAL(7, count, "should have 7 entries: echo a b ax | wc x"); + + ASSERT_STR_EQUAL("echo", expanded[0], "command is echo"); + ASSERT_STR_EQUAL("|", expanded[4], "pipe at index 4 after glob expansion"); + ASSERT_STR_EQUAL("wc", expanded[5], "wc at index 5"); + ASSERT_STR_EQUAL("x", expanded[6], "x at index 6"); + + ASSERT_TRUE(glob_eligible[4], "pipe operator after glob should be glob_eligible=1"); + + free(glob_eligible); + free_array_of_strings(expanded); + teardown_fixture(); +} + +int main() { + TEST_SUITE_START("Glob Expansion Tests"); + + test_glob_txt_files(); + test_quoted_glob_not_expanded(); + test_no_match_pattern(); + test_question_mark_glob(); + test_no_glob_simple_args(); + test_escaped_glob_not_expanded(); + test_glob_eligible_after_expansion(); + test_glob_with_pipe_operator(); + + TEST_SUITE_END(); +} diff --git a/tests/test_input.c b/tests/test_input.c index 5dfc614..4d6ad03 100644 --- a/tests/test_input.c +++ b/tests/test_input.c @@ -1,9 +1,9 @@ +#include "../include/input.h" +#include "../src/utils/utils.h" +#include "test_framework.h" #include #include #include -#include "../include/input.h" -#include "test_framework.h" -#include "../src/utils/utils.h" void test_split_line_basic() { char *line = strdup("ls -la /home"); @@ -78,7 +78,8 @@ void test_split_line_double_quotes_basic() { ASSERT_NOT_NULL(tokens, "trigger_split_line should return non-NULL"); ASSERT_STR_EQUAL("echo", tokens[0], "First token should be 'echo'"); - ASSERT_STR_EQUAL("hello world", tokens[1], "Second token should be 'hello world' (with space preserved)"); + ASSERT_STR_EQUAL("hello world", tokens[1], + "Second token should be 'hello world' (with space preserved)"); ASSERT_NULL(tokens[2], "Third token should be NULL"); free_array_of_strings(tokens); @@ -131,7 +132,8 @@ void test_split_line_double_quotes_concatenated() { ASSERT_NOT_NULL(tokens, "trigger_split_line should return non-NULL"); ASSERT_STR_EQUAL("echo", tokens[0], "First token should be 'echo'"); - ASSERT_STR_EQUAL("helloworldtest", tokens[1], "Second token should be 'helloworldtest' (concatenated)"); + ASSERT_STR_EQUAL("helloworldtest", tokens[1], + "Second token should be 'helloworldtest' (concatenated)"); ASSERT_NULL(tokens[2], "Third token should be NULL"); free_array_of_strings(tokens); @@ -146,7 +148,8 @@ void test_split_line_single_quotes_basic() { ASSERT_NOT_NULL(tokens, "trigger_split_line should return non-NULL"); ASSERT_STR_EQUAL("echo", tokens[0], "First token should be 'echo'"); - ASSERT_STR_EQUAL("hello world", tokens[1], "Second token should be 'hello world' (with space preserved)"); + ASSERT_STR_EQUAL("hello world", tokens[1], + "Second token should be 'hello world' (with space preserved)"); ASSERT_NULL(tokens[2], "Third token should be NULL"); free_array_of_strings(tokens); @@ -172,7 +175,8 @@ void test_split_line_single_quotes_with_double_quotes() { ASSERT_NOT_NULL(tokens, "trigger_split_line should return non-NULL"); ASSERT_STR_EQUAL("echo", tokens[0], "First token should be 'echo'"); - ASSERT_STR_EQUAL("hello \"world\"", tokens[1], "Second token should preserve double quotes inside single quotes"); + ASSERT_STR_EQUAL("hello \"world\"", tokens[1], + "Second token should preserve double quotes inside single quotes"); ASSERT_NULL(tokens[2], "Third token should be NULL"); free_array_of_strings(tokens); @@ -200,7 +204,8 @@ void test_split_line_backslash_space() { ASSERT_NOT_NULL(tokens, "trigger_split_line should return non-NULL"); ASSERT_STR_EQUAL("echo", tokens[0], "First token should be 'echo'"); - ASSERT_STR_EQUAL("hello world", tokens[1], "Second token should be 'hello world' (escaped space)"); + ASSERT_STR_EQUAL("hello world", tokens[1], + "Second token should be 'hello world' (escaped space)"); ASSERT_NULL(tokens[2], "Third token should be NULL"); free_array_of_strings(tokens); @@ -293,7 +298,8 @@ void test_split_line_mixed_quotes() { ASSERT_NOT_NULL(tokens, "trigger_split_line should return non-NULL"); ASSERT_STR_EQUAL("echo", tokens[0], "First token should be 'echo'"); - ASSERT_STR_EQUAL("hello 'world'", tokens[1], "Second token should preserve single quotes in double quotes"); + ASSERT_STR_EQUAL("hello 'world'", tokens[1], + "Second token should preserve single quotes in double quotes"); ASSERT_NULL(tokens[2], "Third token should be NULL"); free_array_of_strings(tokens); @@ -306,7 +312,8 @@ void test_split_line_mixed_quotes_reversed() { ASSERT_NOT_NULL(tokens, "trigger_split_line should return non-NULL"); ASSERT_STR_EQUAL("echo", tokens[0], "First token should be 'echo'"); - ASSERT_STR_EQUAL("hello \"world\"", tokens[1], "Second token should preserve double quotes in single quotes"); + ASSERT_STR_EQUAL("hello \"world\"", tokens[1], + "Second token should preserve double quotes in single quotes"); ASSERT_NULL(tokens[2], "Third token should be NULL"); free_array_of_strings(tokens); @@ -388,7 +395,8 @@ void test_split_line_backslash_at_end() { ASSERT_NOT_NULL(tokens, "trigger_split_line should return non-NULL"); ASSERT_STR_EQUAL("echo", tokens[0], "First token should be 'echo'"); - ASSERT_STR_EQUAL("test", tokens[1], "Second token should be 'test' (trailing backslash ignored or handled)"); + ASSERT_STR_EQUAL("test", tokens[1], + "Second token should be 'test' (trailing backslash ignored or handled)"); ASSERT_NULL(tokens[2], "Third token should be NULL"); free_array_of_strings(tokens); @@ -414,7 +422,8 @@ void test_split_line_path_with_spaces() { ASSERT_NOT_NULL(tokens, "trigger_split_line should return non-NULL"); ASSERT_STR_EQUAL("cd", tokens[0], "First token should be 'cd'"); - ASSERT_STR_EQUAL("/home/user/my documents", tokens[1], "Second token should preserve path with spaces"); + ASSERT_STR_EQUAL("/home/user/my documents", tokens[1], + "Second token should preserve path with spaces"); ASSERT_NULL(tokens[2], "Third token should be NULL"); free_array_of_strings(tokens); @@ -483,4 +492,3 @@ int main() { TEST_SUITE_END(); } - diff --git a/tests/test_pipeline.c b/tests/test_pipeline.c index 2b19671..2590b86 100644 --- a/tests/test_pipeline.c +++ b/tests/test_pipeline.c @@ -1,224 +1,224 @@ -#include -#include -#include -#include -#include -#include "../include/pipeline.h" -#include "../include/execute.h" -#include "../include/input.h" -#include "../include/builtins.h" -#include "../src/utils/utils.h" -#include "test_framework.h" - -static char **make_tokens(const char *input) { - extern char **parse_line_with_quotes(const char *line, int **out_glob_eligible); - return parse_line_with_quotes(input, NULL); -} - -static void free_parse_result(PipelineStage *stages, int num_stages) { - for (int i = 0; i < num_stages; i++) { - free(stages[i].infile); - free(stages[i].outfile); - if (stages[i].argv != NULL) { - free_array_of_strings(stages[i].argv); - } - } - free(stages); -} - -static int run_command(const char *input) { - int *glob_eligible = NULL; - char **args = trigger_split_line_ex(input, &glob_eligible); - ASSERT_NOT_NULL(args, input); - int result = trigger_execute(&args, &glob_eligible); - free(glob_eligible); - if (args != NULL) { - free_array_of_strings(args); - } - return result; -} - -void test_parse_simple_pipe() { - char **tokens = make_tokens("ls | grep src"); - ASSERT_NOT_NULL(tokens, "tokens should not be NULL"); - - int num_stages = 0; - PipelineStage *stages = trigger_parse_pipeline(tokens, &num_stages); - free(tokens); - - ASSERT_EQUAL(2, num_stages, "should have 2 stages"); - ASSERT_STR_EQUAL("ls", stages[0].argv[0], "first stage command is ls"); - ASSERT_NULL(stages[0].argv[1], "first stage has one arg"); - ASSERT_STR_EQUAL("grep", stages[1].argv[0], "second stage command is grep"); - ASSERT_STR_EQUAL("src", stages[1].argv[1], "second stage has src arg"); - ASSERT_NULL(stages[1].argv[2], "second stage has two args"); - - free_parse_result(stages, num_stages); -} - -void test_parse_redirect_out() { - char **tokens = make_tokens("echo hello > /tmp/out.txt"); - ASSERT_NOT_NULL(tokens, "tokens should not be NULL"); - - int num_stages = 0; - PipelineStage *stages = trigger_parse_pipeline(tokens, &num_stages); - free(tokens); - - ASSERT_EQUAL(1, num_stages, "should have 1 stage"); - ASSERT_STR_EQUAL("echo", stages[0].argv[0], "command is echo"); - ASSERT_STR_EQUAL("hello", stages[0].argv[1], "arg is hello"); - ASSERT_NULL(stages[0].argv[2], "two args"); - ASSERT_STR_EQUAL("/tmp/out.txt", stages[0].outfile, "outfile is /tmp/out.txt"); - ASSERT_EQUAL(0, stages[0].append, "append should be 0"); - ASSERT_NULL(stages[0].infile, "no infile"); - - free_parse_result(stages, num_stages); -} - -void test_parse_redirect_append() { - char **tokens = make_tokens("echo hello >> /tmp/out.txt"); - ASSERT_NOT_NULL(tokens, "tokens should not be NULL"); - - int num_stages = 0; - PipelineStage *stages = trigger_parse_pipeline(tokens, &num_stages); - free(tokens); - - ASSERT_EQUAL(1, num_stages, "should have 1 stage"); - ASSERT_STR_EQUAL("/tmp/out.txt", stages[0].outfile, "outfile is /tmp/out.txt"); - ASSERT_EQUAL(1, stages[0].append, "append should be 1"); - - free_parse_result(stages, num_stages); -} - -void test_parse_redirect_in() { - char **tokens = make_tokens("wc -l < /tmp/input.txt"); - ASSERT_NOT_NULL(tokens, "tokens should not be NULL"); - - int num_stages = 0; - PipelineStage *stages = trigger_parse_pipeline(tokens, &num_stages); - free(tokens); - - ASSERT_EQUAL(1, num_stages, "should have 1 stage"); - ASSERT_STR_EQUAL("wc", stages[0].argv[0], "command is wc"); - ASSERT_STR_EQUAL("-l", stages[0].argv[1], "arg is -l"); - ASSERT_NULL(stages[0].argv[2], "two args"); - ASSERT_STR_EQUAL("/tmp/input.txt", stages[0].infile, "infile is /tmp/input.txt"); - ASSERT_NULL(stages[0].outfile, "no outfile"); - - free_parse_result(stages, num_stages); -} - -void test_parse_pipe_with_redirect() { - char **tokens = make_tokens("cat < /etc/hostname | wc -c > /tmp/count.txt"); - ASSERT_NOT_NULL(tokens, "tokens should not be NULL"); - - int num_stages = 0; - PipelineStage *stages = trigger_parse_pipeline(tokens, &num_stages); - free(tokens); - - ASSERT_EQUAL(2, num_stages, "should have 2 stages"); - ASSERT_STR_EQUAL("cat", stages[0].argv[0], "first stage: cat"); - ASSERT_STR_EQUAL("/etc/hostname", stages[0].infile, "first stage: infile"); - ASSERT_NULL(stages[0].outfile, "first stage: no outfile"); - ASSERT_STR_EQUAL("wc", stages[1].argv[0], "second stage: wc"); - ASSERT_STR_EQUAL("-c", stages[1].argv[1], "second stage: -c"); - ASSERT_STR_EQUAL("/tmp/count.txt", stages[1].outfile, "second stage: outfile"); - ASSERT_NULL(stages[1].infile, "second stage: no infile"); - - free_parse_result(stages, num_stages); -} - -void test_e2e_redirect_out() { - unlink("/tmp/trigger_t_e2e_out"); - - int result = run_command("/bin/echo test_output > /tmp/trigger_t_e2e_out"); - ASSERT_TRUE(result, "echo with redirect should return true"); - - FILE *f = fopen("/tmp/trigger_t_e2e_out", "r"); - ASSERT_NOT_NULL(f, "output file should exist"); - - char buf[256]; - ASSERT_NOT_NULL(fgets(buf, sizeof(buf), f), "should read from output file"); - fclose(f); - - ASSERT_STR_EQUAL("test_output\n", buf, "output file should contain test_output"); - unlink("/tmp/trigger_t_e2e_out"); -} - -void test_e2e_redirect_append() { - unlink("/tmp/trigger_t_e2e_append"); - - int result = run_command("/bin/echo first > /tmp/trigger_t_e2e_append"); - ASSERT_TRUE(result, "first redirect should succeed"); - - result = run_command("/bin/echo second >> /tmp/trigger_t_e2e_append"); - ASSERT_TRUE(result, "append redirect should succeed"); - - FILE *f = fopen("/tmp/trigger_t_e2e_append", "r"); - ASSERT_NOT_NULL(f, "append file should exist"); - - char b1[256], b2[256]; - ASSERT_NOT_NULL(fgets(b1, sizeof(b1), f), "should read first line"); - ASSERT_NOT_NULL(fgets(b2, sizeof(b2), f), "should read second line"); - fclose(f); - - ASSERT_STR_EQUAL("first\n", b1, "first line should be first"); - ASSERT_STR_EQUAL("second\n", b2, "second line should be second"); - unlink("/tmp/trigger_t_e2e_append"); -} - -void test_e2e_redirect_in() { - FILE *f = fopen("/tmp/trigger_t_e2e_in", "w"); - fprintf(f, "hello\nworld\n"); - fclose(f); - - int result = run_command("/usr/bin/wc -l < /tmp/trigger_t_e2e_in"); - ASSERT_TRUE(result, "input redirect should return true"); - unlink("/tmp/trigger_t_e2e_in"); -} - -void test_e2e_pipe() { - int result = run_command("/bin/echo hello | /bin/cat"); - ASSERT_TRUE(result, "pipe should succeed"); -} - -void test_e2e_builtin_in_pipeline() { - char cwd_before[4096]; - ASSERT_NOT_NULL(getcwd(cwd_before, sizeof(cwd_before)), "getcwd should succeed"); - - int result = run_command("cd /tmp | /bin/true"); - ASSERT_TRUE(result, "cd in pipe should succeed"); - - char cwd_after[4096]; - ASSERT_NOT_NULL(getcwd(cwd_after, sizeof(cwd_after)), "getcwd should succeed"); - ASSERT_STR_EQUAL(cwd_before, cwd_after, "cd in pipeline should not affect parent cwd"); -} - -void test_quoted_pipe_not_operator() { - int result = run_command("/bin/echo \"|\""); - ASSERT_TRUE(result, "echo of quoted pipe char should not be parsed as pipeline"); -} - -void test_quoted_redirect_not_operator() { - int result = run_command("/bin/echo \">\""); - ASSERT_TRUE(result, "echo of quoted > should not be parsed as redirect"); -} - -int main() { - TEST_SUITE_START("Pipeline Tests"); - - test_parse_simple_pipe(); - test_parse_redirect_out(); - test_parse_redirect_append(); - test_parse_redirect_in(); - test_parse_pipe_with_redirect(); - test_e2e_redirect_out(); - test_e2e_redirect_append(); - test_e2e_redirect_in(); - test_e2e_pipe(); - test_e2e_builtin_in_pipeline(); - test_quoted_pipe_not_operator(); - test_quoted_redirect_not_operator(); - - TEST_SUITE_END(); -} +#include "../include/builtins.h" +#include "../include/execute.h" +#include "../include/input.h" +#include "../include/pipeline.h" +#include "../src/utils/utils.h" +#include "test_framework.h" +#include +#include +#include +#include +#include + +static char **make_tokens(const char *input) { + extern char **parse_line_with_quotes(const char *line, int **out_glob_eligible); + return parse_line_with_quotes(input, NULL); +} + +static void free_parse_result(PipelineStage *stages, int num_stages) { + for (int i = 0; i < num_stages; i++) { + free(stages[i].infile); + free(stages[i].outfile); + if (stages[i].argv != NULL) { + free_array_of_strings(stages[i].argv); + } + } + free(stages); +} + +static int run_command(const char *input) { + int *glob_eligible = NULL; + char **args = trigger_split_line_ex(input, &glob_eligible); + ASSERT_NOT_NULL(args, input); + int result = trigger_execute(&args, &glob_eligible); + free(glob_eligible); + if (args != NULL) { + free_array_of_strings(args); + } + return result; +} + +void test_parse_simple_pipe() { + char **tokens = make_tokens("ls | grep src"); + ASSERT_NOT_NULL(tokens, "tokens should not be NULL"); + + int num_stages = 0; + PipelineStage *stages = trigger_parse_pipeline(tokens, &num_stages); + free(tokens); + + ASSERT_EQUAL(2, num_stages, "should have 2 stages"); + ASSERT_STR_EQUAL("ls", stages[0].argv[0], "first stage command is ls"); + ASSERT_NULL(stages[0].argv[1], "first stage has one arg"); + ASSERT_STR_EQUAL("grep", stages[1].argv[0], "second stage command is grep"); + ASSERT_STR_EQUAL("src", stages[1].argv[1], "second stage has src arg"); + ASSERT_NULL(stages[1].argv[2], "second stage has two args"); + + free_parse_result(stages, num_stages); +} + +void test_parse_redirect_out() { + char **tokens = make_tokens("echo hello > /tmp/out.txt"); + ASSERT_NOT_NULL(tokens, "tokens should not be NULL"); + + int num_stages = 0; + PipelineStage *stages = trigger_parse_pipeline(tokens, &num_stages); + free(tokens); + + ASSERT_EQUAL(1, num_stages, "should have 1 stage"); + ASSERT_STR_EQUAL("echo", stages[0].argv[0], "command is echo"); + ASSERT_STR_EQUAL("hello", stages[0].argv[1], "arg is hello"); + ASSERT_NULL(stages[0].argv[2], "two args"); + ASSERT_STR_EQUAL("/tmp/out.txt", stages[0].outfile, "outfile is /tmp/out.txt"); + ASSERT_EQUAL(0, stages[0].append, "append should be 0"); + ASSERT_NULL(stages[0].infile, "no infile"); + + free_parse_result(stages, num_stages); +} + +void test_parse_redirect_append() { + char **tokens = make_tokens("echo hello >> /tmp/out.txt"); + ASSERT_NOT_NULL(tokens, "tokens should not be NULL"); + + int num_stages = 0; + PipelineStage *stages = trigger_parse_pipeline(tokens, &num_stages); + free(tokens); + + ASSERT_EQUAL(1, num_stages, "should have 1 stage"); + ASSERT_STR_EQUAL("/tmp/out.txt", stages[0].outfile, "outfile is /tmp/out.txt"); + ASSERT_EQUAL(1, stages[0].append, "append should be 1"); + + free_parse_result(stages, num_stages); +} + +void test_parse_redirect_in() { + char **tokens = make_tokens("wc -l < /tmp/input.txt"); + ASSERT_NOT_NULL(tokens, "tokens should not be NULL"); + + int num_stages = 0; + PipelineStage *stages = trigger_parse_pipeline(tokens, &num_stages); + free(tokens); + + ASSERT_EQUAL(1, num_stages, "should have 1 stage"); + ASSERT_STR_EQUAL("wc", stages[0].argv[0], "command is wc"); + ASSERT_STR_EQUAL("-l", stages[0].argv[1], "arg is -l"); + ASSERT_NULL(stages[0].argv[2], "two args"); + ASSERT_STR_EQUAL("/tmp/input.txt", stages[0].infile, "infile is /tmp/input.txt"); + ASSERT_NULL(stages[0].outfile, "no outfile"); + + free_parse_result(stages, num_stages); +} + +void test_parse_pipe_with_redirect() { + char **tokens = make_tokens("cat < /etc/hostname | wc -c > /tmp/count.txt"); + ASSERT_NOT_NULL(tokens, "tokens should not be NULL"); + + int num_stages = 0; + PipelineStage *stages = trigger_parse_pipeline(tokens, &num_stages); + free(tokens); + + ASSERT_EQUAL(2, num_stages, "should have 2 stages"); + ASSERT_STR_EQUAL("cat", stages[0].argv[0], "first stage: cat"); + ASSERT_STR_EQUAL("/etc/hostname", stages[0].infile, "first stage: infile"); + ASSERT_NULL(stages[0].outfile, "first stage: no outfile"); + ASSERT_STR_EQUAL("wc", stages[1].argv[0], "second stage: wc"); + ASSERT_STR_EQUAL("-c", stages[1].argv[1], "second stage: -c"); + ASSERT_STR_EQUAL("/tmp/count.txt", stages[1].outfile, "second stage: outfile"); + ASSERT_NULL(stages[1].infile, "second stage: no infile"); + + free_parse_result(stages, num_stages); +} + +void test_e2e_redirect_out() { + unlink("/tmp/trigger_t_e2e_out"); + + int result = run_command("/bin/echo test_output > /tmp/trigger_t_e2e_out"); + ASSERT_TRUE(result, "echo with redirect should return true"); + + FILE *f = fopen("/tmp/trigger_t_e2e_out", "r"); + ASSERT_NOT_NULL(f, "output file should exist"); + + char buf[256]; + ASSERT_NOT_NULL(fgets(buf, sizeof(buf), f), "should read from output file"); + fclose(f); + + ASSERT_STR_EQUAL("test_output\n", buf, "output file should contain test_output"); + unlink("/tmp/trigger_t_e2e_out"); +} + +void test_e2e_redirect_append() { + unlink("/tmp/trigger_t_e2e_append"); + + int result = run_command("/bin/echo first > /tmp/trigger_t_e2e_append"); + ASSERT_TRUE(result, "first redirect should succeed"); + + result = run_command("/bin/echo second >> /tmp/trigger_t_e2e_append"); + ASSERT_TRUE(result, "append redirect should succeed"); + + FILE *f = fopen("/tmp/trigger_t_e2e_append", "r"); + ASSERT_NOT_NULL(f, "append file should exist"); + + char b1[256], b2[256]; + ASSERT_NOT_NULL(fgets(b1, sizeof(b1), f), "should read first line"); + ASSERT_NOT_NULL(fgets(b2, sizeof(b2), f), "should read second line"); + fclose(f); + + ASSERT_STR_EQUAL("first\n", b1, "first line should be first"); + ASSERT_STR_EQUAL("second\n", b2, "second line should be second"); + unlink("/tmp/trigger_t_e2e_append"); +} + +void test_e2e_redirect_in() { + FILE *f = fopen("/tmp/trigger_t_e2e_in", "w"); + fprintf(f, "hello\nworld\n"); + fclose(f); + + int result = run_command("/usr/bin/wc -l < /tmp/trigger_t_e2e_in"); + ASSERT_TRUE(result, "input redirect should return true"); + unlink("/tmp/trigger_t_e2e_in"); +} + +void test_e2e_pipe() { + int result = run_command("/bin/echo hello | /bin/cat"); + ASSERT_TRUE(result, "pipe should succeed"); +} + +void test_e2e_builtin_in_pipeline() { + char cwd_before[4096]; + ASSERT_NOT_NULL(getcwd(cwd_before, sizeof(cwd_before)), "getcwd should succeed"); + + int result = run_command("cd /tmp | /bin/true"); + ASSERT_TRUE(result, "cd in pipe should succeed"); + + char cwd_after[4096]; + ASSERT_NOT_NULL(getcwd(cwd_after, sizeof(cwd_after)), "getcwd should succeed"); + ASSERT_STR_EQUAL(cwd_before, cwd_after, "cd in pipeline should not affect parent cwd"); +} + +void test_quoted_pipe_not_operator() { + int result = run_command("/bin/echo \"|\""); + ASSERT_TRUE(result, "echo of quoted pipe char should not be parsed as pipeline"); +} + +void test_quoted_redirect_not_operator() { + int result = run_command("/bin/echo \">\""); + ASSERT_TRUE(result, "echo of quoted > should not be parsed as redirect"); +} + +int main() { + TEST_SUITE_START("Pipeline Tests"); + + test_parse_simple_pipe(); + test_parse_redirect_out(); + test_parse_redirect_append(); + test_parse_redirect_in(); + test_parse_pipe_with_redirect(); + test_e2e_redirect_out(); + test_e2e_redirect_append(); + test_e2e_redirect_in(); + test_e2e_pipe(); + test_e2e_builtin_in_pipeline(); + test_quoted_pipe_not_operator(); + test_quoted_redirect_not_operator(); + + TEST_SUITE_END(); +}