blob: 8819f544a3348702d9b3775325336e4f7c83f63e [file] [log] [blame] [view]
# 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.
## 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 https://github.com/google/iree/tree/master/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
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](GetStarted/generic_vulkan_env_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/master/googletest/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/google/iree/tree/master/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
)
```
## Compiler Tests
Tests for the IREE compilation pipeline are written as lit tests in the same
style as MLIR.
### Running a Test
For the test
https://github.com/google/iree/tree/master/iree/compiler/Dialect/VMLA/Conversion/HLOToVMLA/test/math_ops.mlir
With CMake, run this from the build directory:
```shell
$ ctest -R iree/compiler/Dialect/VMLA/Conversion/HLOToVMLA/test:math_ops.mlir.test
```
With Bazel, run this from the repo root:
```shell
$ bazel test iree/compiler/Dialect/VMLA/Conversion/HLOToVMLA/test:math_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. Instead of
`FileCheck`, use
[`IreeFileCheck`](https://github.com/google/iree/tree/master/iree/tools/IreeFileCheck.sh),
a shell-script wrapper around FileCheck that passes it a few
`--do-the-right-thing` flags.
As with all 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:lit_test.bzl", "iree_lit_test_suite")
iree_lit_test_suite(
name = "lit",
srcs = glob(["*.mlir"]),
data = [
"//iree/tools:IreeFileCheck",
"//iree/tools:iree-opt",
],
)
```
There is a corresponding CMake function, calls to which will be generated by our
[Bazel to CMake Converter](https://github.com/google/iree/tree/master/build_tools/bazel_to_cmake/bazel_to_cmake.py).
```cmake
file(GLOB _GLOB_X_MLIR LIST_DIRECTORIES false RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} CONFIGURE_DEPENDS *.mlir)
iree_lit_test_suite(
NAME
lit
SRCS
"${_GLOB_X_MLIR}"
DATA
iree::tools::IreeFileCheck
iree::tools::iree-opt
)
```
You can also create a test for a single file with `iree_lit_test`.
## IREE Core End-to-End Tests
Here "End-to-End" means from the input accepted by the IREE core compiler (the
XLA HLO MLIR dialect) to execution using the IREE runtime components. It does
not include tests of the integrations with ML frameworks (e.g. TF) or bindings
to other languages (e.g. Python).
To test these flows we use a custom framework called `check`.
> Note:<br>
> &nbsp;&nbsp;&nbsp;&nbsp;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`.
### Running a Test
For the test
https://github.com/google/iree/tree/master/iree/test/e2e/xla_ops/floor.mlir
compiled for the VMLA target backend and running on the VMLA 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 iree/test/e2e/xla_ops:check_vmla_vmla_floor.mlir
```
With Bazel, run this from the repo root:
```shell
$ bazel test iree/test/e2e/xla_ops:check_vmla_vmla_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 `iree/test/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 XLA HLO floor operation:
```mlir
func @tensor() attributes { iree.module.export } {
%input = iree.unfoldable_constant dense<[0.0, 1.1, 2.5, 4.9]> : tensor<4xf32>
%result = "xla_hlo.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 @scalar() attributes { iree.module.export } {
%input = iree.unfoldable_constant dense<101.3> : tensor<f32>
%result = "xla_hlo.floor"(%input) : (tensor<f32>) -> tensor<f32>
check.expect_almost_eq_const(%result, dense<101.0> : tensor<f32>): tensor<f32>
return
}
func @double() attributes { iree.module.export } {
%input = iree.unfoldable_constant dense<11.2> : tensor<f64>
%result = "xla_hlo.floor"(%input) : (tensor<f64>) -> tensor<f64>
check.expect_almost_eq_const(%result, dense<11.0> : tensor<f64>): tensor<f64>
return
}
func @negative() attributes { iree.module.export } {
%input = iree.unfoldable_constant dense<-1.1> : tensor<f32>
%result = "xla_hlo.floor"(%input) : (tensor<f32>) -> tensor<f32>
check.expect_almost_eq_const(%result, dense<-2.0> : tensor<f32>): tensor<f32>
return
}
```
The test case functions are exported using the `iree.module.export` attribute.
Each of these exported functions will be used to create a test case in gtest.
Note the use of
[`iree.unfoldable_constant`](https://google.github.io/iree/Dialects/IREEDialect#ireeunfoldable_constant-ireeunfoldableconstantop)
to specify test constants. If we were to use a regular constant, the compiler
would "helpfully" fold away everything at compile time and our test would not
actually test the runtime. `unfoldable_constant` hides the value of the constant
from the compiler so it cannot use it at compile time. To hide an arbitrary
SSA-value, you can use
[`iree.do_not_optimize`](https://google.github.io/iree/Dialects/IREEDialect#ireedo_not_optimize-ireedonotoptimizeop).
This wraps any value in an unoptimizable identity function.
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://google.github.io/iree/Dialects/CheckDialect).
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 = 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.
### Dynamic Shapes
Constants with dynamic shape are not yet supported. See
https://github.com/google/iree/issues/1601. For now, these tests have to use
`iree-run-mlir` lit tests and input arguments.
### 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_vmla_vmla_floor.mlir",
src = "floor.mlir",
driver = "vmla",
target_backend = "vmla",
)
```
The target naming convention is "check_backend_driver_src". The generated test
will automatically be tagged with a "driver=vmla" 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_vmla_vmla",
srcs = glob(["*.mlir"]),
driver = "vmla",
target_backend = "vmla",
)
```
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_vmla_vmla" 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 = [
("vmla", "vmla"),
("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/google/iree/tree/master/build_tools/bazel_to_cmake/bazel_to_cmake.py).
## Binding Tests
TODO(laurenzo): Explain binding test setup.
## Integration Tests
TODO(silvasean): Explain integration test setup.