Multi-platform application

Principle

Coco has strict checks to detect that the coverage data is coherent with the source code. It is, for example, not possible to import an execution report (.csexe file) of an earlier project version or to import the coverage information of an old version into a newer instrumentation database (.csmes file). This prevents some usage errors and the generation of incoherent test metrics.

Coherence analysis includes verifying that every instrumented source file in the project is not modified. This is done during the build and when coverage data are imported or merged. If the C preprocessor produces different files across the build – which can occur when some files are built in debug mode and others in release mode – Coco detects it, adds a suffix like #1 or #2 to the file name, and requires that both versions are tested to achieve full coverage.

It is possible to compile a program on several platforms and then merge the coverage data. This requires that the source code is identical for each build. If that is not the case, Coco refuses to merge.

We use a small example to illustrate how this works. The project consists of only one source file, project.cpp, and we build it on Microsoft® Windows and Linux™.

On Linux, we compile the project with the command:

$ csg++ /home/me/project.cpp -o project-unix

The project-unix.csmes file is generated and the tests can be executed and imported as usual into it.

On Windows, we compile the project with the command:

C:\code> cscl C:\Home\me\project.cpp /Feproject-windows.exe

The project-windows.exe.csmes file is generated.

Merging the coverage information is possible with cmmerge:

$ cmmerge -o project.csmes project-unix.csmes project-windows.exe.csmes

Unfortunately, this operation is not enough because the file project.csmes will then contain two project.cpp files:

  • /home/me/project.cpp
  • C:\Home\me\project.cpp

It is necessary to tell Coco that both files are the same. To do that, we use cmedit to change two absolute file names to an identical one:

$ cmedit project.csmes --rename="/home/me/,/PRJ/" --rename="C:\Home\me\,/PRJ/" --verbose

These two rules rename the base directories /home/me/ and C:\Home\me\ to /PRJ/ to a single source file: /PRJ/project.cpp. The coverage information from both platforms can now be merged.

Restrictions

Code generators

In some build processes, code generators are used, like flex/bison or, for Qt products, Meta-Object Compiler (moc). Then the code generators need to produce identical source code on each platform to make the editing work.

If this is not the case, cmedit will refuse the renaming operation because it assumes that the project is not produced from the same source code. The solution is then to skip explicitly the files that are automatically generated.

Let's consider the case of moc. qmake generates files with the name moc_*.cpp. The trick could consist of using the switch --force which skips the renaming operation for files with conflicts:

$ cmedit project.csmes --force --rename="/home/me/,/PRJ/" --rename="C:\Home\me\,/PRJ/" --verbose

As result, all source files are merged together in a directory PRJ except the moc files on which a conflict is detected. These moc files need then to be covered separately on each platforms.

Platform dependent macros

In general, C macros expand to platform dependent code and should therefore be avoided. If macro expansions are different between the platforms, Coco will create duplicates by appending a #1 or #2 to the source files. Both versions of the sources need then to be covered.

There are some common pitfalls that you need to know to avoid conflicts:

  • The __FILE__ macro can generate unwanted differences because it may contain the absolute source file name. To solve this issue, since this macro is often used in asserts, the easiest solution is simply to work with the release build. Another solution is to ensure that the source file names passed to the compiler are not absolute file names. In this case, on most platforms __FILE__ is not expended to be the source's absolute path.
  • The NULL macro may also be different on each platform. Sometimes it is expanded as (void*)0 and sometimes as __null. The proper solution is to avoid the comparison with NULL and to use the C++ extension std::nullptr.