Codice comune di unit test - Follow-up
Questa domanda è una domanda di follow-up alla parte del codice comune di unit test delle mie domande sull'analizzatore lessicale.
La mia preoccupazione principale è il codice nel file di intestazione e il file sorgente C che implementa strdup (). Poiché il programma di cui fa parte questo codice è progettato per essere multipiattaforma, deve essere compilato ed eseguito su Windows o Linux e dovrebbe essere compatibile con entrambi. La strdup()funzione fa parte dello standard C2X C quindi se diventa disponibile il codice dovrebbe continuare a compilarsi e funzionare. Le #define nel file di intestazione si basano sulla gccversione di string.h.
Una preoccupazione secondaria è la prestazione, molti dei parametri sono cambiati in const. I membri della struttura Test_Log_Data sono stati riordinati per migliorare l'utilizzo della memoria.
Una terza preoccupazione era l'uso arcaico, l'extern che precede i prototipi di funzione è stato rimosso in tutti i file di intestazione, non solo common_unit_test_logic.h.
Il codice originale è fornito per il confronto.
Nuovo codice
common_unit_test_logic.h
#ifndef COMMON_UNIT_TEST_LOGIC_H
#define COMMON_UNIT_TEST_LOGIC_H
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#ifndef REDUCED_VM_AND_HRF_DEPENDENCIES
#include "human_readable_program_format.h"
#endif
typedef struct test_log_data
{
const char* function_name;
char* path;
bool status;
bool stand_alone;
} Test_Log_Data;
extern FILE* error_out_file;
extern FILE* unit_test_log_file;
bool init_vm_error_reporting(const char* error_log_file_name);
#ifndef REDUCED_VM_AND_HRF_DEPENDENCIES
Human_Readable_Program_Format* default_program(size_t* program_size);
#endif
#ifndef strdup
#ifdef _MSC_VER
#if _MSC_VER > 1920
#define strdup _strdup
#endif
#else
#define strdup mystrdup
#endif
#endif
char* mystrdup(const char* string_to_copy);
unsigned char* ucstrdup(const unsigned char* string_to_copy);
void disengage_error_reporting(void);
bool init_unit_tests(const char* log_file_name);
void report_error_generic(const char* error_message);
void report_create_and_init_test_log_data_memory_failure(const char* function_name);
void log_test_status_each_step(const char* function_name, const bool status, const char* path, const bool stand_alone);
void init_test_log_data(Test_Log_Data* log_data, const char* function_name, const bool status, char* path, const bool stand_alone);
Test_Log_Data* create_and_init_test_log_data(const char* function_name, const bool status, char* path, const bool stand_alone);
void log_test_status_each_step2(const Test_Log_Data* test_data_to_log);
void log_start_positive_path(const char* function_name);
void log_start_positive_path2(const Test_Log_Data* log_data);
void log_start_test_path(const Test_Log_Data* log_data);
void log_end_test_path(const Test_Log_Data* log_data);
void log_end_positive_path(const char* function_name);
void log_end_positive_path2(const Test_Log_Data* log_data);
void log_start_negative_path(const char* function_name);
void log_end_negative_path(const char* function_name);
void log_generic_message(const char *log_message);
void close_unit_tests(void);
#endif // !COMMON_UNIT_TEST_LOGIC_H
common_unit_test_logic.c
#include "common_unit_test_logic.h"
#ifndef REDUCED_VM_AND_HRF_DEPENDENCIES
#include "virtual_machine.h"
#endif
#include <ctype.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
FILE* error_out_file = NULL;
FILE* unit_test_log_file = NULL;
char* mystrdup(const char* string_to_copy)
{
char* return_string = NULL;
size_t length = strlen(string_to_copy);
++length;
return_string = calloc(length, sizeof(*return_string));
if (return_string)
{
memcpy(return_string, string_to_copy, length - 1);
}
return return_string;
}
unsigned char* ucstrdup(const unsigned char* string_to_copy)
{
unsigned char* return_string = NULL;
size_t length = strlen((const char *)string_to_copy);
++length;
return_string = calloc(length, sizeof(*return_string));
if (return_string)
{
memcpy(return_string, string_to_copy, length - 1);
}
return return_string;
}
bool init_vm_error_reporting(const char* error_log_file_name)
{
bool status_is_good = true;
if (error_log_file_name)
{
error_out_file = fopen(error_log_file_name, "w");
if (!error_out_file)
{
error_out_file = stderr;
fprintf(error_out_file, "Can't open error output file, %s", "error_log_file_name");
status_is_good = false;
}
}
else
{
error_out_file = stderr;
}
return status_is_good;
}
void disengage_error_reporting(void)
{
if (error_out_file != stderr)
{
fclose(error_out_file);
}
}
#ifndef REDUCED_VM_AND_HRF_DEPENDENCIES
/*
* Allow unit tests that don't require virtual_machine.c and human_readable_program_format.c.
*/
Human_Readable_Program_Format* default_program(size_t* program_size)
{
Human_Readable_Program_Format program[] =
{
{PUSH, 0x0A},
{PUSH, 0x43},
{PUSH, 0x42},
{PUSH, 0x41},
{OUTPUTCHAR, 0x00},
{POP, 0x00},
{OUTPUTCHAR, 0x00},
{POP, 0x00},
{OUTPUTCHAR, 0x00},
{POP, 0x00},
{HALT, 0x00}
};
size_t progsize = sizeof(program) / sizeof(*program);
Human_Readable_Program_Format* copy_of_program = duplicate_program(program, progsize);
if (copy_of_program)
{
*program_size = progsize;
}
return copy_of_program;
}
#endif
bool init_unit_tests(const char* log_file_name)
{
if (log_file_name)
{
unit_test_log_file = fopen(log_file_name, "w");
if (!unit_test_log_file)
{
fprintf(error_out_file, "Can't open %s for output\n", log_file_name);
return false;
}
error_out_file = unit_test_log_file;
}
else
{
unit_test_log_file = stdout;
error_out_file = stderr;
}
return true;
}
void report_error_generic(const char *error_message)
{
fprintf(error_out_file, "%s\n", error_message);
}
void close_unit_tests(void)
{
if (unit_test_log_file != stdout)
{
fclose(unit_test_log_file);
}
}
static bool log_test_is_positive_path(const Test_Log_Data* log_data)
{
bool is_positive = true;
if (!log_data->path)
{
fprintf(error_out_file, "Programmer error: log_data->path is NULL in log_test_is_positive_path()\n");
return false;
}
char* string_to_test = strdup(log_data->path);
if (!string_to_test)
{
fprintf(error_out_file, "Memory Allocation error: strdup() failed in log_test_is_positive_path()\n");
fprintf(error_out_file, "Exiting program.\n");
exit(EXIT_FAILURE);
}
char* stt_ptr = string_to_test;
while (*stt_ptr)
{
*stt_ptr = (char) toupper(*stt_ptr);
stt_ptr++;
}
is_positive = (strcmp(string_to_test, "POSITIVE") == 0);
free(string_to_test);
return is_positive;
}
void log_test_status_each_step(const char* function_name, const bool status, const char* path, const bool stand_alone)
{
if (stand_alone)
{
fprintf(unit_test_log_file, "%s(): %s Path %s\n", function_name, path,
(status) ? "Passed" : "Failed");
}
}
void log_test_status_each_step2(const Test_Log_Data *test_data_to_log)
{
if (test_data_to_log->stand_alone)
{
fprintf(unit_test_log_file, "%s(): %s Path %s\n", test_data_to_log->function_name,
test_data_to_log->path, (test_data_to_log->status) ? "Passed" : "Failed");
}
}
void log_start_positive_path(const char* function_name)
{
fprintf(unit_test_log_file, "\nStarting POSITIVE PATH testing for %s\n\n",
function_name);
}
void log_start_positive_path2(const Test_Log_Data *log_data)
{
fprintf(unit_test_log_file, "\nStarting POSITIVE PATH testing for %s\n\n",
log_data->function_name);
}
void log_end_positive_path(const char* function_name)
{
fprintf(unit_test_log_file, "\nEnding POSITIVE PATH testing for %s\n", function_name);
}
void log_end_positive_path2(const Test_Log_Data* log_data)
{
fprintf(unit_test_log_file, "\nEnding POSITIVE PATH testing for %s, POSITIVE PATH %s \n",
log_data->function_name, log_data->status? "PASSED" : "FAILED");
}
void log_start_negative_path(const char* function_name)
{
fprintf(unit_test_log_file, "\nStarting NEGATIVE PATH testing for %s\n\n", function_name);
}
void log_end_negative_path(const char* function_name)
{
fprintf(unit_test_log_file, "\nEnding NEGATIVE PATH testing for %s\n", function_name);
fflush(unit_test_log_file); // Current unit test is done flush the output.
}
void log_start_test_path(const Test_Log_Data* log_data)
{
bool is_positive = log_test_is_positive_path(log_data);
fprintf(unit_test_log_file, "\nStarting %s PATH testing for %s\n\n",
is_positive ? "POSITIVE" : "NEGATIVE", log_data->function_name);
}
void log_end_test_path(const Test_Log_Data *log_data)
{
bool is_positive = log_test_is_positive_path(log_data);
fprintf(unit_test_log_file, "\nEnding %s PATH testing for %s, Path %s\n",
is_positive ? "POSITIVE" : "NEGATIVE", log_data->function_name,
log_data->status ? "PASSED" : "FAILED");
if (!is_positive)
{
fflush(unit_test_log_file); // Current unit test is done flush the output.
}
}
void log_generic_message(const char* log_message)
{
fprintf(unit_test_log_file, log_message);
}
void init_test_log_data(Test_Log_Data* log_data, const char *function_name, const bool status, char *path, bool stand_alone)
{
log_data->function_name = function_name;
log_data->status = status;
log_data->path = path;
log_data->stand_alone = stand_alone;
}
Test_Log_Data *create_and_init_test_log_data(const char* function_name, const bool status, char* path, const bool stand_alone)
{
Test_Log_Data* log_data = calloc(1, sizeof(*log_data));
if (log_data)
{
init_test_log_data(log_data, function_name, status, path, stand_alone);
}
else
{
fprintf(error_out_file, "In %s calloc() failed\n", "create_and_init_test_log_data");
}
return log_data;
}
// provides common error report for memory allocation error.
void report_create_and_init_test_log_data_memory_failure(const char *function_name)
{
fprintf(error_out_file, "In function %s, Memory allocation failed in create_and_init_test_log_data\n", function_name);
}
Codice originale :
common_unit_test_logic.h
#ifndef COMMON_UNIT_TEST_LOGIC_H
#define COMMON_UNIT_TEST_LOGIC_H
#include <stdio.h>
#include <stdbool.h>
#ifndef REDUCED_VM_AND_HRF_DEPENDENCIES
#include "human_readable_program_format.h"
#endif
typedef struct test_log_data
{
char* function_name;
bool status;
char* path;
bool stand_alone;
} Test_Log_Data;
extern FILE* error_out_file;
extern FILE* unit_test_log_file;
extern bool init_vm_error_reporting(char* error_log_file_name);
#ifndef REDUCED_VM_AND_HRF_DEPENDENCIES
extern Human_Readable_Program_Format* default_program(size_t* program_size);
#endif
extern void disengage_error_reporting(void);
extern bool init_unit_tests(char* log_file_name);
extern void report_error_generic(char* error_message);
extern void report_create_and_init_test_log_data_memory_failure(char* function_name);
extern void log_test_status_each_step(char* function_name, bool status, char* path, bool stand_alone);
extern void init_test_log_data(Test_Log_Data* log_data, char* function_name, bool status, char* path, bool stand_alone);
extern Test_Log_Data* create_and_init_test_log_data(char* function_name, bool status, char* path, bool stand_alone);
extern void log_test_status_each_step2(Test_Log_Data* test_data_to_log);
extern void log_start_positive_path(char* function_name);
extern void log_start_positive_path2(Test_Log_Data* log_data);
extern void log_start_test_path(Test_Log_Data* log_data);
extern void log_end_test_path(Test_Log_Data* log_data);
extern void log_end_positive_path(char* function_name);
extern void log_end_positive_path2(Test_Log_Data* log_data);
extern void log_start_negative_path(char* function_name);
extern void log_end_negative_path(char* function_name);
extern void log_generic_message(char *log_message);
extern void close_unit_tests(void);
#endif // !COMMON_UNIT_TEST_LOGIC_H
common_unit_test_logic.c
#include "common_unit_test_logic.h"
#ifndef REDUCED_VM_AND_HRF_DEPENDENCIES
#include "virtual_machine.h"
#endif
#include <ctype.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
FILE* error_out_file = NULL;
FILE* unit_test_log_file = NULL;
bool init_vm_error_reporting(char* error_log_file_name)
{
bool status_is_good = true;
if (error_log_file_name)
{
error_out_file = fopen(error_log_file_name, "w");
if (!error_out_file)
{
error_out_file = stderr;
fprintf(error_out_file, "Can't open error output file, %s", "error_log_file_name");
status_is_good = false;
}
}
else
{
error_out_file = stderr;
}
return status_is_good;
}
void disengage_error_reporting(void)
{
if (error_out_file != stderr)
{
fclose(error_out_file);
}
}
#ifndef REDUCED_VM_AND_HRF_DEPENDENCIES
/*
* Allow unit tests that don't require virtual_machine.c and human_readable_program_format.c.
*/
Human_Readable_Program_Format* default_program(size_t* program_size)
{
Human_Readable_Program_Format program[] =
{
{PUSH, 0x0A},
{PUSH, 0x43},
{PUSH, 0x42},
{PUSH, 0x41},
{OUTPUTCHAR, 0x00},
{POP, 0x00},
{OUTPUTCHAR, 0x00},
{POP, 0x00},
{OUTPUTCHAR, 0x00},
{POP, 0x00},
{HALT, 0x00}
};
size_t progsize = sizeof(program) / sizeof(*program);
Human_Readable_Program_Format* copy_of_program = duplicate_program(program, progsize);
if (copy_of_program)
{
*program_size = progsize;
}
return copy_of_program;
}
#endif
bool init_unit_tests(char* log_file_name)
{
if (log_file_name)
{
unit_test_log_file = fopen(log_file_name, "w");
if (!unit_test_log_file)
{
fprintf(error_out_file, "Can't open %s for output\n", log_file_name);
return false;
}
error_out_file = unit_test_log_file;
}
else
{
unit_test_log_file = stdout;
error_out_file = stderr;
}
return true;
}
void report_error_generic(char *error_message)
{
fprintf(error_out_file, "%s\n", error_message);
}
void close_unit_tests(void)
{
if (unit_test_log_file != stdout)
{
fclose(unit_test_log_file);
}
}
static bool log_test_is_positive_path(Test_Log_Data* log_data)
{
bool is_positive = true;
if (!log_data->path)
{
fprintf(error_out_file, "Programmer error: log_data->path is NULL in log_test_is_positive_path()\n");
return false;
}
char* string_to_test = _strdup(log_data->path);
if (!string_to_test)
{
fprintf(error_out_file, "Memory Allocation error: _strdup() failed in log_test_is_positive_path()\n");
fprintf(error_out_file, "Exiting program.\n");
exit(EXIT_FAILURE);
}
char* stt_ptr = string_to_test;
while (*stt_ptr)
{
*stt_ptr = (char) toupper(*stt_ptr);
stt_ptr++;
}
is_positive = (strcmp(string_to_test, "POSITIVE") == 0);
return is_positive;
}
void log_test_status_each_step(char* function_name, bool status, char* path, bool stand_alone)
{
if (stand_alone)
{
fprintf(unit_test_log_file, "%s(): %s Path %s\n", function_name, path,
(status) ? "Passed" : "Failed");
}
}
void log_test_status_each_step2(Test_Log_Data *test_data_to_log)
{
if (test_data_to_log->stand_alone)
{
fprintf(unit_test_log_file, "%s(): %s Path %s\n", test_data_to_log->function_name,
test_data_to_log->path, (test_data_to_log->status) ? "Passed" : "Failed");
}
}
void log_start_positive_path(char* function_name)
{
fprintf(unit_test_log_file, "\nStarting POSITIVE PATH testing for %s\n\n",
function_name);
}
void log_start_positive_path2(Test_Log_Data *log_data)
{
fprintf(unit_test_log_file, "\nStarting POSITIVE PATH testing for %s\n\n",
log_data->function_name);
}
void log_end_positive_path(char* function_name)
{
fprintf(unit_test_log_file, "\nEnding POSITIVE PATH testing for %s\n", function_name);
}
void log_end_positive_path2(Test_Log_Data* log_data)
{
fprintf(unit_test_log_file, "\nEnding POSITIVE PATH testing for %s, POSITIVE PATH %s \n",
log_data->function_name, log_data->status? "PASSED" : "FAILED");
}
void log_start_negative_path(char* function_name)
{
fprintf(unit_test_log_file, "\nStarting NEGATIVE PATH testing for %s\n\n", function_name);
}
void log_end_negative_path(char* function_name)
{
fprintf(unit_test_log_file, "\nEnding NEGATIVE PATH testing for %s\n", function_name);
fflush(unit_test_log_file); // Current unit test is done flush the output.
}
void log_start_test_path(Test_Log_Data* log_data)
{
bool is_positive = log_test_is_positive_path(log_data);
fprintf(unit_test_log_file, "\nStarting %s PATH testing for %s\n\n",
is_positive ? "POSITIVE" : "NEGATIVE", log_data->function_name);
}
void log_end_test_path(Test_Log_Data *log_data)
{
bool is_positive = log_test_is_positive_path(log_data);
fprintf(unit_test_log_file, "\nEnding %s PATH testing for %s, Path %s\n",
is_positive ? "POSITIVE" : "NEGATIVE", log_data->function_name,
log_data->status ? "PASSED" : "FAILED");
if (!is_positive)
{
fflush(unit_test_log_file); // Current unit test is done flush the output.
}
}
void log_generic_message(char* log_message)
{
fprintf(unit_test_log_file, log_message);
}
void init_test_log_data(Test_Log_Data* log_data, char *function_name, bool status, char *path, bool stand_alone)
{
log_data->function_name = function_name;
log_data->status = status;
log_data->path = path;
log_data->stand_alone = stand_alone;
}
Test_Log_Data *create_and_init_test_log_data(char* function_name, bool status, char* path, bool stand_alone)
{
Test_Log_Data* log_data = calloc(1, sizeof(*log_data));
if (log_data)
{
init_test_log_data(log_data, function_name, status, path, stand_alone);
}
else
{
fprintf(error_out_file, "In %s calloc() failed\n", "create_and_init_test_log_data");
}
return log_data;
}
// provides common error report for memory allocation error.
void report_create_and_init_test_log_data_memory_failure(char *function_name)
{
fprintf(error_out_file, "In function %s, Memory allocation failed in create_and_init_test_log_data\n", function_name);
}
Risposte
mystrdup()ha un difetto: per essere * nix-like, mi aspetto di rilevare i casi che potrebbero essere impostati errno.
IMO, usa malloc()e copia anche il carattere null .
Da Quando è una buona idea usare strdup (vs malloc / strcpy)
#include <errno.h>
#include <stdlib.h>
char *mystrdup(const char *s) {
// Optional test, s should point to a string
if (s == NULL) {
#ifdef EINVAL
// For systems that support this "invalid argument" errno
errno = EINVAL;
#ednif
return NULL;
}
size_t siz = strlen(s) + 1;
char *y = malloc(siz);
if (y != NULL) {
memcpy(y, s, siz);
} else {
#ifdef ENOMEM
// For systems that support this "out-of-memory" errno
errno = ENOMEM;
#else
;
#endif
}
return y;
}
I file common_unit_test_logic.*sono troppo complessi.
Il file common_unit_test_logic .c e il file di intestazione non seguono il principio di responsabilità unica che afferma
… Che ogni modulo, classe o funzione dovrebbe avere la responsabilità su una singola parte della funzionalità fornita dal software, e tale responsabilità dovrebbe essere interamente incapsulata da quel modulo, classe o funzione.
Ciò ha forzato le istruzioni #ifdef e #ifndef non necessarie nel codice. Questo è stato rettificato da rottura common_unit_test_logic.ce common_unit_test_logic.hin 3 moduli separati, error_reporting, my_strdup, e unit_test_logging.
Solo il unit_test_loggingmodulo è ancora nella Common_UnitTest_Codedirectory sotto la UnitTestsdirectory. Il error_reportingmodulo e il my_strdupmodulo sono stati entrambi spostati nella VMWithEditordirectory del codice sorgente in modo che possano essere condivisi con il progetto principale e con i progetti di più unit test. Un quarto modulo è default_programstato creato anche per il programma principale e alcuni degli altri test unitari, il codice è stato ifdef'de fuori dal test unitario dell'analizzatore lessicale.
La suddivisione del codice consente un maggiore riutilizzo di ciascuno dei moduli, ma richiede #includeistruzioni aggiuntive in molti dei file.
I moduli separati:
my_strdup.h
#ifndef MY_STRDUP_H
#define MY_STRDUP_H
#include <string.h>
#ifndef strdup
#ifdef _MSC_VER
#if _MSC_VER > 1920
#define strdup _strdup
#endif
#else
#define strdup mystrdup
#endif
#endif
char* mystrdup(const char* string_to_copy);
unsigned char* ucstrdup(const unsigned char* string_to_copy);
#endif // MY_STRDUP_H
my_strdup.c
#include "my_strdup.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char* mystrdup(const char* string_to_copy)
{
char* return_string = NULL;
size_t length = strlen(string_to_copy);
++length;
return_string = calloc(length, sizeof(*return_string));
if (return_string)
{
memcpy(return_string, string_to_copy, length - 1);
}
return return_string;
}
unsigned char* ucstrdup(const unsigned char* string_to_copy)
{
unsigned char* return_string = NULL;
size_t length = strlen((const char*)string_to_copy);
++length;
return_string = calloc(length, sizeof(*return_string));
if (return_string)
{
memcpy(return_string, string_to_copy, length - 1);
}
return return_string;
}
error_reporting.h
#ifndef ERROR_REPORTING_H
#define ERROR_REPORTING_H
#include <stdbool.h>
#include <stdio.h>
extern FILE* error_out_file;
bool init_vm_error_reporting(const char* error_log_file_name);
void disengage_error_reporting(void);
void report_error_generic(const char* error_message);
#endif // !ERROR_REPORTING_H
error_reporting.c
#ifndef ERROR_REPORTING_C
#define ERROR_REPORTING_C
#include "error_reporting.h"
#ifdef UNIT_TESTING
#include "unit_test_logging.h"
#endif // UNIT_TESTING
#include <stdio.h>
FILE* error_out_file = NULL;
bool init_vm_error_reporting(const char* error_log_file_name)
{
bool status_is_good = true;
if (error_log_file_name)
{
error_out_file = fopen(error_log_file_name, "w");
if (!error_out_file)
{
#ifdef UNIT_TESTING
error_out_file = stderr;
#endif // UNIT_TESTING
fprintf(error_out_file, "Can't open error output file, %s", "error_log_file_name");
status_is_good = false;
}
}
else
{
error_out_file = stderr;
}
return status_is_good;
}
void disengage_error_reporting(void)
{
if (error_out_file != stderr)
{
fclose(error_out_file);
}
}
void report_error_generic(const char *error_message)
{
fprintf(error_out_file, "%s\n", error_message);
}
#endif // !ERROR_REPORTING_C
default_program.h
#ifndef DEFAULT_PROGRAM_H
#define DEFAULT_PROGRAM_H
#include "human_readable_program_format.h"
#include <stdint.h>
Human_Readable_Program_Format* default_program(size_t* program_size);
#endif // DEFAULT_PROGRAM_H
programma_predefinito.c
#ifndef DEFAULT_PROGRAM_C
#define DEFAULT_PROGRAM_C
#include "human_readable_program_format.h"
#include "default_program.h"
#include <stdint.h>
Human_Readable_Program_Format* default_program(size_t* program_size)
{
Human_Readable_Program_Format program[] =
{
{PUSH, 0x0A},
{PUSH, 0x43},
{PUSH, 0x42},
{PUSH, 0x41},
{OUTPUTCHAR, 0x00},
{POP, 0x00},
{OUTPUTCHAR, 0x00},
{POP, 0x00},
{OUTPUTCHAR, 0x00},
{POP, 0x00},
{HALT, 0x00}
};
size_t progsize = sizeof(program) / sizeof(*program);
Human_Readable_Program_Format* copy_of_program = duplicate_program(program, progsize);
if (copy_of_program)
{
*program_size = progsize;
}
return copy_of_program;
}
#endif // DEFAULT_PROGRAM_C
unit_test_logging.h
#ifndef UNIT_TEST_LOGGING_H
#define UNIT_TEST_LOGGING_H
#include <stdio.h>
#include <stdbool.h>
typedef struct test_log_data
{
const char* function_name;
char* path;
bool status;
bool stand_alone;
} Test_Log_Data;
extern FILE* unit_test_log_file;
bool init_unit_tests(const char* log_file_name);
void report_create_and_init_test_log_data_memory_failure(const char* function_name);
void log_test_status_each_step(const char* function_name, const bool status, const char* path, const bool stand_alone);
void init_test_log_data(Test_Log_Data* log_data, const char* function_name, const bool status, char* path, const bool stand_alone);
Test_Log_Data* create_and_init_test_log_data(const char* function_name, const bool status, char* path, const bool stand_alone);
void log_test_status_each_step2(const Test_Log_Data* test_data_to_log);
void log_start_positive_path(const char* function_name);
void log_start_positive_path2(const Test_Log_Data* log_data);
void log_start_test_path(const Test_Log_Data* log_data);
void log_end_test_path(const Test_Log_Data* log_data);
void log_end_positive_path(const char* function_name);
void log_end_positive_path2(const Test_Log_Data* log_data);
void log_start_negative_path(const char* function_name);
void log_end_negative_path(const char* function_name);
void log_generic_message(const char *log_message);
void close_unit_tests(void);
#endif // !UNIT_TEST_LOGGING_H
unit_test_logging.c
#include "error_reporting.h"
#include <ctype.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
FILE* unit_test_log_file = NULL;
bool init_unit_tests(const char* log_file_name)
{
if (log_file_name)
{
unit_test_log_file = fopen(log_file_name, "w");
if (!unit_test_log_file)
{
fprintf(error_out_file, "Can't open %s for output\n", log_file_name);
return false;
}
error_out_file = unit_test_log_file;
}
else
{
unit_test_log_file = stdout;
error_out_file = stderr;
}
return true;
}
void close_unit_tests(void)
{
if (unit_test_log_file != stdout)
{
fclose(unit_test_log_file);
}
}
static bool log_test_is_positive_path(const Test_Log_Data* log_data)
{
bool is_positive = true;
if (!log_data->path)
{
fprintf(error_out_file, "Programmer error: log_data->path is NULL in log_test_is_positive_path()\n");
return false;
}
char* string_to_test = strdup(log_data->path);
if (!string_to_test)
{
fprintf(error_out_file, "Memory Allocation error: strdup() failed in log_test_is_positive_path()\n");
fprintf(error_out_file, "Exiting program.\n");
exit(EXIT_FAILURE);
}
char* stt_ptr = string_to_test;
while (*stt_ptr)
{
*stt_ptr = (char) toupper(*stt_ptr);
stt_ptr++;
}
is_positive = (strcmp(string_to_test, "POSITIVE") == 0);
free(string_to_test);
return is_positive;
}
void log_test_status_each_step(const char* function_name, const bool status, const char* path, const bool stand_alone)
{
if (stand_alone)
{
fprintf(unit_test_log_file, "%s(): %s Path %s\n", function_name, path,
(status) ? "Passed" : "Failed");
}
}
void log_test_status_each_step2(const Test_Log_Data *test_data_to_log)
{
if (test_data_to_log->stand_alone)
{
fprintf(unit_test_log_file, "%s(): %s Path %s\n", test_data_to_log->function_name,
test_data_to_log->path, (test_data_to_log->status) ? "Passed" : "Failed");
}
}
void log_start_positive_path(const char* function_name)
{
fprintf(unit_test_log_file, "\nStarting POSITIVE PATH testing for %s\n\n",
function_name);
}
void log_start_positive_path2(const Test_Log_Data *log_data)
{
fprintf(unit_test_log_file, "\nStarting POSITIVE PATH testing for %s\n\n",
log_data->function_name);
}
void log_end_positive_path(const char* function_name)
{
fprintf(unit_test_log_file, "\nEnding POSITIVE PATH testing for %s\n", function_name);
}
void log_end_positive_path2(const Test_Log_Data* log_data)
{
fprintf(unit_test_log_file, "\nEnding POSITIVE PATH testing for %s, POSITIVE PATH %s \n",
log_data->function_name, log_data->status? "PASSED" : "FAILED");
}
void log_start_negative_path(const char* function_name)
{
fprintf(unit_test_log_file, "\nStarting NEGATIVE PATH testing for %s\n\n", function_name);
}
void log_end_negative_path(const char* function_name)
{
fprintf(unit_test_log_file, "\nEnding NEGATIVE PATH testing for %s\n", function_name);
fflush(unit_test_log_file); // Current unit test is done flush the output.
}
void log_start_test_path(const Test_Log_Data* log_data)
{
bool is_positive = log_test_is_positive_path(log_data);
fprintf(unit_test_log_file, "\nStarting %s PATH testing for %s\n\n",
is_positive ? "POSITIVE" : "NEGATIVE", log_data->function_name);
}
void log_end_test_path(const Test_Log_Data *log_data)
{
bool is_positive = log_test_is_positive_path(log_data);
fprintf(unit_test_log_file, "\nEnding %s PATH testing for %s, Path %s\n",
is_positive ? "POSITIVE" : "NEGATIVE", log_data->function_name,
log_data->status ? "PASSED" : "FAILED");
if (!is_positive)
{
fflush(unit_test_log_file); // Current unit test is done flush the output.
}
}
void log_generic_message(const char* log_message)
{
fprintf(unit_test_log_file, log_message);
}
void init_test_log_data(Test_Log_Data* log_data, const char *function_name, const bool status, char *path, bool stand_alone)
{
log_data->function_name = function_name;
log_data->status = status;
log_data->path = path;
log_data->stand_alone = stand_alone;
}
Test_Log_Data *create_and_init_test_log_data(const char* function_name, const bool status, char* path, const bool stand_alone)
{
Test_Log_Data* log_data = calloc(1, sizeof(*log_data));
if (log_data)
{
init_test_log_data(log_data, function_name, status, path, stand_alone);
}
else
{
fprintf(error_out_file, "In %s calloc() failed\n", "create_and_init_test_log_data");
}
return log_data;
}
// provides common error report for memory allocation error.
void report_create_and_init_test_log_data_memory_failure(const char *function_name)
{
fprintf(error_out_file, "In function %s, Memory allocation failed in create_and_init_test_log_data\n", function_name);
}
Aggiorna 9/9/2020.
In risposta alla risposta originale di @ chux-ReinstateMonica e al loro commento di seguito, error_reporting.hora ERH_error_reporting.htutti i simboli globali forniti da quel modulo iniziano con ERH_.
lexical_analyzer.hè stato rinominato LAH_lexical_analyzer.he tutti i simboli globali forniti dall'analizzatore lessicale ora iniziano con LAH_.
my_strdup.hè stato rinominato SSF_safe_string_functions.he tutti i simboli ora iniziano con SSF_, char* SSF_strcat(char* destination, char* source, size_t destination_size);sono state aggiunte funzioni aggiuntive come .
unit_test_logging.hè stato rinominato UTL_unit_test_logging.hcon le modifiche di nome corrispondenti alle strutture, alle funzioni e alla nuova enum che sostituisce la char* pathvariabile nella struttura.
Sono state apportate modifiche al nome simili anche ad almeno altri 3 file.
In risposta alla risposta di @ G.Sliepen sono state aggiunte 2 funzioni variadiche, void UTL_va_log_fprintf(const char* format, ...);in UTL_unit_test_logging.he void ERH_va_report_error_fprintf(const char* format, ...);in ERH_error_reporting.hper ridurre l'utilizzo di sprintf()ed eventuali sprintf()istruzioni rimanenti sono state convertite in snprintf().
I programmi non dipendono più da una BUFSIZdi stdio.h ERH_error_reporting.hfornisce la costante ERH_ERROR_BUFFER_SIZE.