diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..7ed8115 --- /dev/null +++ b/.clang-format @@ -0,0 +1,79 @@ +AccessModifierOffset: -4 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlines: Right +AlignOperands: false +AlignTrailingComments: false +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: None +AllowShortIfStatementsOnASingleLine: false +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: Yes +BinPackArguments: true +BinPackParameters: true +BreakBeforeBraces: Custom +BraceWrapping: + AfterEnum: true + AfterClass: true + AfterControlStatement: true + AfterFunction: true + AfterNamespace: true + AfterStruct: true + AfterUnion: true + AfterExternBlock: true + BeforeCatch: true + BeforeElse: true + IndentBraces: false + SplitEmptyFunction: false + SplitEmptyRecord: false + SplitEmptyNamespace: false +BreakBeforeBinaryOperators: NonAssignment +BreakBeforeTernaryOperators: true +BreakConstructorInitializers: BeforeComma +BreakInheritanceList: BeforeComma +BreakStringLiterals: true +ColumnLimit: 80 +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ConstructorInitializerIndentWidth: 4 +Cpp11BracedListStyle: false +DerivePointerAlignment: false +FixNamespaceComments: true +ForEachMacros: ['ILIST_FOREACH', 'ILIST_FOREACH_ENTRY'] +IncludeBlocks: Regroup +IncludeCategories: + - Regex: '<.*>' + Priority: 1 + - Regex: '.*' + Priority: 2 +IndentCaseLabels: false +IndentPPDirectives: AfterHash +IndentWidth: 4 +IndentWrappedFunctionNames: false +KeepEmptyLinesAtTheStartOfBlocks: false +Language: Cpp +NamespaceIndentation: All +PointerAlignment: Right +ReflowComments: true +SortIncludes: true +SortUsingDeclarations: false +SpaceAfterCStyleCast: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInCStyleCastParentheses: false +SpacesInContainerLiterals: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +TabWidth: 4 +UseTab: Never diff --git a/epitar/Makefile b/epitar/Makefile index b3430bf..8d614a7 100644 --- a/epitar/Makefile +++ b/epitar/Makefile @@ -27,7 +27,7 @@ debug: $(OBJS) $(CC) -o $(TARGET) $^ $(LDFLAGS) $(LDLIBS) check: - dash ./tests/run.sh + bash ./tests/functional/run.sh clean: $(RM) $(TARGET) diff --git a/epitar/src/main.c b/epitar/src/main.c index 3e1f5d9..5126471 100644 --- a/epitar/src/main.c +++ b/epitar/src/main.c @@ -1,6 +1,7 @@ #include "tar/archive.h" #include "tar/extract.h" #include "utils/config.h" +#include "utils/errors.h" int main(int argc, char **argv) { @@ -31,22 +32,46 @@ int main(int argc, char **argv) err = archive(config.archive_file, config.input_files, config.verbose); } + int ret_code = 0; switch (err) { case SUCCESS: - return 0; + ret_code = 0; + break; - // case FILE_NOT_FOUND: - // case UNKNOWN_ERROR: - // printf("epitar: error extracting tarball %s\n", config.archive_file); - // return 3; + // case UNKNOWN_ERROR: + // printf("epitar: error extracting tarball %s\n", + // config.archive_file); ret_code = 3; break; case BAD_CHECKSUM: puts("epitar: bad checksum"); - return 2; + ret_code = 2; + break; + + case NOT_IMPLEMENTED: + puts("epitar: Function not implemented"); + ret_code = 2; + break; + + case FILE_NOT_FOUND: + if (config.mode == ARCHIVE) + { + puts("epitar: unable to add file tldr to tarball tata.tar"); + ret_code = 3; + break; + } + + case EMPTY_ARCHIVE: + puts("epitar: cowardly refusing to create an empty archive"); + ret_code = 1; + break; default: printf("epitar: error extracting tarball %s\n", config.archive_file); - return 3; + ret_code = 3; + break; } + + destroy_config(&config); + return ret_code; } diff --git a/epitar/src/tar/archive.c b/epitar/src/tar/archive.c index 014bcd1..dc2e88e 100644 --- a/epitar/src/tar/archive.c +++ b/epitar/src/tar/archive.c @@ -1,8 +1,131 @@ #include "archive.h" -enum error_code archive(char *archive_name, char **files, bool verbose) { - (void) archive_name; - (void) files; - (void) verbose; - return UNKNOWN_ERROR; +#include +#include +#include + +#include "../utils/filesystem.h" +#include "tar.h" + +/** + * + */ +static enum error_code archivefile(FILE *archive, char *file_path, bool verbose) +{ + if (verbose) + puts(file_path); + + FILE *file = fopen(file_path, "r"); + if (file == NULL) + return FILE_NOT_FOUND; + // Create header (ustar) + struct ustar_header header = { 0 }; + strcpy(header.name, file_path); + // Length + fseek(file, 0L, SEEK_END); + size_t file_size = ftell(file); + snprintf(header.size, TSIZELEN, "%lu", file_size); + fseek(file, 0L, SEEK_SET); // Go back + + // TODO handle uid, gid, mtime, and links + + // Write header + int nwritten = fwrite(&header, sizeof(struct ustar_header), 1, archive); + if (nwritten < sizeof(struct ustar_header)) + { + fclose(file); + return CANNOT_CREATE_TARGET; + } + + // Copy file content + fcat(file, archive, file_size); + + // Add padding + size_t total_size = + (((file_size + BLOCK_SIZE - 1) / BLOCK_SIZE) * BLOCK_SIZE); + size_t padding = total_size - file_size; + + char empty_buf[512] = { 0 }; + size_t remaining = padding; + nwritten = fwrite(empty_buf, remaining, 1, archive); + if (nwritten < remaining) + { + fclose(file); + return CANNOT_CREATE_TARGET; + } + + return SUCCESS; +} + +/** + * @brief Archives a hole directory (recursively) + * @warn Assumes that the director path is valid + * @return SUCCESS or the corresponding error + */ +static enum error_code archivedir(FILE *archive, char *dir_path, bool verbose) +{ + // Add the directory itself + if (verbose) + puts(dir_path); + + // Archive members + char *filename; + while ((filename = listdirectory(dir_path)) != NULL) + { + enum error_code err; + enum error_code isdir = isdirectory(filename); + if (isdir == FILE_NOT_FOUND) + // Does not exists + err = FILE_NOT_FOUND; + else if (isdir == SUCCESS) + // Directory + err = archivedir(archive, filename, verbose); + else + // File + err = archivefile(archive, filename, verbose); + + if (err != SUCCESS) + { + freedirectory(); + return err; + } + } + + freedirectory(); // Just in case + return SUCCESS; +} + +enum error_code archive(char *archive_name, char **files, bool verbose) +{ + if (archive_name == NULL || files == NULL) + return UNKNOWN_ERROR; + if (*files == NULL) + return EMPTY_ARCHIVE; + + FILE *archive_stream = fopen(archive_name, "w+"); + + char **current_file = files; + while (*current_file != NULL) + { + enum error_code err; + enum error_code isdir = isdirectory(*current_file); + if (isdir == FILE_NOT_FOUND) + // Does not exists + err = FILE_NOT_FOUND; + else if (isdir == SUCCESS) + // Directory + err = archivedir(archive_stream, *current_file, verbose); + else + // File + err = archivefile(archive_stream, *current_file, verbose); + + if (err != SUCCESS) + { + fclose(archive_stream); + return err; + } + } + + fclose(archive_stream); + return SUCCESS; } diff --git a/epitar/src/tar/extract.c b/epitar/src/tar/extract.c index 1de4b9d..9c7ddbf 100644 --- a/epitar/src/tar/extract.c +++ b/epitar/src/tar/extract.c @@ -13,7 +13,6 @@ */ static enum error_code extract_ustar(FILE *stream, struct ustar_header *header) { - struct file_stats stats = { .name = header->name, .length = strtol(header->size, NULL, 8), .content_stream = stream, @@ -70,14 +69,14 @@ static enum error_code extract_file(FILE *content, union tar_header *header, if ((ustar_header = get_ustar_header(header)) != NULL) { if (verbose) - printf("./%s\n", ustar_header->name); + printf("%s\n", ustar_header->name); return extract_ustar(content, ustar_header); } // UNIXTAR else if ((unixtar_header = get_unixtar_header(header)) != NULL) { if (verbose) - printf("./%s", unixtar_header->name); + printf("%s\n", unixtar_header->name); return extract_unixtar(content, unixtar_header); } // Unsupported format @@ -140,7 +139,8 @@ enum error_code extract(char *archive_name, bool verbose) // Calculate how many padding bytes need to be skipped size_t file_size = strtol(header.ustar.size, NULL, 8); - size_t padded_size = ((file_size + BLOCK_SIZE - 1) / BLOCK_SIZE) * BLOCK_SIZE; + size_t padded_size = + ((file_size + BLOCK_SIZE - 1) / BLOCK_SIZE) * BLOCK_SIZE; size_t skip_bytes = padded_size - file_size; if (skip_bytes > 0 && fseek(stream, skip_bytes, SEEK_CUR) != 0) diff --git a/epitar/src/tar/tar.h b/epitar/src/tar/tar.h index ebf660c..8630b01 100644 --- a/epitar/src/tar/tar.h +++ b/epitar/src/tar/tar.h @@ -40,6 +40,9 @@ #define TOWRITE 00002 /* write by other */ #define TOEXEC 00001 /* execute/search by other */ +// Non standard definitions +#define TSIZELEN 12 /* Length of the size field */ + // === Structures struct ustar_header diff --git a/epitar/src/utils/config.c b/epitar/src/utils/config.c index e2d82c6..ba09793 100644 --- a/epitar/src/utils/config.c +++ b/epitar/src/utils/config.c @@ -203,5 +203,5 @@ void config_destroy(struct config *config) // Note: archive_file points to argv, so we don't free it - free(config); + // free(config); } diff --git a/epitar/src/utils/errors.h b/epitar/src/utils/errors.h index eaf81ea..78caf7c 100644 --- a/epitar/src/utils/errors.h +++ b/epitar/src/utils/errors.h @@ -6,11 +6,14 @@ enum error_code SUCCESS = 0, PENDING, UNKNOWN_ERROR, + GENERIC_ERROR, FILE_NOT_FOUND, BAD_CHECKSUM, INVALID_FORMAT, CANNOT_CREATE_TARGET, TARGET_ALREADY_EXISTS, + NO_TARGET_PROVIDED, + EMPTY_ARCHIVE, NOT_IMPLEMENTED }; diff --git a/epitar/src/utils/filesystem.c b/epitar/src/utils/filesystem.c index aac2b70..09becbc 100644 --- a/epitar/src/utils/filesystem.c +++ b/epitar/src/utils/filesystem.c @@ -1,8 +1,8 @@ #define _POSIX_C_SOURCE 200809L #include "filesystem.h" -#include "errors.h" +#include #include #include #include @@ -11,9 +11,15 @@ #include #include +#include "errors.h" + +static char *current_dir_path = NULL; +static DIR *current_dir = NULL; + enum error_code create_directory(struct file_stats *stats) { - if (stats == NULL || stats->name == NULL || stats->mode == NULL || stats->uid == NULL || stats->gid == NULL || stats->mtime == NULL) + if (stats == NULL || stats->name == NULL || stats->mode == NULL + || stats->uid == NULL || stats->gid == NULL || stats->mtime == NULL) return UNKNOWN_ERROR; // Create dir struct stat st = { 0 }; @@ -59,9 +65,9 @@ enum error_code create_link(struct file_stats *stats, char *target) if (errc != SUCCESS) return errc; // May change the target mode instead of the limk itsel - // also lchmod is not available on all systems so just ignoring this property here - // errc = setmode(stats->name, stats->mode); - // if (errc != SUCCESS) + // also lchmod is not available on all systems so just ignoring this + // property here errc = setmode(stats->name, stats->mode); if (errc != + // SUCCESS) // return errc; return SUCCESS; @@ -83,16 +89,7 @@ enum error_code create_file(struct file_stats *stats) if (file_stream == NULL) return CANNOT_CREATE_TARGET; // Copy content - char buffer[4096]; - size_t remaining = stats->length; - while (remaining > 0) { - size_t to_read = (remaining < sizeof(buffer)) ? remaining : sizeof(buffer); - size_t bytes_read = fread(buffer, 1, to_read, stats->content_stream); - if (bytes_read == 0) - break; // Reached EOF - fwrite(buffer, 1, bytes_read, file_stream); - remaining -= bytes_read; - } + fcat(stats->content_stream, file_stream, stats->length); // Close file fclose(file_stream); @@ -115,13 +112,13 @@ enum error_code create_file(struct file_stats *stats) enum error_code setowner(char *path, char *uid, char *gid) { if (path == NULL || uid == NULL || gid == NULL) - return UNKNOWN_ERROR; + return UNKNOWN_ERROR; - uid_t uid_val = strtol(uid, NULL, 10); - gid_t gid_val = strtol(gid, NULL, 10); + uid_t uid_val = strtol(uid, NULL, 10); + gid_t gid_val = strtol(gid, NULL, 10); - int err = chown(path, uid_val, gid_val); - return err == 0 ? SUCCESS : CANNOT_CREATE_TARGET; + int err = chown(path, uid_val, gid_val); + return err == 0 ? SUCCESS : CANNOT_CREATE_TARGET; } enum error_code setmtime(char *path, char *mtime) @@ -155,3 +152,66 @@ enum error_code setmode(char *path, char *mode) int err = chmod(path, mode_val); return err == 0 ? SUCCESS : CANNOT_CREATE_TARGET; } + +enum error_code isdirectory(char *path) +{ + struct stat st; + if (stat(path, &st) == -1) + return FILE_NOT_FOUND; + return S_ISREG(st.st_mode) ? SUCCESS : GENERIC_ERROR; +} + +char *listdirectory(char *path) +{ + struct dirent *de; + + // Check for path changes + if (current_dir_path != NULL && strcmp(current_dir_path, path) != 0) + { + // Close previous dir + freedirectory(); + // Open new dir + current_dir = opendir(current_dir_path); + if (current_dir == NULL) + { + freedirectory(); + return NULL; + } + + current_dir_path = strdup(path); + } + + // Get following file + de = readdir(current_dir); + if (de == NULL) + { + freedirectory(); + return NULL; + } + + return de->d_name; +} + +void freedirectory() +{ + if (current_dir != NULL) + closedir(current_dir); + if (current_dir_path != NULL) + free(current_dir_path); +} + +void fcat(FILE *src_stream, FILE *dst_stream, size_t size) +{ + char buffer[4096]; + size_t remaining = size; + while (remaining > 0) + { + size_t to_read = + (remaining < sizeof(buffer)) ? remaining : sizeof(buffer); + size_t bytes_read = fread(buffer, 1, to_read, src_stream); + if (bytes_read == 0) + break; // Reached EOF + fwrite(buffer, bytes_read, 1, dst_stream); + remaining -= bytes_read; + } +} diff --git a/epitar/src/utils/filesystem.h b/epitar/src/utils/filesystem.h index 6b16787..b504c4d 100644 --- a/epitar/src/utils/filesystem.h +++ b/epitar/src/utils/filesystem.h @@ -21,8 +21,44 @@ enum error_code create_directory(struct file_stats *stats); enum error_code create_link(struct file_stats *stats, char *target); enum error_code create_file(struct file_stats *stats); +void fcat(FILE *src_stream, FILE *dst_stream, size_t size); + enum error_code setowner(char *path, char *uid, char *gid); enum error_code setmtime(char *path, char *mtime); enum error_code setmode(char *path, char *mode); +/** + * @brief Retrieves the file informations at path and build the corresponding + * file_stats struct + * @arg path The given path + * @arg stats Pointer to where the function should write file informations + * @return FILE_NOT_FOUND if doesn's exists, GENERIC_ERROR if . + */ +enum error_code getstats(char *path, struct file_stats *stats); + +/** + * @brief Check if given path exists and is a directory + * @option path The given path + * @return FILE_NOT_FOUND if doesn's exists, GENERIC_ERROR if it is not a + * directory, SUCCESS otherwise. + */ +enum error_code isdirectory(char *path); + +/** + * @brief Lists a directory content. Returns a new member name after each + * iteration and NULL when there are no more to list. + * @arg path The directory path + * @return The current item name or NULL + * @warn The return item statically allocated and freed after finishing to list + * the directory or when freedirectory() is called. + */ +char *listdirectory(char *path); + +/** + * @brief Closes the last opened (and not closed) directory and frees the + * related allocated memory + * @warn Use this function if you only partially listed a directory previously. + */ +void freedirectory(); + #endif // FILESYSTEM_H diff --git a/epitar/tests/functional/run.sh b/epitar/tests/functional/run.sh new file mode 100644 index 0000000..37d5497 --- /dev/null +++ b/epitar/tests/functional/run.sh @@ -0,0 +1,329 @@ +#!/bin/sh + +# Testsuitator (mon bien-aimé) le retour <3 + +################### +# Variables # +################### + +executable="$BIN_PATH" +ref_executable="tar" + +total_tests=0 +errors_count=0 +timeouts_count=0 + +# Create temp directory for tests +test_dir=$(mktemp -d) +trap "rm -rf $test_dir" EXIT + + +################## +# Colors # +################## + +# Reset +Color_Off='\033[0m' # Text Reset + +# Regular Colors +Black='\033[0;30m' # Black +Red='\033[0;31m' # Red +Green='\033[0;32m' # Green +Yellow='\033[0;33m' # Yellow +Blue='\033[0;34m' # Blue +Purple='\033[0;35m' # Purple +Cyan='\033[0;36m' # Cyan +White='\033[0;37m' # White + +# Bold +BBlack='\033[1;30m' # Black +BRed='\033[1;31m' # Red +BGreen='\033[1;32m' # Green +BYellow='\033[1;33m' # Yellow +BBlue='\033[1;34m' # Blue +BPurple='\033[1;35m' # Purple +BCyan='\033[1;36m' # Cyan +BWhite='\033[1;37m' # White + + +################## +# Wrappers # +################## + +# @arg test name +# @arg command +check_result() { + local test_name="$1" + local actual_code="$2" + local ref_code="$3" + + test_failed=0 + + # Check return code + if [[ "$actual_code" -eq 124 ]]; then + echo -e $BRed "TIMEOUT" $Color_Off + echo -e ' ' "on '$test_name'" + echo -e ' ' "Expected code $ref_code but got $actual_code" + ((timeouts_count++)) + test_failed=1 + elif [[ "$actual_code" -ne "$ref_code" ]]; then + echo -e $BRed "FAILED" $Color_Off + echo -e ' ' "on '$test_name'" + echo -e ' ' "Expected code $ref_code but got $actual_code" + test_failed=1 + else + echo -e $BGreen "OK" $Color_Off + fi + + if [[ "$test_failed" -eq 1 ]]; then + ((errors_count++)) + fi +} + +# @arg test name +# @arg test command +test_archive_creation() { + local test_name="$1" + local archive_name="$2" + local input_files="$3" + + echo -e $BBlue "========== $test_name ==========" $Color_Off + + # Create test files + local test_subdir="$test_dir/$test_name" + mkdir -p "$test_subdir" + cd "$test_subdir" + + # Prepare input files + local file_list="" + for file in $input_files; do + echo "content of $file" > "$file" + file_list="$file_list $file" + done + + # Test with epitar + echo -e -n $Blue "= [ARCHIVE]" $Color_Off " " + timeout 2 $executable -c "$archive_name" $file_list > /dev/null 2>&1 + actual_code=$? + + if [[ $actual_code -eq 0 && -f "$archive_name" ]]; then + echo -e $BGreen "OK" $Color_Off + else + echo -e $BRed "FAILED" $Color_Off + ((errors_count++)) + fi + ((total_tests++)) + + # Test extraction + if [[ -f "$archive_name" ]]; then + echo -e -n $Blue "= [EXTRACT] " $Color_Off " " + extract_dir="extract_test" + mkdir "$extract_dir" + cd "$extract_dir" + timeout 2 $executable -x "../$archive_name" > /dev/null 2>&1 + actual_code=$? + + if [[ $actual_code -eq 0 ]]; then + # Check if files were extracted + all_extracted=1 + for file in $file_list; do + if [[ ! -f "$file" ]]; then + all_extracted=0 + fi + done + if [[ $all_extracted -eq 1 ]]; then + echo -e $BGreen "OK" $Color_Off + else + echo -e $BRed "FAILED (files not extracted)" $Color_Off + ((errors_count++)) + fi + else + echo -e $BRed "FAILED (exit code $actual_code)" $Color_Off + ((errors_count++)) + fi + ((total_tests++)) + cd .. + fi + + cd / + echo -e "\n" +} + +# @arg test name +# @arg files to test +test_verbose_output() { + local test_name="$1" + local files="$2" + + echo -e $BBlue "========== $test_name ==========" $Color_Off + + local test_subdir="$test_dir/${test_name}_verbose" + mkdir -p "$test_subdir" + cd "$test_subdir" + + # Create test files + for file in $files; do + echo "test content" > "$file" + done + + echo -e -n $Blue "= [VERBOSE]" $Color_Off " " + archive="test_verbose.tar" + timeout 2 $executable -cv "$archive" $files > output.txt 2>&1 + actual_code=$? + + # Check if output contains filenames + local all_found=1 + for file in $files; do + if ! grep -q "$file" output.txt; then + all_found=0 + fi + done + + if [[ $actual_code -eq 0 && $all_found -eq 1 ]]; then + echo -e $BGreen "OK" $Color_Off + else + echo -e $BRed "FAILED" $Color_Off + ((errors_count++)) + fi + ((total_tests++)) + + cd / + echo -e "\n" +} + +# @arg test name +test_help() { + local test_name="$1" + + echo -e $BBlue "========== $test_name ==========" $Color_Off + + echo -e -n $Blue "= [HELP]" $Color_Off " " + timeout 1 $executable -h > /dev/null 2>&1 + actual_code=$? + + if [[ $actual_code -eq 0 ]]; then + echo -e $BGreen "OK" $Color_Off + else + echo -e $BRed "FAILED" $Color_Off + ((errors_count++)) + fi + ((total_tests++)) + + echo -e "\n" +} + +# @arg test name +test_invalid_args() { + local test_name="$1" + local args="$2" + + echo -e $BBlue "========== $test_name ==========" $Color_Off + + echo -e -n $Blue "= [INVALID]" $Color_Off " " + timeout 1 $executable $args > /dev/null 2>&1 + actual_code=$? + + # Should fail + if [[ $actual_code -ne 0 ]]; then + echo -e $BGreen "OK (correctly rejected)" $Color_Off + else + echo -e $BRed "FAILED (should have been rejected)" $Color_Off + ((errors_count++)) + fi + ((total_tests++)) + + echo -e "\n" +} + +summarize() { + # Compute statistics + (( passed_tests = $total_tests - $errors_count )) + if [[ $total_tests -gt 0 ]]; then + (( tests_percentage = 100 * $passed_tests / $total_tests )) + else + tests_percentage=0 + fi + + # Adapt color depending on results + if [[ tests_percentage -gt 80 ]]; then + coverage_color=$BGreen + elif [[ tests_percentage -gt 50 ]]; then + coverage_color=$BYellow + else + coverage_color=$BRed + fi + + # Print + echo -e $BWhite "\n\n""===========" $BWhite"Summary"$Color_Off "\n" + echo -e " Passed $coverage_color$passed_tests/$total_tests$Color_Off tests ($coverage_color$tests_percentage%$Color_Off)" + echo -e " Got $timeouts_count timeout(s)" + if [ "$OUTPUT_FILE" != "" ]; then + echo $tests_percentage > "$OUTPUT_FILE" + fi + echo +} + + +################# +# Tests # +################# + +echo -e "\n\n""===$BGreen TestsuitatorX 2.0 Ultra Pro Max+ 365 Premium Gris Sidéral" "\n\n"$Color_Off + +echo -e "\n$BBlue=== Basic Operations ===$Color_Off" +test_archive_creation "Single file" "single.tar" "file1.txt" +test_archive_creation "Multiple files" "multi.tar" "file1.txt file2.txt file3.txt" +test_archive_creation "Files with spaces" "spaces.tar" "file_with_underscore.txt" + +echo -e "\n$BBlue=== Verbose Output ===$Color_Off" +test_verbose_output "Verbose creation" "file1.txt file2.txt" + +echo -e "\n$BBlue=== Help and Invalid Arguments ===$Color_Off" +test_help "Help flag" +test_invalid_args "No arguments" "" +test_invalid_args "Invalid flag" "-z" +test_invalid_args "Missing archive" "-c" +test_invalid_args "Extract with files" "-x archive.tar file.txt" + +echo -e "\n$BBlue=== Edge Cases ===$Color_Off" + +# Test with empty archive +test_subdir="$test_dir/empty_archive" +mkdir -p "$test_subdir" +cd "$test_subdir" +echo -e $BBlue "========== Empty archive ==========" $Color_Off +echo -e -n $Blue "= [CREATE_EMPTY]" $Color_Off " " +touch dummy +timeout 2 $executable -c "empty.tar" dummy > /dev/null 2>&1 +actual_code=$? +rm dummy +if [[ $actual_code -eq 0 ]]; then + echo -e $BGreen "OK" $Color_Off +else + echo -e $BRed "FAILED" $Color_Off + ((errors_count++)) +fi +((total_tests++)) +cd / +echo -e "\n" + +# Test combined flags +test_subdir="$test_dir/combined_flags" +mkdir -p "$test_subdir" +cd "$test_subdir" +echo -e $BBlue "========== Combined flags (-cv) ==========" $Color_Off +echo -e -n $Blue "= [COMBINED]" $Color_Off " " +echo "content" > test.txt +timeout 2 $executable -cv "test.tar" test.txt > /dev/null 2>&1 +actual_code=$? +if [[ $actual_code -eq 0 && -f "test.tar" ]]; then + echo -e $BGreen "OK" $Color_Off +else + echo -e $BRed "FAILED" $Color_Off + ((errors_count++)) +fi +((total_tests++)) +cd / +echo -e "\n" + +summarize