Skip to content

Commit

Permalink
Merge pull request #3 from christopherkinyua/chris/create-create-unit…
Browse files Browse the repository at this point in the history
…-test-framework-i2

Create create unit test framework
  • Loading branch information
christopherkinyua authored Oct 31, 2024
2 parents 4c539c5 + 7635254 commit 2548026
Show file tree
Hide file tree
Showing 12 changed files with 302 additions and 39 deletions.
90 changes: 90 additions & 0 deletions docs/C_Unit_Tests.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# C Unit Tests

This repo uses an in-house unit test framework, which is about as minimal as unit tests can be.

## How to Write a Test

The following guide demonstrates an example of creating a testable function, and then writing
unit tests for that function.

1. Come up with a testable function (called the Function-Under-Test, or FUT). For examples:

```c
// File: transforms/byte_transforms.c

#include "transforms/byte_transforms.h"

uint32_t GEN_convert_big_endian_four_byte_array_to_uint32(uint8_t four_byte_array[]) {
// recall: big endian means the Most Significant Bit (MSB) is at index 0 (left side)
return (four_byte_array[0] << 24) | (four_byte_array[1] << 16) |
(four_byte_array[2] << 8) | four_byte_array[3];
}
```

2. Create a new file in `Core/Src/unit_tests` called `test_bytes_transforms.c`. Create the unit test
function within that file:

```c
// File: unit_tests/test_bytes_transforms.c

#include "unit_tests/unit_test_helpers.h" // for all unit tests
#include "transforms/byte_transforms.h" // for the Function-Under-Test

uint8_t TEST_EXEC__GEN_convert_big_endian_four_byte_array_to_uint32() {
// TODO: implement unit test later
TEST_ASSERT_TRUE(1 == 1);

return 0; // THIS LINE MUST BE INCLUDED to indicate the test passed
}
```

3. Add the function prototype to the header file `Core/Inc/unit_tests/test_bytes_transforms.h`:

```c
// File: unit_tests/test_bytes_transforms.h
// ...
uint8_t TEST_EXEC__GEN_convert_big_endian_four_byte_array_to_uint32();
// ...
```

4. Add the test to the "inventory table" of tests in `Core/Src/unit_tests/unit_test_inventory.c`:

```c
// File: unit_tests/unit_test_inventory.c
#include "unit_tests/test_byte_transforms.h"

const TEST_Definition_t TEST_definitions[] = {
// ...
// Add the following section:
{
.test_func = TEST_EXEC__GEN_convert_big_endian_four_byte_array_to_uint32,
.test_file = "transforms/byte_transforms",
.test_func_name = "GEN_convert_big_endian_four_byte_array_to_uint32"
},
// ...
};

```

5. Flash the ESP32, and check the monitor ESP-IDF Monitor window. See that the test passes.

6. Go back to the `TEST_EXEC__GEN_convert_big_endian_four_byte_array_to_uint32` function, and implement
the test:

```c
// File: unit_tests/test_bytes_transforms.c

uint8_t TEST_EXEC__GEN_convert_big_endian_four_byte_array_to_uint32() {
uint8_t test_array_1[4] = {0x12, 0x34, 0x56, 0x78};
uint32_t expected_result_1 = 0x12345678;
TEST_ASSERT_TRUE(GEN_convert_big_endian_four_byte_array_to_uint32(test_array_1) == expected_result_1);

uint8_t test_array_2[4] = {0xFE, 0xDC, 0xBA, 0x98};
uint32_t expected_result_2 = 4275878552; // 0xFEDCBA98
TEST_ASSERT_TRUE(GEN_convert_big_endian_four_byte_array_to_uint32(test_array_2) == expected_result_2);

return 0; // THIS LINE MUST BE INCLUDED to indicate the test passed
}
```

7. Run the test again.
8 changes: 4 additions & 4 deletions firmware/Core/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# Define the source and include directories
set(INCLUDE_DIRS Inc)
set(INCLUDE_DIRS Inc Inc/unit_tests)
set(SRC_DIR Src)

# Automatically include all .c files in src
file(GLOB SRC_FILES "${SRC_DIR}/*.c")
# Automatically include all .c files in Core/Src and Core/Src/unit_tests
file(GLOB SRC_FILES "${SRC_DIR}/*.c" "${SRC_DIR}/unit_tests/*.c")

# Register the component with ESP-IDF
idf_component_register(SRCS ${SRC_FILES}
INCLUDE_DIRS ${INCLUDE_DIRS}
REQUIRES driver)
REQUIRES driver esp_timer)
9 changes: 5 additions & 4 deletions firmware/Core/Inc/pwm.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@
#include <stdint.h>

#ifdef __cplusplus
extern "C" {
extern "C"
{
#endif

void LED_PWM_Init(void);
void LED_set_dimming(uint32_t duty_percentage);
void LED_test_dimming(void);
void LED_PWM_Init(void);
uint8_t LED_set_dimming(uint32_t duty_percentage);
void LED_test_dimming(void);

#ifdef __cplusplus
}
Expand Down
10 changes: 10 additions & 0 deletions firmware/Core/Inc/unit_tests/unit_test_executor.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#ifndef __INCLUDE_GUARD__UNIT_TEST_EXECUTOR_H__
#define __INCLUDE_GUARD__UNIT_TEST_EXECUTOR_H__

#include "main.h"

#include "unit_test_inventory.h"

uint8_t TEST_run_all_unit_tests_and_log(char log_buffer[], uint16_t log_buffer_size);

#endif // __INCLUDE_GUARD__UNIT_TEST_EXECUTOR_H__
9 changes: 9 additions & 0 deletions firmware/Core/Inc/unit_tests/unit_test_helpers.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#ifndef __INCLUDE_GUARD__UNIT_TEST_HELPERS_H__
#define __INCLUDE_GUARD__UNIT_TEST_HELPERS_H__

#define TEST_ASSERT(x) if (!(x)) { return 1; }
#define TEST_ASSERT_TRUE(x) if (!(x)) { return 1; }
#define TEST_ASSERT_FALSE(x) if ((x)) { return 1; }

#endif // __INCLUDE_GUARD__UNIT_TEST_HELPERS_H__

19 changes: 19 additions & 0 deletions firmware/Core/Inc/unit_tests/unit_test_inventory.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#ifndef __INCLUDE_GUARD__UNIT_TEST_INVENTORY_H__
#define __INCLUDE_GUARD__UNIT_TEST_INVENTORY_H__

#include <stdint.h>

typedef uint8_t (*TEST_Function_Ptr)();

typedef struct {
TEST_Function_Ptr test_func;
char* test_file;
char* test_func_name;
} TEST_Definition_t;


extern const TEST_Definition_t TEST_definitions[];
extern const int16_t TEST_definitions_count;


#endif // __INCLUDE_GUARD__UNIT_TEST_INVENTORY_H__
8 changes: 8 additions & 0 deletions firmware/Core/Inc/unit_tests/unit_test_pwm.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#ifndef __INCLUDE_GUARD__UNIT_TEST_PWM_H__
#define __INCLUDE_GUARD__UNIT_TEST_PWM_H__

#include <stdint.h>

uint8_t TEST_EXEC__LED_set_dimming();

#endif // __INCLUDE_GUARD__UNIT_TEST_PWM_H__
32 changes: 24 additions & 8 deletions firmware/Core/Src/main.c
Original file line number Diff line number Diff line change
@@ -1,23 +1,39 @@
#include "main.h"
#include "IO.h"
#include "pwm.h"
#include "unit_tests/unit_test_executor.h"

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "esp_log.h"

#include "main.h"
#include "IO.h"
#include "pwm.h"


void app_main(void) {
void app_main(void)
{

GPIO_init();
LED_PWM_Init();

char response_output_buf[2048] = {0};
uint8_t unit_tests_result = TEST_run_all_unit_tests_and_log(response_output_buf, sizeof(response_output_buf));
printf(response_output_buf);

if (unit_tests_result)
{
printf("\nAll tests passed!\n");
}
else
{
printf("\nSome tests failed!\n");
}

ESP_LOGI("Main", "Hello, ESP32-C3!");

// For demonstration, create a simple task
while (true) {
vTaskDelay(1000 / portTICK_PERIOD_MS); // Delay 1 second
while (true)
{
vTaskDelay(1000 / portTICK_PERIOD_MS); // Delay 1 second
ESP_LOGI("Main", "Running...");
LED_test_dimming();
}
Expand Down
64 changes: 41 additions & 23 deletions firmware/Core/Src/pwm.c
Original file line number Diff line number Diff line change
Expand Up @@ -9,28 +9,27 @@
/// @brief : LEDC Timer and Channel Configuration
/// @param None
/// TODO: Add other channels for the RGBWW PWM Dimming
void LED_PWM_Init(void) {
void LED_PWM_Init(void)
{

// Setting up the LEDC Timer Configuration
ledc_timer_config_t ledc_timer = {
.speed_mode = LEDC_LOW_SPEED_MODE,
.timer_num = LEDC_TIMER_0,
.duty_resolution = LEDC_TIMER_13_BIT, // 13-bit resolution for the duty cycle
.freq_hz = 5000, // Frequency in Hertz
.clk_cfg = LEDC_AUTO_CLK
};
.speed_mode = LEDC_LOW_SPEED_MODE,
.timer_num = LEDC_TIMER_0,
.duty_resolution = LEDC_TIMER_13_BIT, // 13-bit resolution for the duty cycle
.freq_hz = 5000, // Frequency in Hertz
.clk_cfg = LEDC_AUTO_CLK};
ledc_timer_config(&ledc_timer);

// Setting up the LEDC channel Configuration for GPIO pin 0
ledc_channel_config_t ledc_channel = {
.speed_mode = LEDC_LOW_SPEED_MODE,
.channel = LEDC_CHANNEL_0,
.timer_sel = LEDC_TIMER_0,
.intr_type = LEDC_INTR_DISABLE,
.gpio_num = GPIO_NUM_0,
.duty = 0, // Initially off
.hpoint = 0
};
.speed_mode = LEDC_LOW_SPEED_MODE,
.channel = LEDC_CHANNEL_0,
.timer_sel = LEDC_TIMER_0,
.intr_type = LEDC_INTR_DISABLE,
.gpio_num = GPIO_NUM_0,
.duty = 0, // Initially off
.hpoint = 0};
ledc_channel_config(&ledc_channel);
}

Expand All @@ -39,27 +38,46 @@ void LED_PWM_Init(void) {
/// - Arg 0: The desired brightness level as a percentage (uint32_t)
/// @return 0 on success, > 0 on error
/// TODO: Add error checks for the ledc_set_duty and ledc_update_duty
void LED_set_dimming(uint32_t duty_percentage) {
uint8_t LED_set_dimming(uint32_t duty_percentage)
{
if (duty_percentage > 100)
{
return 1;
}

// Calculate duty cycle based on percentage (0-100)
uint32_t duty = (8191 * duty_percentage) / 100; // 8191 = 2^13 - 1 (13-bit resolution)

// Set the duty cycle and update the PWM output
ledc_set_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, duty);
ledc_update_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0);

return 0;
}

/// @brief 0-10V Dimming: Testing the 0-10V Dimming through PWM
/// @param None
/// @return 0 on success, >0 on error
/// TODO: Implement the error checks obtained from LED_set_dimming
void LED_test_dimming(void) {
for (int i = 0; i <= 100; i += 5) {
LED_set_dimming(i);
vTaskDelay(500 / portTICK_PERIOD_MS); // Delay 500ms
void LED_test_dimming(void)
{
uint8_t dimming_result;
for (int i = 0; i <= 100; i += 5)
{
dimming_result = LED_set_dimming(i);
if (dimming_result == 0)
{
continue;
}
vTaskDelay(500 / portTICK_PERIOD_MS); // Delay 500ms
}
for (int i = 100; i >= 0; i -= 5) {
LED_set_dimming(i);
vTaskDelay(500 / portTICK_PERIOD_MS); // Delay 500ms
for (int i = 100; i >= 0; i -= 5)
{
dimming_result = LED_set_dimming(i);
if (dimming_result == 0)
{
continue;
}
vTaskDelay(500 / portTICK_PERIOD_MS); // Delay 500ms
}
}
57 changes: 57 additions & 0 deletions firmware/Core/Src/unit_tests/unit_test_executor.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#include "unit_tests/unit_test_executor.h"
#include "unit_tests/unit_test_inventory.h"

#include "main.h"

#include "string.h"
#include "stdio.h"
#include "esp_timer.h"

uint8_t TEST_run_all_unit_tests_and_log(char log_buffer[], uint16_t log_buffer_size)
{
uint16_t total_exec_count = 0;
uint16_t total_pass_count = 0;
uint16_t total_fail_count = 0;
const uint32_t start_time_ms = esp_timer_get_time() / 1000;

log_buffer[0] = '\0';

for (uint8_t test_num = 0; test_num < TEST_definitions_count; test_num++)
{
TEST_Function_Ptr test_function = TEST_definitions[test_num].test_func;
uint8_t result = test_function();

char test_log_buffer[200];
snprintf(
test_log_buffer,
sizeof(test_log_buffer),
"Test #%03d: %s (%s > %s)\n",
test_num,
(result == 0) ? "PASS ✅" : "FAIL ❌",
TEST_definitions[test_num].test_file,
TEST_definitions[test_num].test_func_name);
printf(test_log_buffer);

total_exec_count++;
if (result == 0)
{
total_pass_count++;
}
else
{
total_fail_count++;
}
}
const uint32_t end_time_ms = esp_timer_get_time() / 1000;

snprintf(
log_buffer,
log_buffer_size,
"Total tests: %d - Pass: %d, Fail: %d, Duration: %lums",
total_exec_count,
total_pass_count,
total_fail_count,
end_time_ms - start_time_ms);

return total_fail_count == 0;
}
15 changes: 15 additions & 0 deletions firmware/Core/Src/unit_tests/unit_test_inventory.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#include "unit_tests/unit_test_helpers.h"
#include "unit_tests/unit_test_inventory.h"
#include "unit_tests/unit_test_pwm.h"

// extern
const TEST_Definition_t TEST_definitions[] = {

{.test_func = TEST_EXEC__LED_set_dimming,
.test_file = "unit_tests/unit_test_pwm",
.test_func_name = "LED_set_dimming"},

};

// extern
const int16_t TEST_definitions_count = sizeof(TEST_definitions) / sizeof(TEST_Definition_t);
Loading

0 comments on commit 2548026

Please sign in to comment.