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
+
+[](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();
+}