Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .github/hardware-roots.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,15 @@ ALL:
- "lib/f_core/**"
- "snippets/**"

# Unit Tests
app/tests/f_core/gnss_utils:
- paths:
- "app/tests/f_core/gnss_utils/**"
- "include/f_core/utils/n_gnss_utils.h"

app/tests/f_core/golay:
- paths:
- "app/tests/f_core/golay/**"
- "include/f_core/radio/protocols/horus/golay.h"
- "lib/f_core/radio/protocols/horus/golay.c"

1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,4 @@ __pycache__

flash/
app/payload/control_freak/flight-software/ramreport
_codeql_detected_source_root
138 changes: 138 additions & 0 deletions app/tests/f_core/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
# F_Core Unit Tests

This directory contains unit tests for the core library modules located in `include/f_core` and `lib/f_core`.

## Test Structure

Each test directory should contain:
- `main.c` - Test implementation using ZTest framework
- `CMakeLists.txt` - CMake configuration for the test
- `prj.conf` - Zephyr project configuration
- `testcase.yaml` - Test metadata for Twister discovery
- `Kconfig` - Kconfig configuration file

## Running Tests

Tests are automatically discovered and run by the CI using `west twister`. The CI uses the mappings in `.github/hardware-roots.yaml` to determine which tests to run based on changed files.

To run tests locally (after setting up the Zephyr environment):

```bash
# Run a specific test
west twister -T app/tests/f_core/gnss_utils

# Run all f_core tests
west twister -T app/tests/f_core

# Run tests on a specific platform
west twister -T app/tests/f_core -p native_sim
```

## Writing New Tests

### 1. Create Test Directory Structure

Create a new directory under `app/tests/f_core/` for your module:

```bash
mkdir -p app/tests/f_core/<module_name>
```

### 2. Create Test Implementation (`main.c`)

Use the ZTest framework. Example:

```c
#include <zephyr/ztest.h>
#include <f_core/your/header.h>

ZTEST(test_suite_name, test_case_name) {
// Test implementation
zassert_equal(actual, expected, "Error message");
}

ZTEST_SUITE(test_suite_name, NULL, NULL, NULL, NULL, NULL);
```

### 3. Create CMakeLists.txt

```cmake
# SPDX-License-Identifier: Apache-2.0

cmake_minimum_required(VERSION 3.20.0)

find_package(Zephyr COMPONENTS unittest REQUIRED HINTS $ENV{ZEPHYR_BASE})

project(your_test_name)

# Include f_core headers
include_directories(${ZEPHYR_BASE}/../FSW.git/include)

target_sources(testbinary
PRIVATE
main.c
# Add any source files from lib/f_core if needed
)
```

### 4. Create prj.conf

```
CONFIG_ZTEST=y
# Add any additional configuration needed for your tests
```

### 5. Create testcase.yaml

```yaml
common:
tags:
- f_core
- <module_category>
tests:
f_core.<module_name>:
type: unit
```

### 6. Create Kconfig

```
mainmenu "Test Configuration"

source "Kconfig.zephyr"
```

### 7. Update CI Configuration

Add your test to `.github/hardware-roots.yaml`:

```yaml
app/tests/f_core/<module_name>:
- paths:
- "app/tests/f_core/<module_name>/**"
- "include/f_core/path/to/header.h"
- "lib/f_core/path/to/source.c" # if applicable
```

## ZTest Assertions

Common ZTest assertions:
- `zassert_true(condition, msg, ...)` - Assert condition is true
- `zassert_false(condition, msg, ...)` - Assert condition is false
- `zassert_equal(a, b, msg, ...)` - Assert a == b
- `zassert_not_equal(a, b, msg, ...)` - Assert a != b
- `zassert_within(actual, expected, delta, msg, ...)` - Assert |actual - expected| <= delta
- `zassert_null(ptr, msg, ...)` - Assert pointer is NULL
- `zassert_not_null(ptr, msg, ...)` - Assert pointer is not NULL

## Examples

See existing tests for reference:
- `app/tests/utils/` - C++ debouncer tests
- `app/tests/f_core/gnss_utils/` - GNSS utility function tests
- `app/tests/f_core/golay/` - Golay protocol encoding/decoding tests

## References

- [Zephyr ZTest Documentation](https://docs.zephyrproject.org/latest/develop/test/ztest.html)
- [Zephyr Twister Documentation](https://docs.zephyrproject.org/latest/develop/test/twister.html)
25 changes: 25 additions & 0 deletions app/tests/f_core/gnss_utils/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# SPDX-License-Identifier: Apache-2.0

cmake_minimum_required(VERSION 3.20.0)

find_package(Zephyr COMPONENTS unittest REQUIRED HINTS $ENV{ZEPHYR_BASE})

project(gnss_utils_test)

# Include f_core headers
# Try to find FSW root from Zephyr base or use current project structure
if(EXISTS "${ZEPHYR_BASE}/../FSW.git")
set(FSW_ROOT "${ZEPHYR_BASE}/../FSW.git")
elseif(EXISTS "${ZEPHYR_BASE}/../FSW")
set(FSW_ROOT "${ZEPHYR_BASE}/../FSW")
else()
# Fallback to relative path from test directory
set(FSW_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/../../../..")
endif()

include_directories(${FSW_ROOT}/include)

target_sources(testbinary
PRIVATE
main.c
)
3 changes: 3 additions & 0 deletions app/tests/f_core/gnss_utils/Kconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
mainmenu "Test Configuration"

source "Kconfig.zephyr"
79 changes: 79 additions & 0 deletions app/tests/f_core/gnss_utils/main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
* Copyright (c) 2024 RIT Launch Initiative
*
* SPDX-License-Identifier: Apache-2.0
*/

#include <f_core/utils/n_gnss_utils.h>
#include <math.h>
#include <zephyr/ztest.h>

ZTEST(gnss_utils, test_nanodegrees_to_degrees_float) {
// Test positive latitude conversion
int64_t nanodegrees = 45123456789LL; // 45.123456789 degrees
float result = NGnssUtils::NanodegreesToDegreesFloat(nanodegrees);
zassert_within(result, 45.123456789f, 0.0001f, "Positive float conversion failed");

// Test negative latitude conversion
nanodegrees = -90000000000LL; // -90.0 degrees
result = NGnssUtils::NanodegreesToDegreesFloat(nanodegrees);
zassert_within(result, -90.0f, 0.0001f, "Negative float conversion failed");

// Test zero
nanodegrees = 0;
result = NGnssUtils::NanodegreesToDegreesFloat(nanodegrees);
zassert_within(result, 0.0f, 0.0001f, "Zero float conversion failed");
}

ZTEST(gnss_utils, test_nanodegrees_to_degrees_double) {
// Test positive latitude conversion
int64_t nanodegrees = 45123456789LL; // 45.123456789 degrees
double result = NGnssUtils::NanodegreesToDegreesDouble(nanodegrees);
zassert_within(result, 45.123456789, 0.0000001, "Positive double conversion failed");

// Test negative latitude conversion
nanodegrees = -90000000000LL; // -90.0 degrees
result = NGnssUtils::NanodegreesToDegreesDouble(nanodegrees);
zassert_within(result, -90.0, 0.0000001, "Negative double conversion failed");

// Test zero
nanodegrees = 0;
result = NGnssUtils::NanodegreesToDegreesDouble(nanodegrees);
zassert_within(result, 0.0, 0.0000001, "Zero double conversion failed");
}

ZTEST(gnss_utils, test_millimeters_to_meters_float) {
// Test positive altitude conversion
int64_t millimeters = 1500000LL; // 1500 meters
float result = NGnssUtils::MillimetersToMetersFloat(millimeters);
zassert_within(result, 1500.0f, 0.001f, "Positive meters float conversion failed");

// Test negative altitude conversion
int64_t negative_mm = -500000LL; // -500 meters
result = NGnssUtils::MillimetersToMetersFloat(negative_mm);
zassert_within(result, -500.0f, 0.001f, "Negative meters float conversion failed");

// Test zero
millimeters = 0;
result = NGnssUtils::MillimetersToMetersFloat(millimeters);
zassert_within(result, 0.0f, 0.001f, "Zero meters float conversion failed");
}

ZTEST(gnss_utils, test_millimeters_to_meters_double) {
// Test positive altitude conversion
int64_t millimeters = 1500000LL; // 1500 meters
double result = NGnssUtils::MillimetersToMetersDouble(millimeters);
zassert_within(result, 1500.0, 0.0001, "Positive meters double conversion failed");

// Test negative altitude conversion
int64_t negative_mm = -500000LL; // -500 meters
result = NGnssUtils::MillimetersToMetersDouble(negative_mm);
zassert_within(result, -500.0, 0.0001, "Negative meters double conversion failed");

// Test zero
millimeters = 0;
result = NGnssUtils::MillimetersToMetersDouble(millimeters);
zassert_within(result, 0.0, 0.0001, "Zero meters double conversion failed");
}

ZTEST_SUITE(gnss_utils, NULL, NULL, NULL, NULL, NULL);
1 change: 1 addition & 0 deletions app/tests/f_core/gnss_utils/prj.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
CONFIG_ZTEST=y
8 changes: 8 additions & 0 deletions app/tests/f_core/gnss_utils/testcase.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
common:
tags:
- f_core
- utilities
- gnss
tests:
f_core.gnss_utils:
type: unit
27 changes: 27 additions & 0 deletions app/tests/f_core/golay/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# SPDX-License-Identifier: Apache-2.0

cmake_minimum_required(VERSION 3.20.0)

find_package(Zephyr COMPONENTS unittest REQUIRED HINTS $ENV{ZEPHYR_BASE})

project(golay_test)

# Include f_core headers
# Try to find FSW root from Zephyr base or use current project structure
if(EXISTS "${ZEPHYR_BASE}/../FSW.git")
set(FSW_ROOT "${ZEPHYR_BASE}/../FSW.git")
elseif(EXISTS "${ZEPHYR_BASE}/../FSW")
set(FSW_ROOT "${ZEPHYR_BASE}/../FSW")
else()
# Fallback to relative path from test directory
set(FSW_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/../../../..")
endif()

include_directories(${FSW_ROOT}/include)

# Add the golay.c source file to the test
target_sources(testbinary
PRIVATE
main.c
${FSW_ROOT}/lib/f_core/radio/protocols/horus/golay.c
)
3 changes: 3 additions & 0 deletions app/tests/f_core/golay/Kconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
mainmenu "Test Configuration"

source "Kconfig.zephyr"
Loading
Loading