Tests and Examples ================== This document serves as both the official documentation for usage examples and the executable test suite for the library. Settings -------- First, we import the library and configure the suite setup/teardown. We use `Start Kernel` to initialize the C++ environment (xeus-cpp) and `Shutdown Kernel` to clean it up. We also use `OperatingSystem` to create temporary header files for testing include paths. .. code:: robotframework *** Settings *** Library clang Library OperatingSystem Library Process Test Setup Start Kernel Test Teardown Run Keywords Shutdown Kernel ... AND Terminate All Processes Basic Execution --------------- The core keyword is `Source Exec`. It sends C++ code to the REPL. .. code:: robotframework *** Test Cases *** Hello World C++ [Documentation] Verifies that we can run simple C++ code. ${output}= Source Exec std::cout << "Hello from Robot!" << std::endl; Should Be Equal ${output} Hello from Robot! Variables --------- Variables defined in the global scope of the REPL persist across calls within the same kernel session. .. code:: robotframework *** Test Cases *** Define And Use Variable [Documentation] Defines a variable in one call and uses it in another. Source Exec int x = 42; ${result}= Source Exec std::cout << x; Should Be Equal ${result} 42 Defining Code Structures ------------------------ Use `Source Parse` to define classes, structs, or functions without producing output. This is useful for setting up the environment. .. code:: robotframework *** Test Cases *** Define Function And Class [Documentation] Defines a C++ function and a struct using Source Parse. Source Parse int add(int a, int b) { return a + b; } Source Parse struct Point { int x; int y; }; # Verify usage ${res}= Source Exec std::cout << add(10, 20); Should Be Equal ${res} 30 Source From File ---------------- You can also load C++ code directly from a file using `Source File`. .. code:: robotframework *** Test Cases *** Load C++ File [Documentation] Creates a temporary C++ file and loads it into the kernel. [Setup] None ${temp_dir}= Join Path ${OUTPUT DIR} source_file_test Create Directory ${temp_dir} ${cpp_path}= Join Path ${temp_dir} mylogic.cpp # Define a function in the file Create File ${cpp_path} int subtract(int a, int b) { return a - b; } Start Kernel # Load the file Source File ${cpp_path} # Call the function defined in the file ${res}= Source Exec std::cout << subtract(100, 40); Should Be Equal ${res} 60 [Teardown] Run Keywords Shutdown Kernel AND Remove Directory ${temp_dir} recursive=True Function Calls -------------- The `Call Function` keyword simplifies calling global C++ functions and getting their return value (printed to stdout). .. code:: robotframework *** Test Cases *** Call Global Function [Documentation] Calls the 'add' function defined in the previous test (same kernel session). # We redefine it here just in case tests are reordered or isolated in future, # though currently they share the kernel if not restarted. # Actually, Test Setup 'Start Kernel' restarts it every time! # So we must define it again. Source Parse int multiply(int a, int b) { return a * b; } ${res}= Call Function multiply 6 7 Should Be Equal ${res} 42 Expression Evaluation --------------------- `Get Value` evaluates a C++ expression and returns its string representation. .. code:: robotframework *** Test Cases *** Evaluate Expressions [Documentation] Uses Get Value to evaluate math and string ops. Source Exec int base = 10; ${val}= Get Value base * 5 + 3 Should Be Equal ${val} 53 Assertions ---------- We can use standard C++ boolean logic to perform assertions inside the kernel. .. code:: robotframework *** Test Cases *** Check Assertion [Documentation] Verifies the Assert keyword. Assert 1 == 1 Run Keyword And Expect Error *AssertionError* Assert 1 == 0 Type Introspection ------------------ The library provides helpers to identify C++ types, which is useful given the lack of direct Python object mapping. .. code:: robotframework *** Test Cases *** Check Type Identification [Documentation] Verifies Typeid and Typename keywords. ${id}= Typeid 42 # On Linux/Itanium it's 'i', on Windows/MSVC it's 'int' Should Match Regexp ${id} ^(i|int)$ ${name}= Typename std::string("hello") Should Contain ${name} string Nullptr Support --------------- The keyword `Nullptr` returns a string literal that is interpreted as a true C++ null pointer by the kernel. .. code:: robotframework *** Test Cases *** Verify Nullptr Literal [Documentation] Checks if the string 'nullptr' is recognized as a C++ null pointer. ${null_str}= Nullptr # We check if (nullptr == 0) in C++, which is true. ${is_null}= Source Exec std::cout << (${null_str} == 0); Should Be Equal ${is_null} 1 # We check the type. It can be 'std::nullptr_t' or 'decltype(nullptr)' ${type}= Typename ${null_str} Should Contain ${type} nullptr Custom Includes --------------- We can add custom directories to the include path and load specific headers. **Note:** `Add Include Path` must be called *before* `Start Kernel` (or followed by a restart) for changes to take effect. .. code:: robotframework *** Test Cases *** Using Custom Header [Documentation] Creates a temporary header, adds its path, and includes it. [Setup] None ${temp_dir}= Join Path ${OUTPUT DIR} include_test Create Directory ${temp_dir} ${header_path}= Join Path ${temp_dir} mymath.h Create File ${header_path} const double MY_PI_CONST = 3.14159; # Add path BEFORE starting kernel Add Include Path ${temp_dir} Start Kernel Source Include mymath.h ${pi_val}= Get Value MY_PI_CONST Should Be Equal ${pi_val} 3.14159 [Teardown] Run Keywords Shutdown Kernel AND Remove Directory ${temp_dir} recursive=True Shared Libraries ---------------- We can also load compiled shared libraries (.so/.dylib/dll) into the running kernel. .. code:: robotframework *** Keywords *** Run Unix Compile [Arguments] ${cmd} ${flags} ${ldflags} ${src} ${out} ${is_osx}= Evaluate sys.platform == 'darwin' modules=sys IF ${is_osx} ${shlib_flag}= Set Variable -dynamiclib ELSE ${shlib_flag}= Set Variable -shared END # Use shell=True to correctly handle flags string and environment variables like $PREFIX ${result}= Run Process ${cmd} ${flags} ${shlib_flag} -DEXPORT\= -fPIC -fvisibility\=default ${ldflags} -o ${out} ${src} shell=True Should Be Equal As Integers ${result.rc} 0 Run Windows Compile [Arguments] ${cmd} ${src} ${out} # Make sure the command has .exe extension # Conda provides 'clang++.exe', but CXX may be 'clang++' only, # so Run Process fails without shell=True or an explicit extension ${cmd_has_ext}= Evaluate '${cmd}'.lower().endswith('.exe') ${cmd_fixed}= Set Variable If ${cmd_has_ext} ${cmd} ${cmd}.exe # Check if it's strictly cl or clang-cl (which use MSVC flags) ${cmd_lower}= Evaluate '${cmd_fixed}'.lower() ${is_msvc}= Evaluate 'cl.exe' in '${cmd_lower}' IF ${is_msvc} ${result}= Run Process ${cmd_fixed} -DEXPORT\=__declspec(dllexport) /LD /Fe${out} ${src} ELSE # Assume clang++ or compatible driver ${result}= Run Process ${cmd_fixed} -DEXPORT\=__declspec(dllexport) -shared -o ${out} ${src} END # Debug IF ${result.rc} != 0 Log Compile Failed: ${result.stderr} level=ERROR Log Stdout: ${result.stdout} level=ERROR END Should Be Equal As Integers ${result.rc} 0 *** Test Cases *** Load Shared Library Test [Documentation] Compiles a shared lib and loads it at runtime via dlopen/LoadLibrary. [Setup] None ${temp_dir}= Join Path ${OUTPUT DIR} dlopen_test Create Directory ${temp_dir} ${is_windows}= Evaluate sys.platform == 'win32' modules=sys ${ext}= Set Variable If ${is_windows} dll so ${src_path}= Join Path ${temp_dir} dlopen_lib.cpp ${lib_path}= Join Path ${temp_dir} libdlopen_lib.${ext} Create File ${src_path} extern "C" EXPORT int dlopen_func() { return 789; } ${cxx}= Get Environment Variable CXX clang++ ${cxxflags}= Get Environment Variable CXXFLAGS ${EMPTY} ${ldflags}= Get Environment Variable LDFLAGS ${EMPTY} IF ${is_windows} Run Windows Compile ${cxx} ${src_path} ${lib_path} ELSE Run Unix Compile ${cxx} ${cxxflags} ${ldflags} ${src_path} ${lib_path} END Start Kernel Load Shared Library ${lib_path} Source Parse extern "C" int dlopen_func(); ${res}= Source Exec std::cout << dlopen_func(); Should Be Equal ${res} 789 [Teardown] Run Keywords Shutdown Kernel AND Remove Directory ${temp_dir} recursive=True Link Libraries Test [Documentation] Verifies linking libraries at startup (-L and -l flags). [Setup] None ${temp_dir}= Join Path ${OUTPUT DIR} link_test Create Directory ${temp_dir} ${src_path}= Join Path ${temp_dir} link_lib.cpp ${is_windows}= Evaluate sys.platform == 'win32' modules=sys ${ext}= Set Variable If ${is_windows} dll so # Name must start with 'lib' for -l to work ${lib_path}= Join Path ${temp_dir} libmylink.${ext} Create File ${src_path} extern "C" EXPORT int link_func() { return 456; } ${cxx}= Get Environment Variable CXX clang++ ${cxxflags}= Get Environment Variable CXXFLAGS ${EMPTY} ${ldflags}= Get Environment Variable LDFLAGS ${EMPTY} Run Keyword If ${is_windows} ... Run Windows Compile ${cxx} ${src_path} ${lib_path} ... ELSE ... Run Unix Compile ${cxx} ${cxxflags} ${ldflags} ${src_path} ${lib_path} # Configure linking BEFORE starting kernel Add Link Directory ${temp_dir} Link Libraries mylink Start Kernel Source Parse extern "C" int link_func(); ${res}= Source Exec std::cout << link_func(); Should Be Equal ${res} 456 [Teardown] Run Keywords Shutdown Kernel AND Remove Directory ${temp_dir} recursive=True