Ver Fonte

file based unit tests

Anatoli Arkhipenko há 4 meses atrás
pai
commit
9e62fd26f2
4 ficheiros alterados com 412 adições e 222 exclusões
  1. 12 222
      .github/workflows/test.yml
  2. 81 0
      tests/CMakeLists.txt
  3. 87 0
      tests/mock-arduino.h
  4. 232 0
      tests/test-scheduler.cpp

+ 12 - 222
.github/workflows/test.yml

@@ -12,242 +12,32 @@ jobs:
     
     steps:
       - name: Checkout
-        uses: actions/checkout@main
+        uses: actions/checkout@v4
         
       - name: Install dependencies
         run: |
           sudo apt-get update
-          sudo apt-get install -y cmake build-essential libgtest-dev
+          sudo apt-get install -y cmake build-essential libgtest-dev pkg-config
           
       - name: Build and install Google Test
         run: |
           cd /usr/src/gtest
           sudo cmake .
           sudo cmake --build . --target all
-          sudo cp lib/*.a /usr/lib
-          
-      - name: Create test directory structure
-        run: |
-          mkdir -p test
-          
-      - name: Create unit test file
-        run: |
-          cat > test/test_taskscheduler.cpp << 'EOF'
-          #include <gtest/gtest.h>
-          #include <iostream>
-          #include <vector>
-          #include <chrono>
-          #include <thread>
-
-          // Mock Arduino functions for Linux compilation
-          unsigned long millis() {
-              static auto start = std::chrono::steady_clock::now();
-              auto now = std::chrono::steady_clock::now();
-              return std::chrono::duration_cast<std::chrono::milliseconds>(now - start).count();
-          }
-
-          unsigned long micros() {
-              static auto start = std::chrono::steady_clock::now();
-              auto now = std::chrono::steady_clock::now();
-              return std::chrono::duration_cast<std::chrono::microseconds>(now - start).count();
-          }
-
-          void delay(unsigned long ms) {
-              std::this_thread::sleep_for(std::chrono::milliseconds(ms));
-          }
-
-          // Include TaskScheduler headers
-          #include "TaskScheduler.h"
-
-          // Global test output capture
-          std::vector<std::string> test_output;
-
-          // Test callback functions
-          void task1_callback() {
-              test_output.push_back("Task1 executed");
-              std::cout << "Task1 executed at " << millis() << "ms" << std::endl;
-          }
-
-          void task2_callback() {
-              test_output.push_back("Task2 executed");
-              std::cout << "Task2 executed at " << millis() << "ms" << std::endl;
-          }
-
-          void task3_callback() {
-              test_output.push_back("Task3 executed");
-              std::cout << "Task3 executed at " << millis() << "ms" << std::endl;
-          }
-
-          class TaskSchedulerTest : public ::testing::Test {
-          protected:
-              void SetUp() override {
-                  test_output.clear();
-              }
-              
-              void TearDown() override {
-                  test_output.clear();
-              }
-          };
-
-          TEST_F(TaskSchedulerTest, BasicSchedulerCreation) {
-              Scheduler ts;
-              EXPECT_TRUE(true); // Just test that scheduler can be created
-          }
-
-          TEST_F(TaskSchedulerTest, SingleTaskExecution) {
-              Scheduler ts;
-              
-              // Create a task that runs once after 100ms
-              Task task1(100, 1, &task1_callback, &ts, true);
-              
-              unsigned long start_time = millis();
-              unsigned long timeout = start_time + 1000; // 1 second timeout
-              
-              // Run scheduler until task executes or timeout
-              while (millis() < timeout && test_output.size() == 0) {
-                  ts.execute();
-                  delay(10); // Small delay to prevent busy waiting
-              }
-              
-              EXPECT_EQ(test_output.size(), 1);
-              EXPECT_EQ(test_output[0], "Task1 executed");
-          }
-
-          TEST_F(TaskSchedulerTest, MultipleTaskExecution) {
-              Scheduler ts;
-              
-              // Create multiple tasks with different intervals
-              Task task1(100, 1, &task1_callback, &ts, true);  // Run once after 100ms
-              Task task2(150, 1, &task2_callback, &ts, true);  // Run once after 150ms
-              Task task3(200, 1, &task3_callback, &ts, true);  // Run once after 200ms
-              
-              unsigned long start_time = millis();
-              unsigned long timeout = start_time + 1000; // 1 second timeout
-              
-              // Run scheduler until all tasks execute or timeout
-              while (millis() < timeout && test_output.size() < 3) {
-                  ts.execute();
-                  delay(10);
-              }
-              
-              EXPECT_EQ(test_output.size(), 3);
-              EXPECT_EQ(test_output[0], "Task1 executed");
-              EXPECT_EQ(test_output[1], "Task2 executed");
-              EXPECT_EQ(test_output[2], "Task3 executed");
-          }
-
-          TEST_F(TaskSchedulerTest, RepeatingTask) {
-              Scheduler ts;
-              
-              // Create a task that runs 3 times with 100ms interval
-              Task task1(100, 3, &task1_callback, &ts, true);
-              
-              unsigned long start_time = millis();
-              unsigned long timeout = start_time + 1000; // 1 second timeout
-              
-              // Run scheduler until task executes 3 times or timeout
-              while (millis() < timeout && test_output.size() < 3) {
-                  ts.execute();
-                  delay(10);
-              }
-              
-              EXPECT_EQ(test_output.size(), 3);
-              for (int i = 0; i < 3; i++) {
-                  EXPECT_EQ(test_output[i], "Task1 executed");
-              }
-          }
-
-          TEST_F(TaskSchedulerTest, TaskEnableDisable) {
-              Scheduler ts;
-              
-              // Create a disabled task
-              Task task1(100, 1, &task1_callback, &ts, false);
-              
-              unsigned long start_time = millis();
-              unsigned long timeout = start_time + 300;
-              
-              // Run scheduler - task should not execute (it's disabled)
-              while (millis() < timeout) {
-                  ts.execute();
-                  delay(10);
-              }
-              
-              EXPECT_EQ(test_output.size(), 0);
-              
-              // Now enable the task
-              task1.enable();
-              
-              start_time = millis();
-              timeout = start_time + 300;
-              
-              // Run scheduler - task should execute now
-              while (millis() < timeout && test_output.size() == 0) {
-                  ts.execute();
-                  delay(10);
-              }
-              
-              EXPECT_EQ(test_output.size(), 1);
-              EXPECT_EQ(test_output[0], "Task1 executed");
-          }
-
-          int main(int argc, char **argv) {
-              ::testing::InitGoogleTest(&argc, argv);
-              return RUN_ALL_TESTS();
-          }
-          EOF
-          
-      - name: Create CMakeLists.txt
-        run: |
-          cat > CMakeLists.txt << 'EOF'
-          cmake_minimum_required(VERSION 3.10)
-          project(TaskSchedulerTests)
-
-          set(CMAKE_CXX_STANDARD 11)
-          set(CMAKE_CXX_STANDARD_REQUIRED ON)
-
-          # Find packages
-          find_package(PkgConfig REQUIRED)
-          find_package(Threads REQUIRED)
-
-          # Include TaskScheduler source directory
-          include_directories(${CMAKE_SOURCE_DIR}/src)
-
-          # Add TaskScheduler source files
-          file(GLOB TASKSCHEDULER_SOURCES "src/*.cpp")
-
-          # Create the test executable
-          add_executable(
-              taskscheduler_tests
-              test/test_taskscheduler.cpp
-              ${TASKSCHEDULER_SOURCES}
-          )
-
-          # Link against gtest and pthread
-          target_link_libraries(
-              taskscheduler_tests
-              gtest
-              gtest_main
-              pthread
-          )
-
-          # Add compile definitions to make TaskScheduler work on Linux
-          target_compile_definitions(taskscheduler_tests PRIVATE
-              ARDUINO=200
-              _TASK_MICRO_RES=1
-          )
-
-          # Include directories
-          target_include_directories(taskscheduler_tests PRIVATE 
-              src
-              test
-          )
-          EOF
+          sudo cp lib/*.a /usr/lib || sudo cp *.a /usr/lib
           
       - name: Build tests
         run: |
           cmake .
-          make
+          make -j$(nproc)
           
       - name: Run unit tests
         run: |
-          ./taskscheduler_tests
+          ./taskscheduler_tests --gtest_output=xml:test_results.xml
+          
+      - name: Upload test results
+        uses: actions/upload-artifact@v3
+        if: always()
+        with:
+          name: test-results
+          path: test_results.xml

+ 81 - 0
tests/CMakeLists.txt

@@ -0,0 +1,81 @@
+cmake_minimum_required(VERSION 3.10)
+project(TaskSchedulerTests VERSION 1.0.0)
+
+set(CMAKE_CXX_STANDARD 11)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+
+# Find required packages
+find_package(PkgConfig REQUIRED)
+find_package(Threads REQUIRED)
+
+# Include directories
+include_directories(${CMAKE_SOURCE_DIR}/src)
+include_directories(${CMAKE_SOURCE_DIR}/tests)
+
+# Gather source files
+file(GLOB TASKSCHEDULER_SOURCES 
+    "${CMAKE_SOURCE_DIR}/src/*.cpp"
+    "${CMAKE_SOURCE_DIR}/src/*.c"
+)
+
+file(GLOB TEST_SOURCES 
+    "${CMAKE_SOURCE_DIR}/tests/*.cpp"
+)
+
+# Check if we have test files
+list(LENGTH TEST_SOURCES TEST_COUNT)
+if(TEST_COUNT EQUAL 0)
+    message(WARNING "No test files found in tests/ directory")
+endif()
+
+# Create the test executable only if we have test sources
+if(TEST_COUNT GREATER 0)
+    add_executable(taskscheduler_tests
+        ${TEST_SOURCES}
+        ${TASKSCHEDULER_SOURCES}
+    )
+
+    # Link libraries
+    target_link_libraries(taskscheduler_tests
+        gtest
+        gtest_main
+        pthread
+    )
+
+    # Compiler definitions for Arduino compatibility
+    target_compile_definitions(taskscheduler_tests PRIVATE
+        ARDUINO=200
+        _TASK_MICRO_RES=1
+        _TASK_STD_FUNCTION=0
+        _TASK_TIMECRITICAL=1
+        _TASK_STATUS_REQUEST=1
+        _TASK_WDT_IDS=1
+        _TASK_LTS_POINTER=1
+        _TASK_PRIORITY=1
+        _TASK_TIMEOUT=1
+        _TASK_OO_CALLBACKS=1
+        _TASK_DEFINE_MILLIS=0
+        _TASK_INLINE=0
+        _TASK_THREAD_SAFE=0
+        _TASK_SLEEP_ON_IDLE_RUN=0
+    )
+
+    # Compiler flags
+    target_compile_options(taskscheduler_tests PRIVATE
+        -Wall
+        -Wextra
+        -O2
+    )
+
+    # Enable testing
+    enable_testing()
+    add_test(NAME TaskSchedulerUnitTests COMMAND taskscheduler_tests)
+
+    # Print summary
+    message(STATUS "TaskScheduler Test Build Configuration:")
+    message(STATUS "  Test files found: ${TEST_COUNT}")
+    message(STATUS "  TaskScheduler sources: ${TASKSCHEDULER_SOURCES}")
+    message(STATUS "  Test sources: ${TEST_SOURCES}")
+else()
+    message(STATUS "No tests to build - tests/ directory is empty or missing .cpp files")
+endif()

+ 87 - 0
tests/mock-arduino.h

@@ -0,0 +1,87 @@
+// mock_arduino.h
+#ifndef MOCK_ARDUINO_H
+#define MOCK_ARDUINO_H
+
+#include <chrono>
+#include <thread>
+#include <vector>
+#include <string>
+#include <iostream>
+
+// Arduino constants
+#define HIGH 1
+#define LOW  0
+#define INPUT 0
+#define OUTPUT 1
+#define LED_BUILTIN 13
+
+// Mock Arduino timing functions
+inline unsigned long millis() {
+    static auto start = std::chrono::steady_clock::now();
+    auto now = std::chrono::steady_clock::now();
+    return std::chrono::duration_cast<std::chrono::milliseconds>(now - start).count();
+}
+
+inline unsigned long micros() {
+    static auto start = std::chrono::steady_clock::now();
+    auto now = std::chrono::steady_clock::now();
+    return std::chrono::duration_cast<std::chrono::microseconds>(now - start).count();
+}
+
+inline void delay(unsigned long ms) {
+    std::this_thread::sleep_for(std::chrono::milliseconds(ms));
+}
+
+inline void delayMicroseconds(unsigned long us) {
+    std::this_thread::sleep_for(std::chrono::microseconds(us));
+}
+
+// Mock digital I/O functions
+inline void pinMode(int pin, int mode) {
+    // Mock implementation - do nothing
+}
+
+inline void digitalWrite(int pin, int value) {
+    // Mock implementation - could log pin states if needed
+}
+
+inline int digitalRead(int pin) {
+    // Mock implementation - return LOW by default
+    return LOW;
+}
+
+// Global test output capture
+extern std::vector<std::string> test_output;
+
+// Helper function to clear test output
+inline void clearTestOutput() {
+    test_output.clear();
+}
+
+// Helper function to get test output count
+inline size_t getTestOutputCount() {
+    return test_output.size();
+}
+
+// Helper function to get specific test output
+inline std::string getTestOutput(size_t index) {
+    if (index < test_output.size()) {
+        return test_output[index];
+    }
+    return "";
+}
+
+// Helper function to wait for condition with timeout
+template<typename Condition>
+inline bool waitForCondition(Condition condition, unsigned long timeout_ms) {
+    unsigned long start_time = millis();
+    while (millis() - start_time < timeout_ms) {
+        if (condition()) {
+            return true;
+        }
+        delay(10);
+    }
+    return false;
+}
+
+#endif // MOCK_ARDUINO_H

+ 232 - 0
tests/test-scheduler.cpp

@@ -0,0 +1,232 @@
+// test_scheduler.cpp
+#include <gtest/gtest.h>
+#include "mock_arduino.h"
+#include "TaskScheduler.h"
+
+// Define the global test output vector
+std::vector<std::string> test_output;
+
+// Test callback functions
+void task1_callback() {
+    test_output.push_back("Task1 executed");
+    std::cout << "Task1 executed at " << millis() << "ms" << std::endl;
+}
+
+void task2_callback() {
+    test_output.push_back("Task2 executed");
+    std::cout << "Task2 executed at " << millis() << "ms" << std::endl;
+}
+
+void task3_callback() {
+    test_output.push_back("Task3 executed");
+    std::cout << "Task3 executed at " << millis() << "ms" << std::endl;
+}
+
+void repeating_callback() {
+    static int counter = 0;
+    counter++;
+    test_output.push_back("Repeating task #" + std::to_string(counter));
+    std::cout << "Repeating task #" << counter << " executed at " << millis() << "ms" << std::endl;
+}
+
+class SchedulerTest : public ::testing::Test {
+protected:
+    void SetUp() override {
+        clearTestOutput();
+        // Reset time by creating new static start point
+        millis(); // Initialize timing
+    }
+    
+    void TearDown() override {
+        clearTestOutput();
+    }
+    
+    // Helper to run scheduler until condition is met or timeout
+    bool runSchedulerUntil(Scheduler& ts, std::function<bool()> condition, unsigned long timeout_ms = 1000) {
+        return waitForCondition([&]() {
+            ts.execute();
+            return condition();
+        }, timeout_ms);
+    }
+};
+
+TEST_F(SchedulerTest, BasicSchedulerCreation) {
+    Scheduler ts;
+    EXPECT_TRUE(true); // Scheduler creation should not throw
+}
+
+TEST_F(SchedulerTest, SchedulerInitialState) {
+    Scheduler ts;
+    
+    // Execute empty scheduler - should not crash
+    ts.execute();
+    EXPECT_EQ(getTestOutputCount(), 0);
+}
+
+TEST_F(SchedulerTest, SingleTaskExecution) {
+    Scheduler ts;
+    
+    // Create a task that runs once after 100ms
+    Task task1(100, 1, &task1_callback, &ts, true);
+    
+    // Run scheduler until task executes
+    bool success = runSchedulerUntil(ts, []() { return getTestOutputCount() >= 1; });
+    
+    EXPECT_TRUE(success) << "Task did not execute within timeout";
+    EXPECT_EQ(getTestOutputCount(), 1);
+    EXPECT_EQ(getTestOutput(0), "Task1 executed");
+}
+
+TEST_F(SchedulerTest, MultipleTaskExecution) {
+    Scheduler ts;
+    
+    // Create multiple tasks with different intervals
+    Task task1(50, 1, &task1_callback, &ts, true);   // Run once after 50ms
+    Task task2(100, 1, &task2_callback, &ts, true);  // Run once after 100ms
+    Task task3(150, 1, &task3_callback, &ts, true);  // Run once after 150ms
+    
+    // Run scheduler until all tasks execute
+    bool success = runSchedulerUntil(ts, []() { return getTestOutputCount() >= 3; });
+    
+    EXPECT_TRUE(success) << "Not all tasks executed within timeout";
+    EXPECT_EQ(getTestOutputCount(), 3);
+    EXPECT_EQ(getTestOutput(0), "Task1 executed");
+    EXPECT_EQ(getTestOutput(1), "Task2 executed");
+    EXPECT_EQ(getTestOutput(2), "Task3 executed");
+}
+
+TEST_F(SchedulerTest, RepeatingTaskExecution) {
+    Scheduler ts;
+    
+    // Create a task that runs 3 times with 80ms interval
+    Task repeating_task(80, 3, &repeating_callback, &ts, true);
+    
+    // Run scheduler until task executes 3 times
+    bool success = runSchedulerUntil(ts, []() { return getTestOutputCount() >= 3; }, 1500);
+    
+    EXPECT_TRUE(success) << "Repeating task did not complete within timeout";
+    EXPECT_EQ(getTestOutputCount(), 3);
+    EXPECT_EQ(getTestOutput(0), "Repeating task #1");
+    EXPECT_EQ(getTestOutput(1), "Repeating task #2");
+    EXPECT_EQ(getTestOutput(2), "Repeating task #3");
+}
+
+TEST_F(SchedulerTest, InfiniteRepeatingTask) {
+    Scheduler ts;
+    
+    // Create a task that runs indefinitely with 50ms interval
+    Task infinite_task(50, TASK_FOREVER, &repeating_callback, &ts, true);
+    
+    // Run for a specific time and count executions
+    unsigned long start_time = millis();
+    while (millis() - start_time < 250) { // Run for 250ms
+        ts.execute();
+        delay(10);
+    }
+    
+    // Should have executed approximately 250/50 = 5 times (allowing for timing variance)
+    EXPECT_GE(getTestOutputCount(), 3);
+    EXPECT_LE(getTestOutputCount(), 7);
+}
+
+TEST_F(SchedulerTest, TaskEnableDisable) {
+    Scheduler ts;
+    
+    // Create a disabled task
+    Task task1(100, 1, &task1_callback, &ts, false);
+    
+    // Run scheduler - task should not execute (it's disabled)
+    delay(150);
+    ts.execute();
+    EXPECT_EQ(getTestOutputCount(), 0);
+    
+    // Now enable the task
+    task1.enable();
+    
+    // Run scheduler - task should execute now
+    bool success = runSchedulerUntil(ts, []() { return getTestOutputCount() >= 1; });
+    
+    EXPECT_TRUE(success) << "Task did not execute after being enabled";
+    EXPECT_EQ(getTestOutputCount(), 1);
+    EXPECT_EQ(getTestOutput(0), "Task1 executed");
+}
+
+TEST_F(SchedulerTest, TaskDisableDuringExecution) {
+    Scheduler ts;
+    
+    // Create a repeating task
+    Task repeating_task(60, TASK_FOREVER, &repeating_callback, &ts, true);
+    
+    // Let it run a few times
+    bool success = runSchedulerUntil(ts, []() { return getTestOutputCount() >= 2; });
+    EXPECT_TRUE(success);
+    
+    size_t executions_before_disable = getTestOutputCount();
+    
+    // Disable the task
+    repeating_task.disable();
+    
+    // Continue running scheduler
+    delay(200);
+    for (int i = 0; i < 10; i++) {
+        ts.execute();
+        delay(20);
+    }
+    
+    // Should not have executed any more times
+    EXPECT_EQ(getTestOutputCount(), executions_before_disable);
+}
+
+TEST_F(SchedulerTest, SchedulerWithNoTasks) {
+    Scheduler ts;
+    
+    // Execute scheduler with no tasks multiple times
+    for (int i = 0; i < 100; i++) {
+        ts.execute();
+        delay(1);
+    }
+    
+    EXPECT_EQ(getTestOutputCount(), 0);
+}
+
+TEST_F(SchedulerTest, TaskExecutionOrder) {
+    Scheduler ts;
+    
+    // Create tasks that should execute in specific order
+    Task task_late(200, 1, &task3_callback, &ts, true);  // Latest
+    Task task_early(50, 1, &task1_callback, &ts, true);  // Earliest  
+    Task task_mid(100, 1, &task2_callback, &ts, true);   // Middle
+    
+    // Run until all execute
+    bool success = runSchedulerUntil(ts, []() { return getTestOutputCount() >= 3; });
+    
+    EXPECT_TRUE(success);
+    EXPECT_EQ(getTestOutputCount(), 3);
+    
+    // Tasks should execute in chronological order regardless of creation order
+    EXPECT_EQ(getTestOutput(0), "Task1 executed");  // First (50ms)
+    EXPECT_EQ(getTestOutput(1), "Task2 executed");  // Second (100ms)
+    EXPECT_EQ(getTestOutput(2), "Task3 executed");  // Third (200ms)
+}
+
+TEST_F(SchedulerTest, SchedulerHandlesLargeNumberOfTasks) {
+    Scheduler ts;
+    std::vector<std::unique_ptr<Task>> tasks;
+    
+    // Create many tasks with similar timing
+    for (int i = 0; i < 10; i++) {
+        auto task = std::make_unique<Task>(100 + i * 10, 1, &task1_callback, &ts, true);
+        tasks.push_back(std::move(task));
+    }
+    
+    // Run until all tasks execute
+    bool success = runSchedulerUntil(ts, []() { return getTestOutputCount() >= 10; }, 2000);
+    
+    EXPECT_TRUE(success) << "Not all tasks executed within timeout";
+    EXPECT_EQ(getTestOutputCount(), 10);
+}
+
+int main(int argc, char **argv) {
+    ::testing::InitGoogleTest(&argc, argv);
+    return RUN_ALL_TESTS();
+}