Building Native Tests
So, you want to build your tests natively. As you may already know, this method has a number of advantages:
You have full test control over registers and peripherals
(Usually) faster builds
(Usually) (MUCH) faster test execution
Promotes portable C
It also has a few downsides, as compared to running on a Simulated target:
(Often) requires two toolchains (one for release, one for test)
(Occasionally) requires small modifications to source if release compiler has non-standard features
(Usually) Upfront time investment for creating a flexible register set.
Tool Setup
The configuration of the toolchain should be fairly straightforward, assuming your tools have a command line option for the compiler and linker. If they don't, you might want to consider switching to something that does (like installing some version of gcc or clang).
Then, you can refer to the instructions for the build method you like the most. If you don't see your favorite option here, choose the one that is closest and feel free to ask questions on the forums if you get stuck.
Incidentally, we offer a freely available Docker image that packages up all our tools plus gcc in a ready-to-run dev environment. This environment is the second toolchain referenced above. See our tools list for more.
Non-Standard Compilers
Embedded Compiler vendors sometimes like to add nice features to make our lives easier. Sometimes, they are careful to make these features follow the C standards. Sometimes they come up with painful offshoots that make our code decidedly non-portable.
When this happens, most often there is a method of implementation that allows you to avoid using the new shorthand options, often with the downside of being somewhat more verbose. Let's look at a couple of common examples.
Some compilers add an attribute for marking interrupt functions, so that the compiler knows to handle their stack management differently. This might look something like this:
__inline void AwesomeUartInterrupt(void)
{
//Does stuff
}
This compiles fine on your embedded compiler, but when you go to run it on a non-embedded compiler, it will probably reach that __inline keyword and complain about not understanding what it means. During a test, though, we don't actually care if it's an interrupt or not. We're going to test it the same way... so we can just tell our tools to #define __inline as nothing and it's as if it never happened.
Another common shorthand is to add an at symbol '@' notation to specify where a function or variable should be placed in memory. It looks something like this:
volatile uint32_t GPIO_REGISTER @ 0x1000;
Obviously, this isn't going to compile on all compilers. So it's much better to do the more classic method of defining registers:
#define GPIO_REGISTER *(volatile uint32_t*)(0x1000)
Speaking of Registers
Yeah. Speaking of Registers... how do we do that? 0x1000 might be our GPIO on our target, but on our computer it might be in the middle of that cat gif we meant to email to our sister. We can't just decide to write to it.
We want our registers to be just like any other spot of RAM when we're writing our tests. We want to write different conditions to them. We want to read to verify their contents. So if we want them to be memory, we should just put them in regular memory.
This is actually pretty straightforward. Tedious. But straightforward. Most often our microcontrollers have a single header file that we define all registers in. What needs to happen next is that we spend a few hours editing this file, and getting it ready to test everything we may ever want to throw at it. We do this by creating a #define name TEST that we always define when we test, and never when we don't. We can then use it to alter our definitions a bit.
Let's take our GPIO_REGISTER example above and update it.
#ifndef TEST
#define GPIO_REGISTER *(volatile uint32_t*)(0x1000)
#else
EXTERN volatile uint32_t GPIO_REGISTER;
#endif
Now, when it's a release build, it uses the real register definition, placing it at 0x1000 as expected. When it's a TEST build, we tell it we've created a variable named GPIO_REGISTER somewhere. In this instance we're using an unsigned int, but it could just as easily be a struct or any of the other common ways of defining registers.
In any case, EXTERN will be defined to be extern if undefined at the top of the file. For one file in each unit test build, we define EXTERN to be nothing, so that a real instance is created.
The most convenient place to put the EXTERN definition is in the Test file itself. We KNOW that only one test file is included in each Unit test, so we can guarantee we won't get a collision. But who wants to remember to do that every time? Luckily, we can do a header file trick.
Fun with Headers
Let's say our micro's header file is called micro.h. It could have just as easily been called "at91sam9263.h" or "lpc1768.h", but it doesn't really matter. In any case, all our code is going to include micro.h. All of it, EXCEPT our test files. Instead, our test files are going to include "testable_micro.h"
That header looks like this:
#ifndef TESTABLE_MICRO_REGISTERS_H
#define TESTABLE_MICRO_REGISTERS_H
#define EXTERN
#include "micro_registers.h"
#endif
And presto! We have now automatically created a single instance of all of our register variables. Everywhere else gets an extern for it. And we have magically moved our registers into random spots of RAM for our native builds.
Further Articles on Testing Registers
This has covered most of the common scenarios for unit testing registers. There are a few hidden gotchas that might be lurking somewhere. Feel free to check out some articles on this on my personal blog:
Happy Testing!