| # Testing Guide |
| |
| Like the IREE project in general, IREE tests are divided into a few different |
| components and use different tooling depending on the needs of that component. |
| |
| | Test type | Test | Build system | Supported platforms | |
| |:-------------- | :----------------- | ----------- | ------------- | |
| | Compiler tests | iree_lit_test | Bazel/CMake | Host | |
| | Runtime tests | iree_cc_test | Bazel/CMake | Host/Device | |
| | | iree_native_test | Bazel/CMake | Host/Device | |
| | | iree_hal_cts_test_suite | CMake | Host/Device | |
| | Core E2E tests | iree_check_test | Bazel/CMake | Host/Device | |
| | | iree_trace_runner_test | Bazel/CMake | Host/Device | |
| | | iree_single_backend_generated_trace_runner_test | Bazel/CMake | Host/Device | |
| | | iree_generated_trace_runner_test | Bazel/CMake | Host/Device | |
| | | iree_static_linker_test | CMake | Host/Device | |
| |
| There are also more `*_test_suite` targets that groups test targets with the |
| same configuration together. |
| |
| ## Compiler Tests |
| |
| Tests for the IREE compilation pipeline are written as lit tests in the same |
| style as MLIR. |
| |
| By convention, IREE includes tests for printing and parsing of MLIR ops in |
| `.../IR/test/{OP_CATEGORY}_ops.mlir` files, tests for folding and |
| canonicalization in `.../IR/test/{OP_CATEGORY}_folding.mlir` files, and tests |
| for compiler passes and pipelines in other `.../test/*.mlir` files. |
| |
| ### Running a Test |
| |
| For the test |
| https://github.com/openxla/iree/blob/main/iree/compiler/Dialect/VM/Conversion/MathToVM/test/arithmetic_ops.mlir |
| |
| With CMake, run this from the build directory: |
| |
| ```shell |
| $ ctest -R iree/compiler/Dialect/VM/Conversion/MathToVM/test/arithmetic_ops.mlir.test |
| ``` |
| |
| With Bazel, run this from the repo root: |
| |
| ```shell |
| $ bazel test //compiler/src/iree/compiler/Dialect/VM/Conversion/MathToVM/test:arithmetic_ops.mlir.test |
| ``` |
| |
| ### Writing a Test |
| |
| For advice on writing MLIR compiler tests, see the |
| [MLIR testing guide](https://mlir.llvm.org/getting_started/TestingGuide/). Tests |
| should be `.mlir` files in `test` directory adjacent to the functionality they |
| are testing. Instead of `mlir-opt`, use `iree-opt`, which registers IREE |
| dialects and passes and doesn't register some unnecessary core ones. |
| |
| As with most parts of the IREE compiler, these should not have a dependency on |
| the runtime. |
| |
| ### Configuring the Build System |
| |
| In the Bazel BUILD file, create a `iree_lit_test_suite` rule. We usually create |
| a single suite that globs all `.mlir` files in the directory and is called |
| "lit". |
| |
| ```bzl |
| load("//iree/build_tools/bazel:iree_lit_test.bzl", "iree_lit_test_suite") |
| |
| iree_lit_test_suite( |
| name = "lit", |
| srcs = glob(["*.mlir"]), |
| tools = [ |
| "@llvm-project//llvm:FileCheck", |
| "//tools:iree-opt", |
| ], |
| ) |
| ``` |
| |
| There is a corresponding CMake function, calls to which will be generated by our |
| [Bazel to CMake Converter](https://github.com/openxla/iree/tree/main/build_tools/bazel_to_cmake/bazel_to_cmake.py). |
| |
| ```cmake |
| iree_lit_test_suite( |
| NAME |
| lit |
| SRCS |
| "arithmetic_ops.mlir" |
| DATA |
| FileCheck |
| iree-opt |
| ) |
| ``` |
| |
| You can also create a test for a single file with `iree_lit_test`. |
| |
| ## Runtime Tests |
| |
| Tests for the runtime C++ code use the |
| [Google Test](https://github.com/google/googletest) testing framework. They |
| should generally follow the style and best practices of that framework. |
| |
| ### Running a Test |
| |
| For the test iree/base/arena_test.cc |
| |
| With CMake, run this from the build directory: |
| |
| ```shell |
| $ ctest -R iree/base/arena_test |
| ``` |
| |
| With Bazel, run this from the repo root: |
| |
| ```shell |
| $ bazel test iree/base:arena_test |
| ``` |
| |
| ### Setting test environments |
| |
| Parallel testing for `ctest` can be enabled via the `CTEST_PARALLEL_LEVEL` |
| environment variable. For example: |
| |
| ```shell |
| $ export CTEST_PARALLEL_LEVEL=$(nproc) |
| ``` |
| |
| To use the Vulkan backend as test driver, you may need to select between a |
| Vulkan implementation from SwiftShader and multiple Vulkan-capable hardware |
| devices. This can be done via environment variables. See the |
| [generic Vulkan setup](../get_started/vulkan_environment_setup.md#useful-environment-variables) |
| page for details regarding these variables. |
| |
| For Bazel, you can persist the configuration in `user.bazelrc` to save typing. |
| For example: |
| |
| ```shell |
| test:vkswiftshader --test_env="LD_LIBRARY_PATH=..." |
| test:vkswiftshader --test_env="VK_LAYER_PATH=..." |
| test:vknative --test_env="LD_LIBRARY_PATH=..." |
| test:vknative --test_env="VK_LAYER_PATH=..." |
| ``` |
| |
| Then you can use `bazel test --config=vkswiftshader` to select SwiftShader as |
| the Vulkan implementation. Similarly for other implementations. |
| |
| ### Writing a Test |
| |
| For advice on writing tests in the Googletest framework, see the |
| [Googletest primer](https://github.com/google/googletest/blob/main/docs/primer.md). |
| Test files for source file `foo.cc` with build target `foo` should live in the |
| same directory with source file `foo_test.cc` and build target `foo_test`. You |
| should `#include` `iree/testing/gtest.h` instead of any of the gtest or gmock |
| headers. |
| |
| As with all parts of the IREE runtime, these should not have a dependency on the |
| compiler. |
| |
| ### Configuring the Build System |
| |
| In the Bazel BUILD file, create a `cc_test` target with your test file as the |
| source and any necessary dependencies. Usually, you can link in a standard gtest |
| main function. Use `iree/testing:gtest_main` instead of the `gtest_main` that |
| comes with gtest. |
| |
| ```bzl |
| cc_test( |
| name = "arena_test", |
| srcs = ["arena_test.cc"], |
| deps = [ |
| ":arena", |
| "//iree/testing:gtest_main", |
| ], |
| ) |
| ``` |
| |
| We have created a corresponding CMake function `iree_cc_test` that mirrors the |
| Bazel rule's behavior. Our |
| [Bazel to CMake converter](https://github.com/openxla/iree/tree/main/build_tools/bazel_to_cmake/bazel_to_cmake.py) |
| should generally derive the `CMakeLists.txt` file from the BUILD file: |
| |
| ```cmake |
| iree_cc_test( |
| NAME |
| arena_test |
| SRCS |
| "arena_test.cc" |
| DEPS |
| ::arena |
| iree::testing::gtest_main |
| ) |
| ``` |
| |
| There are other more specific test targets, such as `iree_hal_cts_test_suite`, |
| which are designed to test specific runtime support with template configuration |
| and is not supported by Bazel rules. |
| |
| ## IREE Core End-to-End Tests |
| |
| Here "End-to-End" means from the input accepted by the IREE core compiler |
| (dialects like TOSA, MHLO, Linalg, and Arithmetic) to execution using the |
| IREE runtime components. It does not include tests of the integrations with ML |
| frameworks (e.g. TensorFlow, TensorFlow Lite) or bindings to other languages |
| (e.g. Python). |
| |
| We avoid using the more traditional `lit` tests used elsewhere in the compiler |
| for runtime execution tests. Lit tests require running the compiler tools on |
| the test platform through shell or python scripts that act on files from a local |
| file system. On platforms like Android, the web, and embedded systems, each of |
| these features is either not available or is severely limited. |
| |
| Instead, to test these flows we use a custom framework called `check`. The check |
| framework compiles test programs on the host machine into standalone test binary |
| files that can be pushed to test devices (such as Android phones) where they |
| run with gtest style assertions (e.g. `check.expect_almost_eq(lhs, rhs)`). |
| |
| > Note:<br> |
| > IREE end-to-end tests historically used `iree-run-mlir`. |
| > We are in the process of transitioning them to use the check framework, but |
| > that migration is incomplete, so some tests still use `iree-run-mlir`. |
| |
| ### Building e2e tests |
| |
| The files needed by these tests are not built by default with CMake. You'll |
| need to build the special `iree-test-deps` target to generate test files prior |
| to running CTest (from the build directory): |
| |
| ```shell |
| $ cmake --build . --target iree-test-deps |
| ``` |
| |
| ### Running a Test |
| |
| For the test |
| https://github.com/openxla/iree/tree/main/tests/e2e/xla_ops/floor.mlir |
| compiled for the VMVX target backend and running on the VMVX driver (here they |
| match exactly, but in principle there's a many-to-many mapping from backends to |
| drivers). |
| |
| With CMake, run this from the build directory: |
| |
| ```shell |
| $ ctest -R tests/e2e/xla_ops/check_vmvx_local-task_floor.mlir |
| ``` |
| |
| With Bazel, run this from the repo root: |
| |
| ```shell |
| $ bazel test tests/e2e/xla_ops:check_vmvx_local-task_floor.mlir |
| ``` |
| |
| ### Setting test environments |
| |
| Similarly, you can use environment variables to select Vulkan implementations |
| for running tests as explained in the [Runtime Tests](#runtime-tests) section. |
| |
| ### Writing a Test |
| |
| These tests live in `tests/e2e`. A single test consists of a `.mlir` source |
| file specifying an IREE module where each exported function takes no inputs and |
| returns no results and corresponds to a single test case. |
| |
| As an example, here are some tests for the MHLO floor operation: |
| |
| ```mlir |
| func.func @tensor() { |
| %input = util.unfoldable_constant dense<[0.0, 1.1, 2.5, 4.9]> : tensor<4xf32> |
| %result = "mhlo.floor"(%input) : (tensor<4xf32>) -> tensor<4xf32> |
| check.expect_almost_eq_const(%result, dense<[0.0, 1.0, 2.0, 4.0]> : tensor<4xf32>): tensor<4xf32> |
| return |
| } |
| |
| func.func @scalar() { |
| %input = util.unfoldable_constant dense<101.3> : tensor<f32> |
| %result = "mhlo.floor"(%input) : (tensor<f32>) -> tensor<f32> |
| check.expect_almost_eq_const(%result, dense<101.0> : tensor<f32>): tensor<f32> |
| return |
| } |
| |
| func.func @negative() { |
| %input = util.unfoldable_constant dense<-1.1> : tensor<f32> |
| %result = "mhlo.floor"(%input) : (tensor<f32>) -> tensor<f32> |
| check.expect_almost_eq_const(%result, dense<-2.0> : tensor<f32>): tensor<f32> |
| return |
| } |
| ``` |
| |
| Test cases are created in gtest for each public function exported by the module. |
| |
| Note the use of `util.unfoldable_constant` to specify test constants. If we were |
| to use a regular constant the compiler would fold away everything at compile |
| time and our test would not actually test the runtime. `unfoldable_constant` |
| adds a barrier that prevents folding. To prevent folding/constant propagate on |
| an arbitrary SSA-value you can use `util.optimization_barrier`. |
| |
| Next we use this input constant to exercise the runtime feature under test (in |
| this case, just a single floor operation). Finally, we use a check dialect |
| operation to make an assertion about the output. There are a few different |
| [assertion operations](https://github.com/openxla/iree/tree/main/iree/compiler/Modules/Check). |
| Here we use the `expect_almost_eq_const` op: *almost* because we are comparing |
| floats and want to allow for floating-point imprecision, and *const* because we |
| want to compare it to a constant value. This last part is just syntactic sugar |
| around |
| |
| ```mlir |
| %expected = arith.constant dense<101.0> : tensor<f32> |
| check.expect_almost_eq(%result, %expected) : tensor<f32> |
| ``` |
| |
| The output of running this test looks like: |
| |
| ```txt |
| [==========] Running 4 tests from 1 test suite. |
| [----------] Global test environment set-up. |
| [----------] 4 tests from module |
| [ RUN ] module.tensor |
| [ OK ] module.tensor (76 ms) |
| [ RUN ] module.scalar |
| [ OK ] module.scalar (79 ms) |
| [ RUN ] module.double |
| [ OK ] module.double (55 ms) |
| [ RUN ] module.negative |
| [ OK ] module.negative (54 ms) |
| [----------] 4 tests from module (264 ms total) |
| |
| [----------] Global test environment tear-down |
| [==========] 4 tests from 1 test suite ran. (264 ms total) |
| [ PASSED ] 4 tests. |
| ``` |
| |
| The "module" name for the test suite comes from the default name for an implicit |
| MLIR module. To give the test suite a more descriptive name, use an explicit |
| named top-level module in this file. |
| |
| ### Configuring the Build System |
| |
| A single `.mlir` source file can be turned into a test target with the |
| `iree_check_test` Bazel macro (and corresponding CMake function). |
| |
| ```bzl |
| load("//build_tools/bazel:iree_check_test.bzl", "iree_check_test") |
| |
| iree_check_test( |
| name = "check_vmvx_local-task_floor.mlir", |
| src = "floor.mlir", |
| driver = "local-task", |
| target_backend = "vmvx", |
| ) |
| ``` |
| |
| The target naming convention is "check_backend_driver_src". The generated test |
| will automatically be tagged with a "driver=vmvx" tag, which can help filter |
| tests by backend (especially when many tests are generated, as below). |
| |
| Usually we want to create a suite of tests across many backends and drivers. |
| This can be accomplished with additional macros. For a single backend/driver |
| pair: |
| |
| ```bzl |
| load("//build_tools/bazel:iree_check_test.bzl", "iree_check_single_backend_test_suite") |
| |
| iree_check_single_backend_test_suite( |
| name = "check_vmvx_local-task", |
| srcs = glob(["*.mlir"]), |
| driver = "local-task", |
| target_backend = "vmvx", |
| ) |
| ``` |
| |
| This will generate a separate test target for each file in `srcs` with a name |
| following the convention above as well as a Bazel |
| [test_suite](https://docs.bazel.build/versions/master/be/general.html#test_suite) |
| called "check_vmvx_local-task" that will run all the generated tests. |
| |
| You can also generate suites across multiple pairs: |
| |
| ```bzl |
| load("//build_tools/bazel:iree_check_test.bzl", "iree_check_test_suite") |
| |
| iree_check_test_suite( |
| name = "check", |
| srcs = ["success.mlir"], |
| # Leave this argument off to run on all supported backend/driver pairs. |
| target_backends_and_drivers = [ |
| ("vmvx", "local-task"), |
| ("vulkan-spirv", "vulkan"), |
| ], |
| ) |
| ``` |
| |
| This will create a test per source file and backend/driver pair, a test suite |
| per backend/driver pair, and a test suite, "check", that will run all the tests. |
| |
| The CMake functions follow a similar pattern. The calls to them are generated in |
| our `CMakeLists.txt` file by |
| [bazel_to_cmake](https://github.com/openxla/iree/tree/main/build_tools/bazel_to_cmake/bazel_to_cmake.py). |
| |
| There are other test targets that generate tests based on template configuraton |
| and platform detection, such as `iree_static_linker_test`. Those targets are |
| not supported by Bazel rules at this point. |
| |
| ## Binding Tests |
| |
| TODO(laurenzo): Explain binding test setup. |
| |
| ## Integration Tests |
| |
| TODO(silvasean): Explain integration test setup. |