3
\$\begingroup\$

I have improved my code here and it looks like this:

Code

assertlib.h:

#ifndef COM_GITHUB_CODERODDE_ASSERTLIB_H
#define COM_GITHUB_CODERODDE_ASSERTLIB_H

void print_test_statistics();
int get_exit_status();

#define ASSERT_IMPL(COND, FILE, LINE) assert_impl(COND, #COND, FILE, LINE);
#define ASSERT(COND) ASSERT_IMPL(COND, __FILE__, __LINE__)

#endif

assertlib.c:

#include "assertlib.h"
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>

#define COMPLETION_BAR_LENGTH 80

static size_t passes = 0;
static size_t failed = 0;

void assert_impl(bool passed,
                 const char* condition_text,
                 const char* file_name,
                 size_t line_number) {
    if (passed) {
        passes++;
    } else {
        failed++;

        fprintf(stderr, 
                "Failed condition \"%s\" in file \"%s\" at line %zu.\n",
                condition_text,
                file_name,
                line_number);
    }
}

int get_exit_status() {
    return failed == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
}

static void print_bar(size_t bar_length) {
    size_t total_assertions = passes + failed;

    char* bar = calloc(bar_length + 16, sizeof(char));

    if (bar == NULL) {
        return;
    }

    bar[0] = '[';
    bar[bar_length + 1] = ']';

    float ratio = (float)(passes) / (float)(total_assertions);

    size_t hash_symbols = (size_t)(bar_length * ratio);

    for (size_t i = 0; i < hash_symbols; i++) {
        bar[i + 1] = '#';
    }

    for (size_t i = 0; i < bar_length - hash_symbols; i++) {
        bar[i + 1 + hash_symbols] = '.';
    }

    printf("%s: %.2f%%.\n", bar, 100.0f * ratio);
    free(bar);
}

void print_test_statistics() {
    printf("Passes: %zu, failures: %zu, total assertions: %zu.\n",
           passes,
           failed,
           passes + failed);

    print_bar(COMPLETION_BAR_LENGTH);
}

main.c:

#include "assertlib.h"

int main()
{
    ASSERT(1 + 1 == 2);
    ASSERT(1);
    ASSERT(1 != 1);
    ASSERT(1 + 2 == 3);

    print_test_statistics();

    return get_exit_status();
}

Unit test output

The output looks like this:

Failed condition "1 != 1" in file "C:\Users\rodio\Documents\VSProjects\assertlib.h\main.c" at line 7.
Passes: 3, failures: 1, total assertions: 4.
[############################################################....................]: 75.00%.

Critique request

As always, I would like to hear anything that comes to mind.

\$\endgroup\$
1
  • 2
    \$\begingroup\$ It appears we could elide ASSERT_IMPL. Looks good otherwise. \$\endgroup\$
    – J_H
    Feb 19 at 19:46

1 Answer 1

3
\$\begingroup\$

In C, void get_exit_status() declares a function that takes an unspecified number of parameters, not a function that takes none.

If the function expects no arguments, it should be defined with the keyword void, unless you are compiling with C2X, where empty parentheses defines a function that takes no arguments, akin to C++. In which case you should also remove the inclusion of stdbool.h, as bool, true, and false are keywords in C23.

You also do not require the inclusion of stddef.h, as stdio.h already declares size_t.


As print_bar() is always passed in COMPLETION_BAR_LENGTH, we can do away with the call to calloc() and replace it with a fixed-size array:

#if 0
char* bar = calloc(bar_length + 16, sizeof(char));
#else
char bar[COMPLETION_BAR_LENGTH + 16] = {0};
#endif

Though I do not see why the memory needs to be zero-initialized, especially when it is overwritten right after its initialization.


Note that sizeof(char) is defined by the C standard to be 1, so you can safely replace it with 1.


Consider conditionally dealing with passed and returning early in assert_impl().

That way you can deal with failed unconditionally, saving four spaces of indent.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.