byexample

Write snippets of code in your documentation and execute them as regression tests.

View project on GitHub

C/C++

Run the C/C++ examples calling byexample as:

$ byexample -l cpp your-file-here                # byexample: +skip

To support C/C++, byexample relays in the cling interpreter.

You need to have cling installed first.

It is still an experimental feature that works pretty well but it is not immune to bugs, quirks nor crashes.

Don’t forget to send your feedback to the cling community.

Stability: experimental - non backward compatibility changes are possible or even removal between versions (even patch versions).

Because installing and using cling may be a little difficult, we offer a docker image with cling pre-installed (the dockerfile is available too).

To use the cling in that docker image follow the how to run with docker tutorial which explains how to configure the host system and how to run byexample with an interpreter (cling) which it is in a docker image.

Variable definition

All the variables are global and can be accessed by other examples

?: double radio = 2.0;
?: double sup = 3.14 * (radio * radio);

?: sup
(double) 12.56<...>

The last expression without ending with a ; is interpreted by cling as the expression to not only evaluate but also to print its value.

stdlib

You can use the stdlib as usual.

Here is an example of how to print something and check the output later:

?: #include <iostream>

?: int i;
?: for (i = 0; i < 3; ++i) {
::    std::cout << i << std::endl;
:: }
0
1
2

External libs

In addition to stdlib you can add your own. These can be in the form of C++ code and it will be compiled by cling or in the form of PIC, shared and dynamically linked library (aka, .so)

For the first case you need to pass the path to the source code with .L or these pragma

?: #pragma cling load("test/ds/mylib1.cpp")   // this will compile mylib1.cpp
?: #include "test/ds/mylib1.h"    // the headers are needed to, as usual

?: mylib1_foo(2)
calling my lib1
(int) 4

For the second case, we need to compile the code ourselves:

$ g++ -shared -fPIC -o test/ds/libmylib2.so test/ds/mylib2.cpp  # byexample: +timeout=16

The load then proceeds as before.

?: #pragma cling load("test/ds/libmylib2.so")   // already compiled
?: #include "test/ds/mylib2.h"    // the headers are needed to, as usual

?: mylib2_foo(2)
increased performance with lib2
(int) 6

We can define where to search for libraries and headers via two pragma so we can avoid some typing

?: #pragma cling add_library_path("test/ds/")
?: #pragma cling add_include_path("test/ds/")

?: #pragma cling load("mylib3.cpp")   // works for .so too
?: #include "mylib3.h"

?: mylib3_foo(2)
lib3, four times better
(int) 8

Note: cling is quite experimental and at least for the 0.6 version, it has not good diagnostic messages.

If you find an error like fatal error: 'libmylib2.so' file not found it may indicate that you are not configuring the path correctly or that the library was not compiled as shared.

Best way to troubleshoot is to use explicit paths to distinguish one error from the other.

If cling tries to load you shared library but it complains that it is not an UTF-8 valid file it means that cling is trying to see the library as source code. I found that this happen if the library is not compiled as a shared library.

Double check with file libmylib2.so, you should see something like libmylib2.so: ELF 64-bit LSB pie executable, ..., dynamically linked, ...

Running C code (and not C++)

cling only supports C++ however it is possible to test C code if it is compiled outside of cling and loaded as a library.

$ gcc -std=c99 -shared -fPIC -o test/ds/libmylibC.so test/ds/mylibC.c   # byexample: +timeout=16
?: #pragma cling load("test/ds/libmylibC.so")   // C code
?: #include "test/ds/mylibC.h"    // the declarations must be inside the extern "C" {...}

?: mylibC_foo(2)
(int) 3

While the examples will be running as C++ code, the function mylibC_foo was compiled as C code.

Keep in mind that the header mylibC.h must have all the function declarations inside extern "C" { ... } so cling will know that it has to use the “C” calling convention and not the “C++” one.

See language linkage for reference.

Syntax errors

byexample will show you the syntax errors detected by cling. You can even check for them as part of the normal output:

?: for (i = 0; i < unknown; ++i) {
::    std::cout << i << std::endl;
:: }
<...>: error: use of undeclared identifier 'unknown'
 for (i = 0; i < unknown; ++i) {
                 ^

Known limitations

Gotchas

To print boolean expressions you need to surround them with parenthesis

?: (1 == 2)
(bool) false

Terminal support

To work with the current C/C++ interpreter, cling, the ANSI terminal emulator is enabled by default (+term=ansi) and cannot be disabled.

Also, the terminal geometry is set to a default of 1024 columns and 2048 rows with a minimum of 128x128.

Changed: before byexample 11.0.0 the runner was forced to use a 128x128 geometry without possibilities of change it. Since 11.0.0, the default is expanded to 2048x1024 and can be changed at any time but always with a minimum of 128x128.

Note: the minimum is enforced because if the snippet of C++ or the output is larger than the size of the terminal, the output will be undefined.

The default of 2048x1024 should be fine for most of the use cases.

Echoed input lines

If the C/C++ snippet has a very long line, greater than the terminal’s width, the last part of the line that does not fit in the terminal will be echoed in the output of the example.

This is an annoying artifact due how cling works.

A simple workaround is to make the lines of the code in the snippet shorter or increase the terminal width.

Abort on a timeout

If a C/C++ example takes too long and timeout, the whole execution timeout.

Type text

The type feature (+type) is not supported.

C/C++ specific options

$ byexample -l cpp --show-options       # byexample: +norm-ws
<...>
cpp's specific options
----------------------
  None.