File structure to satisfy both building and testing?

I am programming an ESP32 Feather board using the Arduino framework and Unity for testing. I’m struggling to figure out how to arrange my files to be able to do both building and testing (on either desktop or embedded hardware).

My file dependency tree looks something like this:

main.cpp
    foo.h and foo.cpp
        some_library.h and some_library.cpp

tests.cpp
    foo.h and foo.cpp
        some_library.h and some_library.cpp
  • main.cpp lives in src
  • tests.cpp lives in test
  • some_library.h lives in .pio/libdeps/...

I’ve tried putting the foo files in various places and nothing seems right:

  1. If I put both foo.h and foo.cpp in src, they are not found when I run tests.
  2. If I put both foo.h and foo.cpp in lib/foo, foo.h breaks because it can no longer find some_library.h. I suppose lib is meant for actual “libraries” that are self-contained?
  3. If I put both foo.h and foo.cpp in include, foo.cpp is not found when I build or run tests.
  4. If I put foo.h in include and foo.cpp in src, foo.cpp is not found when I run tests.
  5. If I put foo.h in include and foo.cpp in test, foo.cpp is not found when I build.

These two options work, but maintaining duplicate files is obviously silly:

  1. Put both foo.h and foo.cpp in both src and test.
  2. Put foo.h in include and put foo.cpp in both src and test.

What is the standard way to do this? I’d prefer to keep corresponding .h and .cpp files together rather than in separate folders.

This should be the right way. If PlatformIO has problems identifying library dependencies, try playing with the lib_ldf_mode. Otherwise, upload the project with which you have problems.

1 Like

Ok, I went back to putting my files in lib. Then I tried various ldf modes without success. I posted the project here: https://github.com/MarredCheese/BLE-keyboard-and-barcode-display

In the project’s current state, the error shown via VS Code intellisense is this:
#include errors detected. Please update your includePath. ...
(in lib/Screen/Screen.h, on line 6: #include <Adafruit_ThinkInk.h>)

And the error shown when I click Platform IO’s Test button is this:
lib\Screen/Screen.h:6:10: fatal error: Adafruit_ThinkInk.h: No such file or directory

I have the “desktop” env activated, by the way.

But the repo has no lib folder?

Sorry about that! Fixed. (lib was in my global .gitignore file, apparently.)

Project looks good.

Probably outdated intellisense or you didn’t try to build after changes which would have triggered it? Just Ctrl+Shift+P → Rebuild Intellisense in VSCode.

2 Likes

Hmm, I see. Would you do me a favor and switch to desktop env and try running the tests? This is what I’m seeing:

env build test
desktop not applicable compile fails
featheresp32 compile ok compile ok

I can almost garuantee you that tests will fail, you throw all Arduino libraries in lib_deps which cannot be executed out of the box since there is no Arduino framework for native computers installed.

Look at the dependency graph with pio test -e desktop -vvv

Dependency Graph
|-- BarcodeDisplay (License: Unknown, Path: C:\Users\Max\Downloads\BLE-keyboard-and-barcode-display-master\lib\BarcodeDisplay)
|   |-- Code128bEncoder (License: Unknown, Path: C:\Users\Max\Downloads\BLE-keyboard-and-barcode-display-master\lib\Code128bEncoder)
|   |-- BarcodeRenderer (License: Unknown, Path: C:\Users\Max\Downloads\BLE-keyboard-and-barcode-display-master\lib\BarcodeRenderer)
|   |-- Screen (License: Unknown, Path: C:\Users\Max\Downloads\BLE-keyboard-and-barcode-display-master\lib\Screen)
|-- Code128bEncoder (License: Unknown, Path: C:\Users\Max\Downloads\BLE-keyboard-and-barcode-display-master\lib\Code128bEncoder)
|-- BarcodeRenderer (License: Unknown, Path: C:\Users\Max\Downloads\BLE-keyboard-and-barcode-display-master\lib\BarcodeRenderer)
|-- Screen (License: Unknown, Path: C:\Users\Max\Downloads\BLE-keyboard-and-barcode-display-master\lib\Screen)
|-- Unity @ 2.5.2 (License: MIT, Path: C:\Users\Max\Downloads\BLE-keyboard-and-barcode-display-master\.pio\libdeps\desktop\Unity)

However, when properly properly specifying that these tests are to be excluded since your tests don’t require them using

[env:desktop]
platform = native
lib_ignore = 
   BarcodeDisplay
   Screen
lib_ldf_mode = chain+
test_ignore = embedded/*

I get…

> pio test -e desktop
Verbosity level can be increased via `-v, -vv, or -vvv` option
Collected 1 tests

Processing * in desktop environment
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------Building...
Testing...
test\test_Code128bEncoder.cpp:152: message_is_encoded   [PASSED]
----------------------------------------------------------------------------------------------- desktop:* [PASSED] Took 1.31 seconds -----------------------------------------------------------------------------------------------
============================================================================================================= SUMMARY ============================================================================================================= 
Environment    Test    Status    Duration
-------------  ------  --------  ------------
desktop        *       PASSED    00:00:01.306
============================================================================================ 1 test cases: 1 succeeded in 00:00:01.306 ============================================================================================ 

For testing libs that are dependent on Arduino, use GitHub - FabioBatSilva/ArduinoFake: Arduino mocking made easy. Also used in the official example (platformio-examples/platformio.ini at develop · platformio/platformio-examples · GitHub).

1 Like

Awesome. I really appreciate the help. I played around more to try to understand things better.

It seems that during testing, what gets compiled from lib/ is anything satisfying the logic below. Please correct me if I’m wrong.

( [is listed under lib_deps] OR
  [is in the dependency tree of your test code] OR
  [is in the dependency tree of src/main.cpp]  <-- why? seems wrong *see below
) AND NOT [is listed under lib_ignore]

* In my case, this rule adds pointless dependencies that make compilation fail. Therefore, I must manually exclude them via lib_ignore.

I’m actually not sure how the dependency graph is generated when pio test is used, it almost looks to me as if it’s just trying to detect dependencies from the src/ folder and all lib/ folders by default. I was pragmatic by looking at what ultra-verbose (-vvv) build output for the test build and lib_ignore-ing everything that wasn’t needed, which ended up making the build pass.

Ok. Yeah, I also initially thought it was including everything in lib/ as a dependency by default unless I listed a particular library under lib_ignore. But then I noticed it was not including lib/Printer. Going further, I see it does include Printer as a dependency if I add #include <Printer.h> to either my test code or src/main.cpp, or if I list it under lib_deps.

To wrap up, these are the lessons learned and discoveries made here, consolidated into one comment:

  • To support both building and testing, put your pairs of files in lib/, like lib/foo/foo.h and lib/foo/foo.cpp.
  • To successfully compile in a native/desktop environment, be sure not to list Arduino-dependent libraries under lib_deps, or, if applicable, use a test dummy via ArduinoFake. See this earlier comment for links.
  • During testing, irrelevant dependencies may be pulled in because they were referenced in src/main. Exclude them manually by listing them under lib_ignore. See this earlier comment for the full dependency logic. Better yet, just tell Platform IO to stop gathering test dependencies from src/main.cpp in general by wrapping that file’s code in #ifndef PIO_UNIT_TESTING / #endif. See this comment for details. Actually, the best way to is add build_src_filter = -<*> to your env in platformio.ini. See this comment for details.
  • You can check the dependency graph for a particular environment by running pio test -e my_env_name -vvv.

Update: I figured out how to get Platform IO to stop gathering irrelevant dependencies from src/main.cpp during testing! No need to manually ignore them via lib_ignore!

platformio.ini

[env:desktop]
platform = native
lib_ldf_mode = chain+

src/main.cpp

#ifndef PIO_UNIT_TESTING

...

#endif  // PIO_UNIT_TESTING

(The + in chain+ makes it respect directives like #ifndef when determining dependencies.)

Dependencies declared in lib_deps are isolated per working environment. The libraries located in the lib folder are common to all working environments. So, you need to decide how to organize your libraries/components. If you move with the private lib folder, the lib_ignore option will help you to exclude unnecessary libraries.

Another piece of advice is to use different working environments per “release” and per “test” builds. See example platformio-examples/platformio.ini at develop · platformio/platformio-examples · GitHub

Yeah. But I argue that having to manually list unreferenced lib libraries under lib_ignore to get your tests to compile is not something we should settle for. It’s a band-aid that’s only needed because of the bug or strange design decision to have tests draw dependencies from src/main (even though that file has nothing to do with running tests).

…Unless I’m missing something, but my experiments showed it working like this (copied from above):

During testing, what gets compiled from lib/ is anything satisfying the logic below.

( [is listed under lib_deps] OR
  [is in the dependency tree of your test code] OR
  [is in the dependency tree of src/main.cpp]  <-- why?
) AND NOT [is listed under lib_ignore]

Another update:

An even better way to stop PIO from pulling false dependencies from src/main during testing is to exclude all src files via build_src_filter. Then you can avoid polluting your code with #ifndef PIO_UNIT_TESTING directives.

Your platformio.ini would look something like this:

[env:desktop]
platform = native
build_src_filter = -<*>

I also tried build_type = test, but I don’t see it having any effect on the issues at hand here. I honestly have no clue what it does - possibly nothing. The documentation states that it enables using the environmental variable PIO_UNIT_TESTING, but I’ve already confirmed that statement as false. That variable works fine without build_type = test.

This is a bug and is fixed in

Could you re-run the pio upgrade --dev command and try testing the project without any “ignore” options?

Does it work as expected now?

1 Like