archivage et métissage

This commit is contained in:
Gu://em_ 2026-04-08 12:49:24 +02:00
parent 3e4e32ed45
commit 642e292124
11 changed files with 696 additions and 38 deletions

79
.clang-format Normal file
View file

@ -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

View file

@ -27,7 +27,7 @@ debug: $(OBJS)
$(CC) -o $(TARGET) $^ $(LDFLAGS) $(LDLIBS) $(CC) -o $(TARGET) $^ $(LDFLAGS) $(LDLIBS)
check: check:
dash ./tests/run.sh bash ./tests/functional/run.sh
clean: clean:
$(RM) $(TARGET) $(RM) $(TARGET)

View file

@ -1,6 +1,7 @@
#include "tar/archive.h" #include "tar/archive.h"
#include "tar/extract.h" #include "tar/extract.h"
#include "utils/config.h" #include "utils/config.h"
#include "utils/errors.h"
int main(int argc, char **argv) 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); err = archive(config.archive_file, config.input_files, config.verbose);
} }
int ret_code = 0;
switch (err) switch (err)
{ {
case SUCCESS: case SUCCESS:
return 0; ret_code = 0;
break;
// case FILE_NOT_FOUND: // case UNKNOWN_ERROR:
// case UNKNOWN_ERROR: // printf("epitar: error extracting tarball %s\n",
// printf("epitar: error extracting tarball %s\n", config.archive_file); // config.archive_file); ret_code = 3; break;
// return 3;
case BAD_CHECKSUM: case BAD_CHECKSUM:
puts("epitar: 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: default:
printf("epitar: error extracting tarball %s\n", config.archive_file); printf("epitar: error extracting tarball %s\n", config.archive_file);
return 3; ret_code = 3;
break;
} }
destroy_config(&config);
return ret_code;
} }

View file

@ -1,8 +1,131 @@
#include "archive.h" #include "archive.h"
enum error_code archive(char *archive_name, char **files, bool verbose) { #include <stddef.h>
(void) archive_name; #include <stdio.h>
(void) files; #include <string.h>
(void) verbose;
return UNKNOWN_ERROR; #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;
} }

View file

@ -13,7 +13,6 @@
*/ */
static enum error_code extract_ustar(FILE *stream, struct ustar_header *header) static enum error_code extract_ustar(FILE *stream, struct ustar_header *header)
{ {
struct file_stats stats = { .name = header->name, struct file_stats stats = { .name = header->name,
.length = strtol(header->size, NULL, 8), .length = strtol(header->size, NULL, 8),
.content_stream = stream, .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 ((ustar_header = get_ustar_header(header)) != NULL)
{ {
if (verbose) if (verbose)
printf("./%s\n", ustar_header->name); printf("%s\n", ustar_header->name);
return extract_ustar(content, ustar_header); return extract_ustar(content, ustar_header);
} }
// UNIXTAR // UNIXTAR
else if ((unixtar_header = get_unixtar_header(header)) != NULL) else if ((unixtar_header = get_unixtar_header(header)) != NULL)
{ {
if (verbose) if (verbose)
printf("./%s", unixtar_header->name); printf("%s\n", unixtar_header->name);
return extract_unixtar(content, unixtar_header); return extract_unixtar(content, unixtar_header);
} }
// Unsupported format // Unsupported format
@ -140,7 +139,8 @@ enum error_code extract(char *archive_name, bool verbose)
// Calculate how many padding bytes need to be skipped // Calculate how many padding bytes need to be skipped
size_t file_size = strtol(header.ustar.size, NULL, 8); 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; size_t skip_bytes = padded_size - file_size;
if (skip_bytes > 0 && fseek(stream, skip_bytes, SEEK_CUR) != 0) if (skip_bytes > 0 && fseek(stream, skip_bytes, SEEK_CUR) != 0)

View file

@ -40,6 +40,9 @@
#define TOWRITE 00002 /* write by other */ #define TOWRITE 00002 /* write by other */
#define TOEXEC 00001 /* execute/search by other */ #define TOEXEC 00001 /* execute/search by other */
// Non standard definitions
#define TSIZELEN 12 /* Length of the size field */
// === Structures // === Structures
struct ustar_header struct ustar_header

View file

@ -203,5 +203,5 @@ void config_destroy(struct config *config)
// Note: archive_file points to argv, so we don't free it // Note: archive_file points to argv, so we don't free it
free(config); // free(config);
} }

View file

@ -6,11 +6,14 @@ enum error_code
SUCCESS = 0, SUCCESS = 0,
PENDING, PENDING,
UNKNOWN_ERROR, UNKNOWN_ERROR,
GENERIC_ERROR,
FILE_NOT_FOUND, FILE_NOT_FOUND,
BAD_CHECKSUM, BAD_CHECKSUM,
INVALID_FORMAT, INVALID_FORMAT,
CANNOT_CREATE_TARGET, CANNOT_CREATE_TARGET,
TARGET_ALREADY_EXISTS, TARGET_ALREADY_EXISTS,
NO_TARGET_PROVIDED,
EMPTY_ARCHIVE,
NOT_IMPLEMENTED NOT_IMPLEMENTED
}; };

View file

@ -1,8 +1,8 @@
#define _POSIX_C_SOURCE 200809L #define _POSIX_C_SOURCE 200809L
#include "filesystem.h" #include "filesystem.h"
#include "errors.h"
#include <dirent.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <sys/stat.h> #include <sys/stat.h>
@ -11,9 +11,15 @@
#include <unistd.h> #include <unistd.h>
#include <utime.h> #include <utime.h>
#include "errors.h"
static char *current_dir_path = NULL;
static DIR *current_dir = NULL;
enum error_code create_directory(struct file_stats *stats) 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; return UNKNOWN_ERROR;
// Create dir // Create dir
struct stat st = { 0 }; struct stat st = { 0 };
@ -59,9 +65,9 @@ enum error_code create_link(struct file_stats *stats, char *target)
if (errc != SUCCESS) if (errc != SUCCESS)
return errc; return errc;
// May change the target mode instead of the limk itsel // May change the target mode instead of the limk itsel
// also lchmod is not available on all systems so just ignoring this property here // also lchmod is not available on all systems so just ignoring this
// errc = setmode(stats->name, stats->mode); // property here errc = setmode(stats->name, stats->mode); if (errc !=
// if (errc != SUCCESS) // SUCCESS)
// return errc; // return errc;
return SUCCESS; return SUCCESS;
@ -83,16 +89,7 @@ enum error_code create_file(struct file_stats *stats)
if (file_stream == NULL) if (file_stream == NULL)
return CANNOT_CREATE_TARGET; return CANNOT_CREATE_TARGET;
// Copy content // Copy content
char buffer[4096]; fcat(stats->content_stream, file_stream, stats->length);
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;
}
// Close file // Close file
fclose(file_stream); 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) enum error_code setowner(char *path, char *uid, char *gid)
{ {
if (path == NULL || uid == NULL || gid == NULL) if (path == NULL || uid == NULL || gid == NULL)
return UNKNOWN_ERROR; return UNKNOWN_ERROR;
uid_t uid_val = strtol(uid, NULL, 10); uid_t uid_val = strtol(uid, NULL, 10);
gid_t gid_val = strtol(gid, NULL, 10); gid_t gid_val = strtol(gid, NULL, 10);
int err = chown(path, uid_val, gid_val); int err = chown(path, uid_val, gid_val);
return err == 0 ? SUCCESS : CANNOT_CREATE_TARGET; return err == 0 ? SUCCESS : CANNOT_CREATE_TARGET;
} }
enum error_code setmtime(char *path, char *mtime) 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); int err = chmod(path, mode_val);
return err == 0 ? SUCCESS : CANNOT_CREATE_TARGET; 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;
}
}

View file

@ -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_link(struct file_stats *stats, char *target);
enum error_code create_file(struct file_stats *stats); 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 setowner(char *path, char *uid, char *gid);
enum error_code setmtime(char *path, char *mtime); enum error_code setmtime(char *path, char *mtime);
enum error_code setmode(char *path, char *mode); 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 #endif // FILESYSTEM_H

View file

@ -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