Código de teste de unidade comum - Acompanhamento

Sep 02 2020

Esta pergunta é uma pergunta de acompanhamento à parte do Código de teste de unidade comum das minhas perguntas do analisador léxico.

Minha principal preocupação é o código do arquivo de cabeçalho e o arquivo-fonte C que implementa strdup (). Como o programa do qual este código faz parte foi projetado para ser multiplataforma, ele precisa ser compilado e executado em Windows ou Linux e deve ser compatível com ambos. A strdup()função faz parte do padrão C2X C, portanto, se estiver disponível, o código deve continuar a compilar e funcionar. O #defines no arquivo de cabeçalho é baseado na gccversão de string.h.

Uma preocupação secundária é o desempenho, muitos dos parâmetros foram alterados para const. Os membros da estrutura Test_Log_Data foram reordenados para melhorar o uso da memória.

Uma terceira preocupação era o uso arcaico, o externo que precede os protótipos de função foi removido em todos os arquivos de cabeçalho, não apenas common_unit_test_logic.h.

O código original é fornecido para comparação.

Novo Código

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);
}

Código Original :

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);
}

Respostas

2 chux-ReinstateMonica Sep 08 2020 at 22:55

mystrdup()tem um defeito: para ser semelhante a * nix, esperaria detectar casos que podem se configurar errno.

IMO, use malloc()e copie o caractere nulo também.

De Quando é uma boa ideia usar 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;
}
2 pacmaninbw Sep 03 2020 at 20:10

Os arquivos common_unit_test_logic.*são muito complexos.

O arquivo common_unit_test_logic .c e o arquivo de cabeçalho não seguem o Princípio de Responsabilidade Única que afirma

… Que cada módulo, classe ou função deve ter responsabilidade sobre uma única parte da funcionalidade fornecida pelo software, e essa responsabilidade deve ser totalmente encapsulada por esse módulo, classe ou função.

Isso forçou instruções #ifdef e #ifndef desnecessárias no código. Este foi rectificado pela quebra common_unit_test_logic.ce common_unit_test_logic.hem 3 módulos separados, error_reporting, my_strdup, e unit_test_logging.

Apenas o unit_test_loggingmódulo ainda está no Common_UnitTest_Codediretório sob o UnitTestsdiretório. O error_reportingmódulo e o my_strdupmódulo foram movidos para o VMWithEditordiretório do código-fonte para que possam ser compartilhados com o projeto principal, bem como com os vários projetos de teste de unidade. Um quarto módulo default_programtambém foi criado para o programa principal e alguns dos outros testes unitários, o código foi ifdef'de fora do teste unitário do analisador léxico.

Dividir o código permite maior reutilização de cada um dos módulos, mas requer #includeinstruções adicionais em muitos dos arquivos.

Os módulos separados:

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

default_program.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);
}

Atualização 09/09/2020.

Em resposta à resposta original de @ chux-ReinstateMonica, bem como ao comentário abaixo, error_reporting.hé agora ERH_error_reporting.h, todos os símbolos globais fornecidos por esse módulo começam com ERH_.

lexical_analyzer.hfoi renomeado LAH_lexical_analyzer.he todos os símbolos globais fornecidos pelo analisador léxico agora começam com LAH_.

my_strdup.hfoi renomeado SSF_safe_string_functions.he todos os símbolos agora começam com SSF_funções adicionais, como char* SSF_strcat(char* destination, char* source, size_t destination_size);foram adicionadas.

unit_test_logging.hfoi renomeado UTL_unit_test_logging.hcom as alterações de nome correspondentes às estruturas, funções e novo enum que substitui a char* pathvariável na estrutura.

Alterações de nome semelhantes também foram feitas em pelo menos 3 outros arquivos.

Em resposta à resposta por @ G.Sliepen duas função variádica foram adicionados, void UTL_va_log_fprintf(const char* format, ...);em UTL_unit_test_logging.he void ERH_va_report_error_fprintf(const char* format, ...);em ERH_error_reporting.hreduzir o uso de sprintf()e quaisquer restantes sprintf()declarações foram convertidos para snprintf().

Os programas não dependem mais de um BUFSIZfrom stdio.h ERH_error_reporting.hfornece a constante ERH_ERROR_BUFFER_SIZE.