Help creating unit tests for the Raspberry Pi Pico

I’m trying to implement unit testing for my Raspberry Pi Pico code and keep running into trouble.
I’m using the earlephilhower Arduino-Pico core and the Unity test framework
My platformio.ini file looks like this

[env:pico]
platform = https://github.com/maxgerhardt/platform-raspberrypi.git
board = pico
test_framework = unity
framework = arduino
board_build.core = earlephilhower
board_build.filesystem_size = 0.5m
monitor_speed = 115200

My main main_pico_tests.hpp file looks like this

#include <Arduino.h>
#include <stdio.h>
#include <stdlib.h>
#include <LittleFS.h>
#include "pico/stdlib.h"
#include "pico/multicore.h"
#include "hardware/flash.h"
#include "hardware/adc.h"
#include "hardware/irq.h"

void pauseOtherCore();
//Resume Other Core
void resumeTheOtherCore();
//Restart Core1
void restartCore();
//Send data to other core via FIFO
bool sendToOtherCore(uint32_t data);

and my main.cpp code looks like this:

#include "main_pico_tests.hpp"

volatile bool settingsFileLock = false; // shared lock variable for settings file

//Multicore functions
//Pause Other Core
void pauseOtherCore(){
  rp2040.idleOtherCore();
}
//Resume Other Core
void resumeTheOtherCore(){
  rp2040.resumeOtherCore();
}
//Restart Core1
void restartCore(){
  rp2040.restartCore1();
}
//Send data to other core via FIFO
bool sendToOtherCore(uint32_t data){
  return rp2040.fifo.push_nb(data);
}

//Send data via Serial USB
void SerialSend(uint32_t data){
  Serial.println(data);
}

void waitUntillFileIsUnlocked(bool fileLock){
  while(fileLock){
    //Wait untill file is unlocked
  }
}


//LittleFS functions
//Open file to read or write
File openFile(String fileName, bool write){
  
  if(fileName == ""){
    Serial.println("No filename given");
  }

  else if(!LittleFS.exists(fileName)){
    Serial.println("File does not exist");
  }
  
  else if(LittleFS.exists(fileName)){
    while(settingsFileLock){
      //Wait untill file is unlocked
    }
    settingsFileLock = true;
    //Open file
  LittleFS.begin();
  File file = LittleFS.open(fileName, write ? "w" : "r");
  //Check if file opened
  if (!file) {
    Serial.println("Failed to open file for reading");
  }
  else if(file){
    Serial.println("File opened");
  return file;
  }
  }
  else{
    Serial.println("Something went wrong");
}
  return File();
}

//Close file
void closeFile(File file, String fileName){
  //Close file
  file.close();
  //Unlock file
  settingsFileLock = false;
  LittleFS.end();
}

//Read file
String readFile(String fileName){
  File file = openFile(fileName, false);
  String fileContent = "";
  if(file){
    while(file.available()){
      fileContent += (char)file.read();
    }
  }
  closeFile(file, fileName);
  return fileContent;
}

//Overwrite file with LittleFS
void writeFile(String fileName, String data){
  File file = openFile(fileName, true);
  if(file){
    file.print(data);
  }
  closeFile(file, fileName);
}

//Create file with LittleFS
void createFile(String fileName){
  File file = openFile(fileName, true);
  closeFile(file, fileName);
}


// First core code
void setup() {
  // put your setup code here, to run once:
   Serial.begin(115200);
  delay(5000);
   //Should start by transmitting the firmware version to the raspberry pi

}

void loop() {
  // put your main code here, to run repeatedly:

  //Check if there is a message from the other core
  if (rp2040.fifo.available() > 0)
  {
    uint32_t data = rp2040.fifo.pop();
}

  //Serial recieve
  if (Serial.available() > 0) {
    uint32_t data = Serial.read();
  }

}

// Second core code
void setup1() {
  //Second core setup
  // put your setup code here, to run once:
}

void loop1() {
  // Second core loop
  // put your main code here, to run repeatedly:

  //Check if there is a message from the other core
  if (rp2040.fifo.available() > 0)
  {
    uint32_t data = rp2040.fifo.pop();
  }
}

The code should be able to store, read and write files with LittleFS, while also being able to (in the future) communicate between the two cores.

Now, i have tried creating a unit test for communication, where the test_pico.cpp looks like this

#include "unity.h"
#include "main_pico_tests.hpp"

// Define setup and teardown functions if needed
void setUp(void)
{
    // Add any setup code here
}

void tearDown(void)
{
    // Add any teardown code here
}

// Define your test functions

void test_sendToOtherCore()
{
    // Test case 1: Sending a valid data value
    uint32_t data = 123;
    TEST_ASSERT_TRUE(sendToOtherCore(data));

    // Test case 2: Sending a zero value
    data = 0;
    TEST_ASSERT_TRUE(sendToOtherCore(data));

    // Test case 3: Sending a negative value
    data = -1;
    TEST_ASSERT_TRUE(sendToOtherCore(data));

    // Add more test cases as needed
}

int main(void)
{
    UNITY_BEGIN();
    RUN_TEST(test_sendToOtherCore);
    // Add more test functions here
    UNITY_END();

    return 0;
}

The code compiles fine, and uploads to the pico, but when i try to run the tests i get this error message

 *  Executing task in folder OctoBox: C:\Users\betal\.platformio\penv\Scripts\platformio.exe test --upload-port COM6 --test-port COM6 

Verbosity level can be increased via `-v, -vv, or -vvv` option
Collected 1 tests

Processing * in pico environment
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Building & Uploading...
c:/users/betal/.platformio/packages/toolchain-rp2040-earlephilhower/bin/../lib/gcc/arm-none-eabi/12.3.0/../../../../arm-none-eabi/bin/ld.exe: .pio\build\pico\test\test_pico.cpp.o: in function `_Z20test_sendToOtherCorev':
test_pico.cpp:(.text._Z20test_sendToOtherCorev+0x4): undefined reference to `_Z15sendToOtherCorem'
c:/users/betal/.platformio/packages/toolchain-rp2040-earlephilhower/bin/../lib/gcc/arm-none-eabi/12.3.0/../../../../arm-none-eabi/bin/ld.exe: test_pico.cpp:(.text._Z20test_sendToOtherCorev+0x16): undefined reference to `_Z15sendToOtherCorem'
c:/users/betal/.platformio/packages/toolchain-rp2040-earlephilhower/bin/../lib/gcc/arm-none-eabi/12.3.0/../../../../arm-none-eabi/bin/ld.exe: test_pico.cpp:(.text._Z20test_sendToOtherCorev+0x28): undefined reference to `_Z15sendToOtherCorem'
collect2.exe: error: ld returned 1 exit status
*** [.pio\build\pico\firmware.elf] Error 1
Uploading stage has failed, see errors above. Use `pio test -vvv` option to enable verbose output.
--------------------------------------------------------------------------------------------------------------------- pico:* [ERRORED] Took 3.14 seconds ---------------------------------------------------------------------------------------------------------------------

================================================================================================================================== SUMMARY ================================================================================================================================== 
Environment    Test    Status    Duration
-------------  ------  --------  ------------
pico           *       ERRORED   00:00:03.140
================================================================================================================= 1 test cases: 0 succeeded in 00:00:03.140 ================================================================================================================= 

 *  The terminal process "C:\Users\betal\.platformio\penv\Scripts\platformio.exe 'test', '--upload-port', 'COM6', '--test-port', 'COM6'" terminated with exit code: 1. 
 *  Terminal will be reused by tasks, press any key to close it. 

What am i doing wrong?
Thank you!

No don’t overwrite the main function. If you’re doing on-target testing with framework = arduino you still have to write an Arduino sketch. See e.g. https://github.com/platformio/platformio-examples/blob/develop/unit-testing/calculator/test/test_embedded/test_calculator.cpp#L47-L65. Since you are using the USB Serial to transport the test data, you will also want to add a small delay before PIO tries to connect to the Pico’s COM port in case it’s not here yet (like this).

We also had a thread on setting up unit tests once with complete working example. Let me find that…

Thank you for helping!

I tried both doing the changes you suggested, and now moving the functions away from main.cpp to its own library in lib/coreCommunication/src, where i created the two files so the setup was more like the calculator example, so now my coreCommunication.h looks like this

#include "pico/stdlib.h"
#include "pico/multicore.h"
#include <Arduino.h>

#ifndef CORECOMMUNICATION_H
#define CORECOMMUNICATION_H

class coreCommunication {
    public:
    coreCommunication() {}
    
    void pauseOtherCore();
    void resumeTheOtherCore();
    void restartCore();
    bool sendToOtherCore(uint32_t data);
};

#endif // CORECOMMUNICATION_H

and my coreCommunication.cpp looks like this

#include <coreCommunication.h>

//Multicore functions
//Pause Other Core
void pauseOtherCore(){
  rp2040.idleOtherCore();
}
//Resume Other Core
void resumeTheOtherCore(){
  rp2040.resumeOtherCore();
}
//Restart Core1
void restartCore(){
  rp2040.restartCore1();
}
//Send data to other core via FIFO
bool sendToOtherCore(uint32_t data){
  return rp2040.fifo.push_nb(data);
}

and my test_coreCommunication.cpp in the folder test/test_embedded looks like this

#include <Arduino.h>
#include <unity.h>
#include <coreCommunication.h>

coreCommunication core1;

// Define setup and teardown functions if needed
void setUp(void)
{
    // Add any setup code here
    
}

void tearDown(void)
{
    // Add any teardown code here
}

// Define your test functions

void test_sendToOtherCore()
{
    // Test case 1: Sending a valid data value
    uint32_t data = 123;
    TEST_ASSERT_TRUE(core1.sendToOtherCore(data));
}

void setup()
{
    Serial.begin(115200);
    delay(2000);
    UNITY_BEGIN();
    RUN_TEST(test_sendToOtherCore);
    UNITY_END();
}

void loop() {

}

i also added the extra_script.py, and added it to my platformio.ini

but i still get the same error

Verbosity level can be increased via `-v, -vv, or -vvv` option
Collected 1 tests

Processing test_embedded in pico environment
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Building & Uploading...
c:/users/betal/.platformio/packages/toolchain-rp2040-earlephilhower/bin/../lib/gcc/arm-none-eabi/12.3.0/../../../../arm-none-eabi/bin/ld.exe: .pio\build\pico\test\test_embedded\test_coreCommunication.cpp.o: in function `_Z20test_sendToOtherCorev':
test_coreCommunication.cpp:(.text._Z20test_sendToOtherCorev+0x6): undefined reference to `_ZN17coreCommunication15sendToOtherCoreEm'
collect2.exe: error: ld returned 1 exit status
*** [.pio\build\pico\firmware.elf] Error 1
Uploading stage has failed, see errors above. Use `pio test -vvv` option to enable verbose output.
--------------------------------------------------------------------------------------------------------------- pico:test_embedded [ERRORED] Took 3.49 seconds ---------------------------------------------------------------------------------------------------------------

================================================================================================================================== SUMMARY ================================================================================================================================== 
Environment    Test           Status    Duration
-------------  -------------  --------  ------------
pico           test_embedded  ERRORED   00:00:03.488
================================================================================================================= 1 test cases: 0 succeeded in 00:00:03.488 ================================================================================================================= 

Does the basic example at https://github.com/maxgerhardt/platform-raspberrypi/issues/28#issuecomment-1533664099 run fine?

Just tried the example. It initially gave an error, but after adding the script and BAUD to platformio.ini it worked fine

monitor_speed = 115200
extra_scripts = post:extra_script.py

Output after running the example script

Executing task in folder OctoBox: C:\Users\betal\.platformio\penv\Scripts\platformio.exe test --upload-port COM6 --test-port COM6 

Verbosity level can be increased via `-v, -vv, or -vvv` option
Collected 1 tests

Processing test_embedded in pico environment
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Building & Uploading...
Testing...
If you don't see any output for the first 10 secs, please reset board (press reset button)

test\test_embedded\test_example.cpp:24: test_calculator_addition        [PASSED]
--------------------------------------------------------------------------------------------------------------- pico:test_embedded [PASSED] Took 7.75 seconds --------------------------------------------------------------------------------------------------------------- 

================================================================================================================================== SUMMARY ================================================================================================================================== 
Environment    Test           Status    Duration
-------------  -------------  --------  ------------
pico           test_embedded  PASSED    00:00:07.747
================================================================================================================= 1 test cases: 1 succeeded in 00:00:07.747 ================================================================================================================= 

Oh actually this is wrong. Since you defined a class coreCommunication in your header, in your .cpp file then, you need to implement the functions as <class name>::<function name>(..), aka

#include <coreCommunication.h>

//Multicore functions
//Pause Other Core
void coreCommunication::pauseOtherCore(){
  rp2040.idleOtherCore();
}
//Resume Other Core
void coreCommunication::resumeTheOtherCore(){
  rp2040.resumeOtherCore();
}
//Restart Core1
void coreCommunication::restartCore(){
  rp2040.restartCore1();
}
//Send data to other core via FIFO
bool coreCommunication::sendToOtherCore(uint32_t data){
  return rp2040.fifo.push_nb(data);
}

Thank you so much! That worked. I think a combination of your suggestions worked, and then it just got mixed up in the end. Gotta read up on my c++ :wink:

1 Like