
Rewriting my code to C is somewhat tedious (especially the math lib…), I’m nowhere near converting every C++ file I have, but of what I got so far I like the end result a lot, and I enjoy the shift I’m seeing in my reasoning ability.
Not having to worry about object oriented programming allows me to cut a lot of fat in the code, make a lot of things way simpler and clearer, and reason better about what I want to do and how I do it.
Here is my reworked test library header:
#ifndef AME_TEST_H
#define AME_TEST_H
/* @library ame_test: very lightweight C test library */
#ifdef __cplusplus
extern "C" {
#endif
#ifndef __cplusplus
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#else
#include <cstdlib>
#include <cstdio>
#include <cstdint>
#include <cstring>
#endif
#ifndef AME_TYPES
typedef uint8_t uint8;
typedef int8_t int8;
typedef uint16_t uint16;
typedef int16_t int16;
typedef uint32_t uint32;
typedef int32_t int32;
typedef uint64_t uint64;
typedef int64_t int64;
typedef float real32;
typedef double real64;
#endif
/* declares a test */
#define TEST(name) void name(bool *out_error)
/* triggers a test error */
#define TEST_ERROR_EMPTY() printf_s("%s (%i)\n", __FILE__, __LINE__); *out_error = false
/* triggers a test error with message */
#define TEST_ERROR(error) printf_s("%s (%i) : %s\n", __FILE__, __LINE__, error); *out_error = false
/* tests if expressions are equal */
#define TEST_EQUAL(value1, value2) if ((value1 == value2) == false){ printf_s("%s (%i) : %s not equal %s\n", __FILE__, __LINE__, #value1, #value2); *out_error = false; }
/* tests if expressions are different */
#define TEST_NOT_EQUAL(value1, value2) if ((value1 != value2) == false){ printf_s("%s (%i) : %s equal %s\n", __FILE__, __LINE__, #value1, #value2); *out_error = false; }
/* tests if expression is true */
#define TEST_TRUE(value) if (value == false){ printf_s("%s (%i) : %s is false\n", __FILE__, __LINE__, #value); *out_error = false; }
/* tests if expression is false */
#define TEST_FALSE(value) if (value == true) { printf_s("%s (%i) : %s is true\n", __FILE__, __LINE__, #value); *out_error = false; }
#define C_TEST_NULL_NAME "null" /* name of null test/suite */
#define C_NULL_TEST { C_TEST_NULL_NAME, NULL } /* null test initialization */
#define C_NULL_TEST_SUITE gc_null_test_suite /* null suite initialization */
typedef void (*test_func)(bool*); /* test function type */
/* test struct with test name and function */
typedef struct {
char name[64];
test_func function;
} Test;
/* test suite struct with suite name and tests */
typedef struct {
char name[64];
Test *tests; /* array of tests terminated by a null test */
} TestSuite;
extern const TestSuite gc_null_test_suite; /* null test suite */
/* runs all tests suite
* @param suites: array of TestSuite terminated with a null test suite
* @return 0 if test is successful
*/
int32 test_run(const char* module, TestSuite* suites);
#ifdef __cplusplus
}
#endif
#endif // AME_TEST_H
Usage
That mini-mini-library by no means has extensive features, but for now it has all I need. Here is a small example of how to use it:
#include "ame_test.h"
TEST(example_test)
{
int foo = 4;
TEST_EQUAL(foo, 4);
TEST_NOT_EQUAL(foo, 5);
bool bar = foo == 4;
TEST_TRUE(bar);
bar = foo == 6;
TEST_FALSE(bar);
}
Test _my_tests[] =
{
{ "example_test", example_test },
C_NULL_TEST
};
TestSuite _my_suites[] =
{
{ "example_suite", _my_tests },
{ "", NULL }
};
int main(int argc, char** argv)
{
(void)argc;
(void)argv;
int res = test_run("example module", _my_suites);
return res;
}
Output when running this:
start test module example module
running test suite example module/example_suite
example module/example_suite/example_test [ OK ] [ 0.20 ms ]
1 of 1 tests successful [ 1.00 ms ]
end test module example module [ 1.74 ms ]
When running the currently converted core library tests it gives something like this:
start test_core
start test module core
running test suite core/memory
core/memory/frame_allocator [ OK ] [ 0.08 ms ]
core/memory/stack_allocator [ OK ] [ 0.07 ms ]
core/memory/pool_allocator [ OK ] [ 0.03 ms ]
core/memory/random_access_allocator [ OK ] [ 0.15 ms ]
4 of 4 tests successful [ 3.09 ms ]
running test suite core/string
core/string/basic_string [ OK ] [ 0.02 ms ]
core/string/fixed_string [ OK ] [ 0.02 ms ]
core/string/dynamic_string [ OK ] [ 0.07 ms ]
3 of 3 tests successful [ 2.07 ms ]
running test suite core/commands
core/commands/interp [ OK ] [ 0.30 ms ]
1 of 1 tests successful [ 1.59 ms ]
running test suite core/filesystem
core/filesystem/dir_functions [ OK ] [ 98.37 ms ]
1 of 1 tests successful [ 100.74 ms ]
end test module core [ 115.72 ms ]
terminate test_core
Could be better so I’ll keep improving it. I’ll probably add fixture-like stuff in the future, maybe parameterized test features, even though I can write most of my unit tests without that.
I got a lot of inspiration from a really neat C unit test library: µnit
Check that out if you want a real good unit-test lib.
Now I C
Going full C and not just C-style C++, I am happy about:
- no operator/function overloading: at first I was very worried about not having those anymore, but now I see why it’s much better without. Not having to worry about implicit conversions anymore, I feel I have more control over what each function does
- strict naming, documentation & indentation/alignment it’s probably useless because I don’t intend to share or deploy my code any time soon, but a clean looking header with consistent naming just warms my heart. It’s a good exercise in being rigorous anyway
- slowly training my brain to program C more efficiently after years of corrupting my thinking with the OOP, I find myself going back to the way I was programming in college, right after learning C. Programming now feels simpler, more fluid, and I think I’m getting already good reflexes to not get in my own way
Let’s hear it for the boys
I’ve been following the advice, or the work of:
- Casey Muratori – I feel like I should watch all of Handmade Hero and I would learn a great deal, so far I’ve watched bits here and there, but mostly I enjoyed his epic rants videos I’ve found (I’m very ashamed of myself…)
- Ryan Fleury – I’ve been following Ryan’s Youtube channel for a while, his Melodist game looks great, it’s because his way of coding looked really cool that I decided to change my approach, so thanks Ryan!
- Eskil Steenberg – I’m only a few minutes through this video but I’m already getting an insane amount of good tricks and tips. I have a feeling I will learn A LOT from him…
There is a good amount to learn still and much (MUCH) more code to write to get closer to the goals I want to achieve, but I have a feeling that keep on following these guys is gonna help me a lot!