Undefined Refference - Forward Decleration of class in .h implementation in .cpp

I am getting an undefined refference on the deconstructor and exists method of my SPIFFSFileManager object.

MY_USER_PATH/test/test_FileManager/SpiffsManager_tests.cpp:112: undefined reference to SPIFFSFileManager::SPIFFSFileManager()

MY_USER_PATH/test_FileManager/SpiffsManager_tests.cpp:13: undefined reference to `SPIFFSFileManager::~SPIFFSFileManager()'

My header file containing the forward decleration:

//
// Created by DripTooHard on 14-04-2023.
//

#ifndef FAGPROJEKTLORA2023_FILEMANAGER_H
#define FAGPROJEKTLORA2023_FILEMANAGER_H

#include "SPIFFS.h"


class FileManager;

class SPIFFSFileManager{

private:

    fs::SPIFFSFS * fileSystem;

public:
    SPIFFSFileManager();
    ~SPIFFSFileManager();
    /**
     * Saves a file to the flash using SPIFFs
     *
     * \return true if a success, false if a failure, prints a custom error message
     */
    bool save_file(const char * filePath, const unsigned char * dataToWrite);

    bool exists(const char * filePath);

    bool delete_file(const char * filePath);

    bool load_file(const char * filePath, unsigned char * resultArray);
};

#endif //FAGPROJEKTLORA2023_FILEMANAGER_H

My implementation:

//
// Created by DripTooHard on 14-04-2023.
//

#include "FileManager.h"

class FileManager{
public:
    FileManager(){}
    virtual ~FileManager(){}
    virtual bool save_file(const char * filePath, const unsigned char * dataToWrite) = 0;
    virtual bool load_file(const char * filePath, unsigned char * resultArray) = 0;
    virtual bool delete_file(const char * filePath) = 0;
    virtual bool exists(const char * filePath) = 0;
};

/**
 * Whenever we deconstruct the SPIFFSFileManager, we unmount the SPIFFs module.
 */

SPIFFSFileManager::SPIFFSFileManager()
{
    //We have to mount the spiffs
    SPIFFS.begin(true);
}


SPIFFSFileManager::~SPIFFSFileManager(){
}

bool SPIFFSFileManager::save_file(const char *filePath, const unsigned char *dataToWrite) {
    Serial.printf("Writing file: %s\r\n", filePath);

    File file = fileSystem->open((const char *)filePath, FILE_WRITE);

    //Failed to open
    if(!file){
        Serial.println("− failed to open file for writing");
        return false;
    }

    //Written succesfully
    if(file.print( (const char *) dataToWrite)){
        Serial.println("− file written");
        return true;

        //Failed to write
    }else {
        Serial.println("− frite failed");
        return false;
    }

}

bool SPIFFSFileManager::exists(const char *filePath) {
    return fileSystem->exists((const char *) filePath);
}

bool SPIFFSFileManager::delete_file(const char * filePath){
    Serial.printf("Deleting file: %s\r\n", filePath);
    if(fileSystem->remove((const char *)filePath)){
        Serial.println("− file deleted");
        return true;
    } else {
        Serial.println("− delete failed");
        return false;
    }
}


bool SPIFFSFileManager::load_file(const char * filePath, unsigned char * resultArray){
    File f1 = SPIFFS.open(filePath);
    if(!f1 || f1.isDirectory()){
        Serial.println("− failed to open file for reading");
        return false;
    }

    int i = 0;
    unsigned char res;

    while(f1.available()){
        res = f1.read();
        resultArray[i++] = res;
    }

    return true;

}

And the SpiffsManager_test.cpp

//
// Created by DripTooHard on 20-05-2023.
//

#include <Arduino.h>
#include <unity.h>
#include "Utility.cpp"
#include "Flash/FileManager.h"
#include "esp_system.h"

const char * TEST_STR_PATH;
char * TEST_STR;
SPIFFSFileManager spiffy;
int testInt;

bool didWeRestart(){
    return  esp_reset_reason() == ESP_RST_SW;
}

void setUp(void) {
    // set stuff up here
    TEST_STR_PATH = "abc";
    TEST_STR = "ABC";
}


void tearDown(void) {
    // delete stuff down here
    testInt = 3;
}


/**
* Scenarie:
 * ABCDEF eksisterer ikke, vi gemmer det og genstarter ESP'en
 * ABCDEF eksisterer nu, vi kan loade det
 * Vi sletter ABCDEF, det eksisterer ikke
*
 */

void dataIsSavedAfterReboot(){
    //Before the reset we save it, and assert that we have indeed saved it
    if(!didWeRestart()){
        TEST_ASSERT_FALSE(spiffy.exists(TEST_STR_PATH));
        spiffy.save_file(TEST_STR_PATH,(const unsigned char *) TEST_STR);
        TEST_ASSERT_TRUE(spiffy.exists(TEST_STR_PATH));
    }
    //After the reset we check to see if it still exists, if we can load it, we then delete it
    else{
        TEST_ASSERT_TRUE(spiffy.exists(TEST_STR_PATH));
        unsigned char * load_result_arr;
        spiffy.load_file(TEST_STR_PATH,load_result_arr);

        //Time to delete it
        TEST_ASSERT_EQUAL_STRING(TEST_STR,load_result_arr);
        spiffy.delete_file(TEST_STR_PATH);
        TEST_ASSERT_FALSE(spiffy.exists(TEST_STR_PATH));
    }
}

/**
* Scenarie: Vi gemmer noget, det eksisterer, vi sletter det, det eksisterer ikke
*
 */


/**
* Scenarie: Vi gemmer ABCDEF, vi loader det i et array og får ABCDEF
*
 */

/**
* Scenarie: Vi gemmer ABCDEF, genstarter ESP32'eren, og vi kan loade det og det eksisterer,
*
 */

/**
* Scenarie: Vi laver to SPIFFS objekter på samme tid
 * Der eksisterer Spiffs objekt 1, der eksisterer spiffs objekt 2, vi bruger spiffs objekt 1 til at gemme ABCDF, vi bruger spiffs objekt 2 til at loade ABCDF
 * ABCDF er nu i arrayet, som vi har brugt til at loade
 * Vi genstarter ESP32'eren
 * Vi laver et Spiffs objekt 3, vi loader ABCDF, ABCDF er nu i arrayet
*/

/**
* Scenarie: Vi laver, sletter, laver
 * Givet laver spiffs2
 * Og sletter spiffs2
 * Og laver spiffs 1
 * Så bør spiffs1 være mounted
*/

/**
* Scenarie: Vi laver, vi laver, vi sletter
 * Givet laver spiffs2
 * Og laver spiffs 1
 * Og sletter spiffs 2
 * Og vi prøver at gemme "ABCDF" i path "EKG"
 * Så bør vi kunne loade "EKG" vhja. Spiffs1
*/

void setup()
{
    delay(5000); // service delay

    //DON'T PUT ANYTHING BEFORE THIS EXCEPT FOR DELAY!!!!
    UNITY_BEGIN(); //Define stuff after this
    RUN_TEST(dataIsSavedAfterReboot);
    if(!didWeRestart()){
        esp_restart();
    }
    UNITY_END(); // stop unit testing
}

void loop()
{
}

This doesn’t match

Does it?

Not quite sure what you mean. They’re different constructors for different classes?

The class works in my main.cpp file, but not in my tests.

Hm, I can’t actually reproduce your error, a normal firmware and tests builds fine.

> pio test -vvv --without-uploading --without-testing
[...]
RAM:   [=         ]   6.9% (used 22472 bytes from 327680 bytes)
Flash: [==        ]  21.5% (used 281589 bytes from 1310720 bytes)
----------esp32dev:test_embedded [PASSED] Took 5.33 seconds ----------

could you upload a minimal project that reproduces the error?

Hmmm, appears it is an issue with the compiler then. Where would I find my .o files if I haven’t touched the CMake files in a standard PlatformIO-CLion project?

Before going to the compiler, are your source files organized the same way as mine? Particularly, the filemanager’s source and header files are a library in lib/, not in src/?

No, but when I move the folder containing the filemanager into my lib folder from my src folder, I get an error, that my IDE cannot find the files.

You have to update the intellisense with Ctrl+Shift+P → Rebuild Intellisense, that’s not a real build error.

I may have misspoke there:

The compiler cannot find the file anymore. I am running on CLion so Intellisense is not a thing.

I have tried reloading the project, but the linker/compiler can still not find the files, this time however it is the actual .cpp and .h files, not the classes and it is pre-compile that it tells me it cannot find them, when I try to include them.

image
Here is my current lib structure.

I tried putting everything in src directly into the “Flash” folder, with the same result.

The way I have tried to include dem:

#include <Arduino.h>
#include <unity.h>
#include "Utility.cpp"
#include <FileManager.h>
#include "Flash/FileManager.h"
#include "Flash/FileManager.cpp"
#include "esp_system.h"

Do I have to somehow change the Platformio.ini file as well to indicate, that I am now using those libraries?

With the above file structure, PlatformIO should detect a dependency on “Flash” and thus auto-include its src/include folder. So you should only need to do #include <FileManager.h>.

Getting this error when I just do
#include <FileManager.h>

'FileManager.h' file not found

Not reproducable.

Can I see your platformio.ini?

What compiler are you using?

Also, I am using CLion, not VSCode.

Standard

[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino

We are using the same compiler since the PlatformIO core manages the used compiler (toolchain-xtensa-esp32, xtensa-esp32-elf-g++), it’s completely independent of the used IDE.

CLion should equivalently have some Tools->PlatformIO → Rebuild Intellisense menu option.

Are you sure it’s a compiler error?

CLion should equivalently have some Tools->PlatformIO → Rebuild Intellisense menu option.

Yep, apparently I didn’t re-init PlatformIO, only the CMake files. I did Tools->PlatformIO → Re-init and it now works.

We’re now back to the original “undefined reference” errors.

Can you upload the full build log that’s failing? https://pastebin.com/ if needed.

Don’t know what I did to be honest, but it is working now.

I suspect the original issue was, that the PlatformIO compiler didn’t link the libraries in src to the tests, such that the FileManager didn’t get compiled. It is fixed now after adding the stuff to the library and re-initting PlatformIO.

I am getting a null-pointer error, but I do not suspect this has anything to do with the original error.

Source code placed in the src/ folder of the project is by-default not compiled for tests. It expects you to organize the to-be-tested components as libraries.

This is however controllable by test_build_src. If you do that, you must however also add macro guards to prevent your regular firmware code from defining setup() and loop().