diff options
Diffstat (limited to 'doc')
-rw-r--r-- | doc/tutorial/tests.org | 253 |
1 files changed, 253 insertions, 0 deletions
diff --git a/doc/tutorial/tests.org b/doc/tutorial/tests.org new file mode 100644 index 00000000..13dd3b39 --- /dev/null +++ b/doc/tutorial/tests.org @@ -0,0 +1,253 @@ +* Creating Tests + +To run tests with justbuild, we do /not/ have dedicated ~test~ subcommand. +Instead, we consider tests being a specific action that generates a test report. +Consequently, we use the ~build~ subcommand to build the test report, and +thereby run the test action. Those actions have to be tainted, in our case with +the string ~"test"~, causing that they can only be depended on by other targets +that are also tainted with ~"test"~. In this way, it is ensured that no test +target will end up in a production build. + +For the remainder of this section, we expect to have the project files available +resulting from successfully completing the tutorial section on /Building Hello +World/. We will demonstrate how to write a test binary for the ~greet~ library +and a shell test for the ~helloworld~ binary. + +** Creating a C++ test binary + +First, we will create a C++ test binary for testing the correct functionality of +the ~greet~ library. Therefore, we need to provide a C++ source file that performs +the actual testing and returns non-~0~ on failure. For simplicity reasons, we do +not use a testing framework for this tutorial. A simple test that captures +standard output and verifies it with the expected output should be provided in +the file ~greet/greet.test.cpp~: + +#+BEGIN_SRC cpp +#include <functional> +#include <iostream> +#include <string> +#include <unistd.h> +#include "greet/greet.hpp" + +template <std::size_t kMaxBufSize = 1024> +auto capture_stdout(std::function<void()> const& func) -> std::string { + int fd[2]; + if (pipe(fd) < 0) return {}; + int fd_stdout = dup(fileno(stdout)); + fflush(stdout); + dup2(fd[1], fileno(stdout)); + + func(); + fflush(stdout); + + std::string buf(kMaxBufSize, '\0'); + auto n = read(fd[0], &(*buf.begin()), kMaxBufSize); + close(fd[0]); + close(fd[1]); + dup2(fd_stdout, fileno(stdout)); + return buf.substr(0, n); +} + +auto test_greet(std::string const& name) -> bool { + auto expect = std::string{"Hello "} + name + "!\n"; + auto result = capture_stdout([&name] { greet(name); }); + std::cout << "greet output: " << result << std::endl; + return result == expect; +} + +int main() { + return test_greet("World") && test_greet("Universe") ? 0 : 1; +} +#+END_SRC + +Next, a new test target needs to be created in module ~greet~. This target uses +the rule ~["@", "rules", "CC/test", "test"]~ and needs to depend on the +~"greet"~ target. To create the test target, add the following to +~greet/TARGETS~: + +#+BEGIN_SRC js +{ "greet": + { /* ... unchanged ... */ } +, "test": + { "type": ["@", "rules", "CC/test", "test"] + , "name": ["test_greet"] + , "srcs": ["greet.test.cpp"] + , "deps": ["greet"] + } +} +#+END_SRC + +Before we can run the test, a proper default module for ~CC/test~ must be +provided. By specifying the appropriate target in this module the default test +runner can be overwritten by a different test runner fom the rule's workspace +root. However, in our case, we want to use the default runner and therefore it +is sufficient to create an empty module: + +Create ~tutorial-defaults/CC/test/TARGETS~: +#+BEGIN_SRC shell +$ mkdir -p tutorial-defaults/CC/test +$ echo '{}' > tutorial-defaults/CC/test/TARGETS +#+END_SRC + +Now we can run the test (i.e., build the test result): + +#+BEGIN_SRC shell +$ CONF=$(/usr/src/justbuild/bin/just-mr.py -C repos.json setup tutorial) +$ just build -C $CONF greet test +INFO: Requested target is [["@","tutorial","greet","test"],{}] +INFO: Analysed target [["@","tutorial","greet","test"],{}] +INFO: Export targets found: 0 cached, 0 uncached, 0 not eligible for caching +INFO: Target tainted ["test"]. +INFO: Discovered 5 actions, 3 trees, 0 blobs +INFO: Building [["@","tutorial","greet","test"],{}]. +INFO: Processed 5 actions, 2 cache hits. +INFO: Artifacts built, logical paths are: + result [359456b7b6a1f91dc435e483cc2b1fc4e8981bf0:8:f] + stderr [e69de29bb2d1d6434b8b29ae775ad8c2e48c5391:0:f] + stdout [21ac68aa0bceffd6d5ad49ba17df80f44f7f3735:59:f] + time-start [c119fd0e22e79555f2446410fd90de4c091039d9:11:f] + time-stop [c119fd0e22e79555f2446410fd90de4c091039d9:11:f] + (1 runfiles omitted.) +INFO: Target tainted ["test"]. +$ +#+END_SRC + +Note that the target is correctly reported as tainted with ~"test"~. It will +produce 3 additional actions for compiling, linking and running the test binary. + +The result of the test target are 5 artifacts: ~result~ (containing ~UNKNOWN~, +~PASS~, or ~FAIL~), ~stderr~, ~stdout~, ~time-start~, and ~time-stop~, and a +single runfile (omitted in the output above), which is a tree artifact with the +name ~test_greet~ that contains all of the above artifacts. The test was run +successfully as otherwise all reported artifacts would have been tagged with +~FAILED~ in the output, and justbuild would have returned the exit code ~2~. + +To immediately print the standard output produced by the test binary on the +command line, the ~-P~ option can be used. Argument to this option is the name +of the artifact that should be printed on the command line, in our case +~stdout~: + +#+BEGIN_SRC shell +$ just build -C $CONF greet test --log-limit 1 -P stdout +greet output: Hello World! + +greet output: Hello Universe! + +$ +#+END_SRC + +Note that ~--log-limit 1~ was just added to omit justbuild's ~INFO:~ prints. + +** Creating a shell test + +Similarly, to create a shell test for testing the ~helloworld~ binary, a test +script must be provided: + +~test.sh~: +#+BEGIN_SRC shell +set -e +[ "$(./helloworld)" = "Hello Universe!" ] +#+END_SRC + +The test target for this shell tests uses the rule +~["@", "rules", "shell/test", "script"]~ and must depend on the ~"helloworld"~ +target. To create the test target, add the following to the top-level ~TARGETS~ +file: + +#+BEGIN_SRC js +{ "helloworld": + { /* ... unchanged ... */ } +, "test": + { "type": ["@", "rules", "shell/test", "script"] + , "name": ["test_helloworld"] + , "test": ["test.sh"] + , "deps": ["helloworld"] + } +} +#+END_SRC + +Similar to the binary tests, also for shell tests we need to provide at least an +empty module for the test rule defaults: + +Create ~tutorial-defaults/shell/test/TARGETS~: +#+BEGIN_SRC shell +$ mkdir -p tutorial-defaults/shell/test +$ echo '{}' > tutorial-defaults/shell/test/TARGETS +#+END_SRC + +Now we can run the shell test (i.e., build the test result): + +#+BEGIN_SRC shell +$ just build -C $CONF test +INFO: Requested target is [["@","tutorial","","test"],{}] +INFO: Analysed target [["@","tutorial","","test"],{}] +INFO: Export targets found: 0 cached, 0 uncached, 0 not eligible for caching +INFO: Target tainted ["test"]. +INFO: Discovered 5 actions, 4 trees, 0 blobs +INFO: Building [["@","tutorial","","test"],{}]. +INFO: Processed 5 actions, 4 cache hits. +INFO: Artifacts built, logical paths are: + result [7ef22e9a431ad0272713b71fdc8794016c8ef12f:5:f] + stderr [e69de29bb2d1d6434b8b29ae775ad8c2e48c5391:0:f] + stdout [e69de29bb2d1d6434b8b29ae775ad8c2e48c5391:0:f] + time-start [9b4a96cc3b929d2909f74395d0317e93a59e621f:11:f] + time-stop [9b4a96cc3b929d2909f74395d0317e93a59e621f:11:f] + (1 runfiles omitted.) +INFO: Target tainted ["test"]. +$ +#+END_SRC + +The result is also similar, containing also the 5 artifacts and a single runfile +(omitted in the output above), which is a tree artifact with the name +~test_helloworld~ that contains all of the above artifacts. + +** Creating a compound test target + +As most people probably do not want to call every test target by hand, it is +desirable to compound test target that triggers the build of multiple test +reports. To do so, an ~"install"~ target can be used. It depends on the tests +that should be triggered and collects the runfiles of those (which happen to be +tree artifacts named the same way as the test and containing all test results). +Furthermore, as the dependent test targets are tainted by ~"test"~, also the +compound test target must be tainted by the same string. To create the compound +test target combining the two tests above, add the following to the top-level +~TARGETS~ file: + +#+BEGIN_SRC js +{ "helloworld": + { /* ... unchanged ... */ } +, "test": + { /* ... unchanged ... */ } +, "all_tests": + { "type": "install" + , "tainted": ["test"] + , "deps": + [ "test" + , ["greet", "test"] + ] + } +} +#+END_SRC + +Now we can run all tests at once by just building the compound test target +~"all_tests"~: + +#+BEGIN_SRC shell +$ just build -C $CONF all_tests +INFO: Requested target is [["@","tutorial","","all_tests"],{}] +INFO: Analysed target [["@","tutorial","","all_tests"],{}] +INFO: Export targets found: 0 cached, 0 uncached, 0 not eligible for caching +INFO: Target tainted ["test"]. +INFO: Discovered 8 actions, 5 trees, 0 blobs +INFO: Building [["@","tutorial","","all_tests"],{}]. +INFO: Processed 8 actions, 8 cache hits. +INFO: Artifacts built, logical paths are: + test_greet [7116c231e3a6da3d23b0549340d75d36a0a0c4ef:285:t] + test_helloworld [c5a0d9fb4ba586d88dc5cddadedb6ddb670e95c4:283:t] +INFO: Target tainted ["test"]. +$ +#+END_SRC + +As a result it reports the runfiles (result directories) of both tests as +artifacts. Both tests ran successfully as none of those artifacts in this output +above are tagged as ~FAILED~. |