From 383f681156845e46bece4e65886571bb952c2ed0 Mon Sep 17 00:00:00 2001 From: "Gu://em_" Date: Wed, 8 Apr 2026 18:43:30 +0200 Subject: [PATCH] Testsuite with ~50 tests and LOOOt of fixes (but seems to work now) --- epitar/Makefile | 2 + epitar/src/main.c | 4 +- epitar/src/tar/archive.c | 105 ++++++++++++++++++++--- epitar/src/tar/extract.c | 1 + epitar/src/tar/tar.h | 5 +- epitar/src/utils/config.c | 2 +- epitar/src/utils/config.h | 2 +- epitar/src/utils/filesystem.c | 52 +++++------ epitar/tests/functional/old_testsuite.sh | 91 ++++++++++++++++++++ epitar/tests/functional/warning.sh | 9 ++ 10 files changed, 226 insertions(+), 47 deletions(-) create mode 100644 epitar/tests/functional/old_testsuite.sh create mode 100644 epitar/tests/functional/warning.sh diff --git a/epitar/Makefile b/epitar/Makefile index 8d614a7..adbe911 100644 --- a/epitar/Makefile +++ b/epitar/Makefile @@ -27,7 +27,9 @@ debug: $(OBJS) $(CC) -o $(TARGET) $^ $(LDFLAGS) $(LDLIBS) check: + bash ./tests/functional/old_testsuite.sh bash ./tests/functional/run.sh + bash ./tests/functional/warning.sh clean: $(RM) $(TARGET) diff --git a/epitar/src/main.c b/epitar/src/main.c index 5126471..1e12eb4 100644 --- a/epitar/src/main.c +++ b/epitar/src/main.c @@ -56,10 +56,12 @@ int main(int argc, char **argv) case FILE_NOT_FOUND: if (config.mode == ARCHIVE) { - puts("epitar: unable to add file tldr to tarball tata.tar"); + // Rest of code should have printed the first part of the line + printf(" to tarball %s\n", config.archive_file); ret_code = 3; break; } + __attribute__((fallthrough)); case EMPTY_ARCHIVE: puts("epitar: cowardly refusing to create an empty archive"); diff --git a/epitar/src/tar/archive.c b/epitar/src/tar/archive.c index dc2e88e..749f239 100644 --- a/epitar/src/tar/archive.c +++ b/epitar/src/tar/archive.c @@ -7,8 +7,63 @@ #include "../utils/filesystem.h" #include "tar.h" +static size_t getfilesize(FILE *file) +{ + fseek(file, 0L, SEEK_END); + size_t file_size = ftell(file); + fseek(file, 0L, SEEK_SET); // Go back + return file_size; +} + /** - * + * @brief Creates and returns the header for the file located at `file_path` and + * with content `file_stream` + * @param file_stream Pointer to the content of the file (as a stream) if it's a + * file. If it's NULL file will be considered as a directory + */ +static struct ustar_header create_header(FILE *file_stream, char *file_path) +{ + struct ustar_header header = { 0 }; + + // Name + strncpy(header.name, file_path, TNAMELEN); + // Magic tag + strncpy(header.magic, USTAR_MAGIC, TMAGLEN); + // Versions + memcpy(header.version, TVERSION, TVERSLEN); + + // TODO handle uid, gid, mtime, and links + + // File or directory + if (file_stream == NULL) + { + // === Directory + // Type + header.typeflag = DIRTYPE; + } + else + { + // === File + // Length + size_t file_size = getfilesize(file_stream); + snprintf(header.size, TSIZELEN, "%lu", file_size); + // Type + header.typeflag = REGTYPE; + } + + // Checksum + union tar_header header_union = { .ustar = header }; + size_t chksum = compute_checksum(&header_union); + snprintf(header.chksum, TCHKSUMLEN, "%lu", chksum); + + return header; +} + +/** + * @brief Adds a file (header + content) to the archive stream + * @param archive The target archive stream + * @param file_path The path where the file to be archived is located + * @return SUCCESS or the corresponding error code */ static enum error_code archivefile(FILE *archive, char *file_path, bool verbose) { @@ -17,21 +72,18 @@ static enum error_code archivefile(FILE *archive, char *file_path, bool verbose) FILE *file = fopen(file_path, "r"); if (file == NULL) + { + printf("Unable to add file %s", file_path); 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 + // Create header (ustar) + struct ustar_header header = create_header(file, file_path); + size_t file_size = getfilesize(file); // Write header - int nwritten = fwrite(&header, sizeof(struct ustar_header), 1, archive); - if (nwritten < sizeof(struct ustar_header)) + size_t nwritten = fwrite(&header, sizeof(struct ustar_header), 1, archive); + if (nwritten < 1) { fclose(file); return CANNOT_CREATE_TARGET; @@ -45,9 +97,9 @@ static enum error_code archivefile(FILE *archive, char *file_path, bool verbose) (((file_size + BLOCK_SIZE - 1) / BLOCK_SIZE) * BLOCK_SIZE); size_t padding = total_size - file_size; - char empty_buf[512] = { 0 }; + char empty_buf[BLOCK_SIZE] = { 0 }; size_t remaining = padding; - nwritten = fwrite(empty_buf, remaining, 1, archive); + nwritten = fwrite(empty_buf, sizeof(char), remaining, archive); if (nwritten < remaining) { fclose(file); @@ -68,6 +120,12 @@ static enum error_code archivedir(FILE *archive, char *dir_path, bool verbose) if (verbose) puts(dir_path); + // Header + struct ustar_header header = create_header(NULL, dir_path); + size_t nwritten = fwrite(&header, sizeof(struct ustar_header), 1, archive); + if (nwritten < 1) + return CANNOT_CREATE_TARGET; + // Archive members char *filename; while ((filename = listdirectory(dir_path)) != NULL) @@ -75,14 +133,21 @@ static enum error_code archivedir(FILE *archive, char *dir_path, bool verbose) enum error_code err; enum error_code isdir = isdirectory(filename); if (isdir == FILE_NOT_FOUND) + { // Does not exists + printf("Unable to add file %s", dir_path); err = FILE_NOT_FOUND; + } else if (isdir == SUCCESS) + { // Directory err = archivedir(archive, filename, verbose); + } else + { // File err = archivefile(archive, filename, verbose); + } if (err != SUCCESS) { @@ -110,22 +175,34 @@ enum error_code archive(char *archive_name, char **files, bool verbose) enum error_code err; enum error_code isdir = isdirectory(*current_file); if (isdir == FILE_NOT_FOUND) + { // Does not exists + printf("Unable to add file %s", *current_file); 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; } + + current_file++; } + // Terminating NULL blocks + char empty_buf[BLOCK_SIZE * 2] = { 0 }; + fwrite(empty_buf, sizeof(char), BLOCK_SIZE * 2, archive_stream); fclose(archive_stream); return SUCCESS; } diff --git a/epitar/src/tar/extract.c b/epitar/src/tar/extract.c index 9c7ddbf..77ffcbd 100644 --- a/epitar/src/tar/extract.c +++ b/epitar/src/tar/extract.c @@ -112,6 +112,7 @@ enum error_code extract(char *archive_name, bool verbose) if (bytes_read < sizeof(union tar_header)) { read_state = INVALID_FORMAT; // Unexpected EOF + continue; } // Check if empty diff --git a/epitar/src/tar/tar.h b/epitar/src/tar/tar.h index 8630b01..db62b12 100644 --- a/epitar/src/tar/tar.h +++ b/epitar/src/tar/tar.h @@ -41,7 +41,9 @@ #define TOEXEC 00001 /* execute/search by other */ // Non standard definitions +#define TNAMELEN 100 /* Length of the name field */ #define TSIZELEN 12 /* Length of the size field */ +#define TCHKSUMLEN 8 /* Length of the size field */ // === Structures @@ -63,6 +65,7 @@ struct ustar_header char devmajor[8]; char devminor[8]; char prefix[155]; + char padding[12]; }; struct unixtar_header @@ -74,7 +77,7 @@ struct unixtar_header char size[12]; char mtime[12]; char chksum[8]; - char link[1]; + char link; char linkname[100]; char padding[255]; // Should be empty }; diff --git a/epitar/src/utils/config.c b/epitar/src/utils/config.c index ba09793..45e4dfb 100644 --- a/epitar/src/utils/config.c +++ b/epitar/src/utils/config.c @@ -190,7 +190,7 @@ void print_invalid_option(void) puts("Try './epitar -h' for more information"); } -void config_destroy(struct config *config) +void destroy_config(struct config *config) { if (config == NULL) return; diff --git a/epitar/src/utils/config.h b/epitar/src/utils/config.h index c429c96..a79bfc9 100644 --- a/epitar/src/utils/config.h +++ b/epitar/src/utils/config.h @@ -55,7 +55,7 @@ void print_invalid_option(void); */ bool validate_config(struct config *cfg); -/* +/* @brief frees all data allocated by config */ void destroy_config(struct config *config); diff --git a/epitar/src/utils/filesystem.c b/epitar/src/utils/filesystem.c index 09becbc..6f3f237 100644 --- a/epitar/src/utils/filesystem.c +++ b/epitar/src/utils/filesystem.c @@ -32,17 +32,13 @@ enum error_code create_directory(struct file_stats *stats) } else { - return TARGET_ALREADY_EXISTS; + // return TARGET_ALREADY_EXISTS; + return SUCCESS; } // Set stats - enum error_code errc; - errc = setowner(stats->name, stats->uid, stats->gid); - if (errc != SUCCESS) - return errc; - errc = setmtime(stats->name, stats->mtime); - if (errc != SUCCESS) - return errc; + setowner(stats->name, stats->uid, stats->gid); + setmtime(stats->name, stats->mtime); return SUCCESS; } @@ -57,13 +53,8 @@ enum error_code create_link(struct file_stats *stats, char *target) return CANNOT_CREATE_TARGET; // Set stats - enum error_code errc; - errc = setowner(stats->name, stats->uid, stats->gid); - if (errc != SUCCESS) - return errc; - errc = setmtime(stats->name, stats->mtime); - if (errc != SUCCESS) - return errc; + setowner(stats->name, stats->uid, stats->gid); + setmtime(stats->name, stats->mtime); // 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 != @@ -95,16 +86,9 @@ enum error_code create_file(struct file_stats *stats) fclose(file_stream); // Set stats - enum error_code errc; - errc = setowner(stats->name, stats->uid, stats->gid); - if (errc != SUCCESS) - return errc; - errc = setmtime(stats->name, stats->mtime); - if (errc != SUCCESS) - return errc; - errc = setmode(stats->name, stats->mode); - if (errc != SUCCESS) - return errc; + setowner(stats->name, stats->uid, stats->gid); + setmtime(stats->name, stats->mtime); + setmode(stats->name, stats->mode); return SUCCESS; } @@ -158,7 +142,7 @@ 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; + return S_ISREG(st.st_mode) ? GENERIC_ERROR : SUCCESS; } char *listdirectory(char *path) @@ -166,19 +150,19 @@ char *listdirectory(char *path) struct dirent *de; // Check for path changes - if (current_dir_path != NULL && strcmp(current_dir_path, path) != 0) + if (current_dir == NULL || current_dir_path == NULL + || strcmp(current_dir_path, path) != 0) { // Close previous dir freedirectory(); // Open new dir + current_dir_path = strdup(path); current_dir = opendir(current_dir_path); if (current_dir == NULL) { freedirectory(); return NULL; } - - current_dir_path = strdup(path); } // Get following file @@ -189,15 +173,25 @@ char *listdirectory(char *path) return NULL; } + // Check for '.' or '..' + if (strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0) + return listdirectory(path); + return de->d_name; } void freedirectory() { if (current_dir != NULL) + { closedir(current_dir); + current_dir = NULL; + } if (current_dir_path != NULL) + { free(current_dir_path); + current_dir_path = NULL; + } } void fcat(FILE *src_stream, FILE *dst_stream, size_t size) diff --git a/epitar/tests/functional/old_testsuite.sh b/epitar/tests/functional/old_testsuite.sh new file mode 100644 index 0000000..5e16a27 --- /dev/null +++ b/epitar/tests/functional/old_testsuite.sh @@ -0,0 +1,91 @@ +#!/bin/sh + +# Old testsuite but still pertinent + +# Colors +GREEN='\033[0;32m' +RED='\033[0;31m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +PASSED=0 +FAILED=0 +TOTAL=0 + +# Ensure executable exists +if [ ! -x "./epitar" ]; then + echo -e "${RED}Error: ./epitar not found or not executable.${NC}" + exit 1 +fi + +# assert "test name" "command" "expected_exit_code" "expected_output_substring" +assert() { + ((TOTAL++)) + local label=$1 + local cmd=$2 + local expected_code=$3 + local expected_msg=$4 + + echo -n "Test $TOTAL: $label... " + + # Run + output=$(eval "$cmd" 2>&1) + exit_code=$? + + if [ "$exit_code" -eq "$expected_code" ] && [[ "$output" == *"$expected_msg"* ]]; then + echo -e "${GREEN}PASS${NC}" + ((PASSED++)) + else + echo -e "${RED}FAIL${NC}" + echo -e " Expected code: $expected_code, Got: $exit_code" + echo -e " Expected msg contains: '$expected_msg'" + echo -e " Actual output: '$output'" + ((FAILED++)) + fi +} + +# === Clean Environment === +rm -f *.tar tmp_file1 tmp_file2 +touch tmp_file1 tmp_file2 +echo "corrupt data" > bad_checksum.tar + +# === Testsuite === + +echo -e "${BLUE}Starting epitar test suite...${NC}\n" + +# Help and Basic Usage +assert "Help flag -h" "./epitar -h" 0 "Usage: epitar -[xcvh] []" +assert "No arguments" "./epitar" 1 "invalid or missing option" +assert "Invalid flag -z" "./epitar -z" 1 "invalid or missing option" +assert "Invalid flag -a" "./epitar -a toto.tar titi" 1 "invalid or missing option" + +# Creation Errors (-c) +assert "Refuse empty archive" "./epitar -c empty.tar" 1 "cowardly refusing" +assert "Missing archive name for -c" "./epitar -c" 1 "invalid or missing option" +assert "Archive adding itself" "./epitar -c self.tar self.tar" 0 "is the archive; not dumped" +assert "Unable to add missing file" "./epitar -cv missing.tar non_existent_file" 3 "unable to add file" + +# Extraction Errors (-x) +assert "Bad checksum" "./epitar -x bad_checksum.tar" 2 "bad checksum" +assert "Extraction of non-existent file" "./epitar -x nowhere.tar" 3 "error extracting tarball" +assert "Missing archive name for -x" "./epitar -x" 1 "invalid or missing option" + +# Combinations & Complex flags +assert "Multiple invalid flags" "./epitar -xz archive.tar" 1 "invalid or missing option" +assert "Help priority" "./epitar -h -x nonexistent.tar" 0 "Usage:" +assert "Multiple files for creation (failure case)" "./epitar -c fail.tar tmp_file1 missing_file" 3 "unable to add file" + +# Path and Environment edge cases +assert "Relative path to non-existent" "./epitar -x ./subdir/fake.tar" 3 "error extracting tarball" +assert "Creation with dot (.)" "./epitar -c current.tar ." 0 "current.tar is the archive; not dumped" +assert "List content (hypothetical -v without -c or -x)" "./epitar -v" 1 "invalid or missing option" +assert "Wrong order of arguments" "./epitar archive.tar -c" 1 "invalid or missing option" + +# === Summary === +echo -e "\n------------------------------------" +echo -e "Results: ${GREEN}$PASSED Passed${NC}, ${RED}$FAILED Failed${NC} / $TOTAL Total" +echo -e "------------------------------------" + +# Cleanup +rm -f *.tar tmp_file1 tmp_file2 + diff --git a/epitar/tests/functional/warning.sh b/epitar/tests/functional/warning.sh new file mode 100644 index 0000000..6b66c3b --- /dev/null +++ b/epitar/tests/functional/warning.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +Color_Off='\033[0m' # Text Reset +BYellow='\033[1;33m' # Yellow + +echo +echo -e $BYellow "WARNING:" $Color_Off " Two testsuites have been run, don't just check the last summary" +echo -e $BYellow "WARNING:" $Color_Off " Archive/Extract tests will be partly hidden if their first part doesn't succeed" +echo " (So if you want the accurate number of tests just add the number of failed tests to the total)"