Files
Lukes-Rocket-Launcher/code/RocketLauncher/test/test_rocket_controller.cpp
T
james.maes fa5a2350e3 fix: embed Unity test framework in test file to avoid git dependency
- Remove Unity library dependency from CMakeLists.txt
- Embed minimal Unity implementation directly in test file
- Fixes 'Cannot find source file: lib/Unity/unity.cpp' error in CI
- Maintains compatibility with PlatformIO Unity for native tests
- All tests pass locally with embedded Unity framework
2025-08-15 16:49:49 -05:00

398 lines
11 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include <cstring>
#include <cstdio>
#include <iostream>
#include "../src/RocketController.h"
#include "../src/ArduinoInterface.h"
// Minimal Unity test framework implementation for CMake builds
// This avoids dependency on external Unity files
namespace Unity {
extern int unity_current_test_failed;
extern int unity_number_of_tests;
extern int unity_number_of_failures;
void unity_begin(const char* filename);
void unity_end(void);
void unity_run_test(void (*test_func)(void), const char* test_name);
}
// Forward declarations for test setup/teardown
void setUp(void);
void tearDown(void);
// Unity test framework - minimal implementation for CMake integration
int Unity::unity_current_test_failed = 0;
int Unity::unity_number_of_tests = 0;
int Unity::unity_number_of_failures = 0;
void Unity::unity_begin(const char* filename) {
std::cout << "Unity Test Framework" << std::endl;
std::cout << "===================" << std::endl;
std::cout << "Running tests from: " << filename << std::endl << std::endl;
unity_current_test_failed = 0;
unity_number_of_tests = 0;
unity_number_of_failures = 0;
}
void Unity::unity_end(void) {
std::cout << std::endl << "===================" << std::endl;
std::cout << "Test Results:" << std::endl;
std::cout << "Tests Run: " << unity_number_of_tests << std::endl;
std::cout << "Failures: " << unity_number_of_failures << std::endl;
if (unity_number_of_failures == 0) {
std::cout << "All tests passed!" << std::endl;
} else {
std::cout << "Some tests failed!" << std::endl;
}
}
void Unity::unity_run_test(void (*test_func)(void), const char* test_name) {
std::cout << "Running test: " << test_name << std::endl;
unity_current_test_failed = 0;
// Call setUp and tearDown if they exist
setUp();
test_func();
tearDown();
if (unity_current_test_failed) {
std::cout << " ❌ FAILED" << std::endl;
} else {
std::cout << " ✅ PASSED" << std::endl;
}
}
// Unity test macros
#define TEST_ASSERT(condition) \
do { \
if (!(condition)) { \
Unity::unity_current_test_failed = 1; \
Unity::unity_number_of_failures++; \
} \
Unity::unity_number_of_tests++; \
} while(0)
#define TEST_ASSERT_EQUAL(expected, actual) \
TEST_ASSERT((expected) == (actual))
#define TEST_ASSERT_TRUE(condition) \
TEST_ASSERT(condition)
#define TEST_ASSERT_FALSE(condition) \
TEST_ASSERT(!(condition))
#define TEST_ASSERT_NULL(pointer) \
TEST_ASSERT((pointer) == nullptr)
#define TEST_ASSERT_NOT_NULL(pointer) \
TEST_ASSERT((pointer) != nullptr)
#define TEST_ASSERT_LESS_OR_EQUAL(expected, actual) \
TEST_ASSERT((actual) <= (expected))
// Unity test runner macros
#define UNITY_BEGIN() Unity::unity_begin(__FILE__)
#define UNITY_END() Unity::unity_end()
#define RUN_TEST(test_func) Unity::unity_run_test(test_func, #test_func)
// Simple mock Arduino interface for testing
class MockArduinoInterface : public ArduinoInterface
{
private:
// Mock state
mutable uint32_t mock_millis = 0;
uint8_t mock_pin_states[20] = {0};
bool mock_tone_active = false;
uint16_t mock_tone_freq = 0;
char mock_lcd_line1[32] = "";
char mock_lcd_line2[32] = "";
// Mock button states
bool mock_arm_pressed = false;
bool mock_reset_pressed = false;
bool mock_launch_pressed = false;
public:
// Pin control
void digitalWrite(uint8_t pin, uint8_t state) override
{
mock_pin_states[pin] = state;
}
uint8_t digitalRead(uint8_t pin) const override
{
return mock_pin_states[pin];
}
void pinMode(uint8_t pin, uint8_t mode) override
{
(void)pin; // Suppress unused parameter warning
(void)mode; // Suppress unused parameter warning
// Mock implementation
}
// Time functions
uint32_t millis() const override
{
return mock_millis;
}
void delay(uint32_t ms) override
{
mock_millis += ms;
}
// Audio functions
void tone(uint8_t pin, uint16_t freq) override
{
(void)pin; // Suppress unused parameter warning
mock_tone_active = true;
mock_tone_freq = freq;
}
void tone(uint8_t pin, uint16_t freq, uint32_t duration) override
{
(void)pin; // Suppress unused parameter warning
(void)duration; // Suppress unused parameter warning
mock_tone_active = true;
mock_tone_freq = freq;
}
void noTone(uint8_t pin) override
{
(void)pin; // Suppress unused parameter warning
mock_tone_active = false;
mock_tone_freq = 0;
}
// LCD functions
void lcdClear() override
{
mock_lcd_line1[0] = '\0';
mock_lcd_line2[0] = '\0';
}
void lcdSetCursor(uint8_t col, uint8_t row) override
{
(void)col; // Suppress unused parameter warning
(void)row; // Suppress unused parameter warning
// Mock implementation
}
void lcdPrint(const char* text) override
{
if (strlen(mock_lcd_line1) == 0)
{
strncpy(mock_lcd_line1, text, 31);
}
else
{
strncpy(mock_lcd_line2, text, 31);
}
}
void lcdPrint(int number) override
{
if (strlen(mock_lcd_line1) == 0)
{
snprintf(mock_lcd_line1, 32, "%d", number);
}
else
{
snprintf(mock_lcd_line2, 32, "%d", number);
}
}
// Button debouncing
void updateDebouncers() override
{
// Mock implementation
}
bool isArmPressed() const override
{
return mock_arm_pressed;
}
bool isResetPressed() const override
{
return mock_reset_pressed;
}
bool isLaunchPressed() const override
{
return mock_launch_pressed;
}
// Test helper methods
void setMockTime(uint32_t time) { mock_millis = time; }
void advanceTime(uint32_t ms) { mock_millis += ms; }
void setArmPressed(bool pressed) { mock_arm_pressed = pressed; }
void setResetPressed(bool pressed) { mock_reset_pressed = pressed; }
void setLaunchPressed(bool pressed) { mock_launch_pressed = pressed; }
// State query methods
uint8_t getPinState(uint8_t pin) const { return mock_pin_states[pin]; }
bool isToneActive() const { return mock_tone_active; }
uint16_t getToneFreq() const { return mock_tone_freq; }
const char* getLCDLine1() const { return mock_lcd_line1; }
const char* getLCDLine2() const { return mock_lcd_line2; }
// Reset mock state
void reset()
{
mock_millis = 0;
mock_tone_active = false;
mock_tone_freq = 0;
mock_lcd_line1[0] = '\0';
mock_lcd_line2[0] = '\0';
mock_arm_pressed = false;
mock_reset_pressed = false;
mock_launch_pressed = false;
for (int i = 0; i < 20; i++)
{
mock_pin_states[i] = 0;
}
}
};
// Test fixture
MockArduinoInterface* mockInterface;
RocketController* controller;
void setUp(void)
{
mockInterface = new MockArduinoInterface();
controller = new RocketController(mockInterface);
mockInterface->reset();
}
void tearDown(void)
{
delete controller;
delete mockInterface;
}
// Test 1: Basic Rocket Controller Creation and Initial State
void test_rocket_controller_initial_state(void)
{
// Test that controller starts in STARTUP state
TEST_ASSERT_EQUAL(State::STARTUP, controller->getState());
// Test that system is initially locked
TEST_ASSERT_TRUE(controller->isSystemLocked());
// Test that controller is not armed initially
TEST_ASSERT_FALSE(controller->isArmed());
// Test that controller is not launching initially
TEST_ASSERT_FALSE(controller->isLaunching());
}
// Test 2: State Transition from STARTUP to READY
void test_startup_to_ready_transition(void)
{
// Verify we start in STARTUP
TEST_ASSERT_EQUAL(State::STARTUP, controller->getState());
// The startup process takes:
// - 20 startup checks × 250ms = 5000ms (5 seconds)
// - Additional wait time = 2000ms (2 seconds)
// - Total: 7000ms (7 seconds)
uint32_t currentTime = 0;
const uint32_t stepSize = 100; // Check every 100ms
const uint32_t maxTime = 10000; // Max 10 seconds to be safe
// Advance time until startup completes
while (currentTime < maxTime && controller->getState() == State::STARTUP)
{
currentTime += stepSize;
mockInterface->setMockTime(currentTime);
controller->update(mockInterface->millis());
}
// Should now transition to READY state
TEST_ASSERT_EQUAL(State::READY, controller->getState());
// System should no longer be locked
TEST_ASSERT_FALSE(controller->isSystemLocked());
// Verify startup completed within reasonable time (should be around 7 seconds)
TEST_ASSERT_LESS_OR_EQUAL(10000, currentTime);
}
// Test 3: Button Input Handling
void test_button_input_handling(void)
{
// Test ARM button state
mockInterface->setArmPressed(true);
TEST_ASSERT_TRUE(mockInterface->isArmPressed());
mockInterface->setArmPressed(false);
TEST_ASSERT_FALSE(mockInterface->isArmPressed());
// Test RESET button state
mockInterface->setResetPressed(true);
TEST_ASSERT_TRUE(mockInterface->isResetPressed());
// Test LAUNCH button state
mockInterface->setLaunchPressed(true);
TEST_ASSERT_TRUE(mockInterface->isLaunchPressed());
}
// Test 4: Manual State Management
void test_manual_state_management(void)
{
// Test that we can manually set states
controller->enter(State::READY);
TEST_ASSERT_EQUAL(State::READY, controller->getState());
// Test that system is unlocked in READY state
TEST_ASSERT_FALSE(controller->isSystemLocked());
// Test ARMED state
controller->enter(State::ARMED);
TEST_ASSERT_EQUAL(State::ARMED, controller->getState());
TEST_ASSERT_TRUE(controller->isArmed());
TEST_ASSERT_FALSE(controller->isLaunching());
// Test LAUNCHING state
controller->enter(State::LAUNCHING);
TEST_ASSERT_EQUAL(State::LAUNCHING, controller->getState());
TEST_ASSERT_TRUE(controller->isLaunching());
TEST_ASSERT_FALSE(controller->isArmed());
// Test return to READY
controller->enter(State::READY);
TEST_ASSERT_EQUAL(State::READY, controller->getState());
TEST_ASSERT_FALSE(controller->isArmed());
TEST_ASSERT_FALSE(controller->isLaunching());
}
// Main test runner
void RUN_UNITY_TESTS()
{
UNITY_BEGIN();
// Rocket Controller Tests
RUN_TEST(test_rocket_controller_initial_state);
RUN_TEST(test_startup_to_ready_transition);
RUN_TEST(test_button_input_handling);
RUN_TEST(test_manual_state_management);
UNITY_END();
}
// Main function for native testing
int main()
{
// Initialize Unity test framework
RUN_UNITY_TESTS();
return 0;
}