# To add new data-driven tests to the rocBLAS Google Test Framework: **I**. Create a C++ header file with the name `testing_.hpp` in the `include` subdirectory, with templated functions for a specific rocBLAS routine. Examples: ``` testing_gemm.hpp testing_gemm_ex.hpp ``` In this `testing_*.hpp` file, create a templated function which returns `void` and accepts a `const Arguments&` parameter. For example: ``` template void testing_gemm_ex(const Arguments& arg) { // ... } ``` This function should be generalized with template parameters as much as possible, to avoid copy-and-paste code. In this function, use the following macros to check results: ``` HIP_CHECK_ERROR Verifies that a HIP call returns success ROCBLAS_CHECK_ERROR Verifies that a rocBLAS call returns success EXPECT_ROCBLAS_STATUS Verifies that a rocBLAS call returns a certain status UNIT_CHECK Check that two answers agree (see unit.hpp) NEAR_CHECK Check that two answers are close (see near.hpp) ``` In addition, you can use Google Test Macros such as the below, as long as they are guarded by `#ifdef GOOGLE_TEST`: ``` EXPECT_EQ ASSERT_EQ EXPECT_TRUE ASSERT_TRUE ... ``` Note: The `device_vector` template allocates memory on the device. You must check whether converting the `device_vector` to `bool` returns `false`, and if so, report a HIP memory error and then exit the current function. For example: ``` // allocate memory on device device_vector dx(size_x); device_vector dy(size_y); if(!dx || !dy) { CHECK_HIP_ERROR(hipErrorOutOfMemory); return; } ``` The general outline of the function should be: 1. Convert any scalar arguments (e.g., `alpha` and `beta`) to `double`. 2. If the problem size arguments are invalid, use a `safe_size` to allocate arrays, call the rocBLAS routine with the original arguments, and verify that it returns `rocblas_status_invalid_size`. Return. 3. Set up host and device arrays (see `rocblas_vector.hpp` and `rocblas_init.hpp`). 4. Call a CBLAS or other reference implementation on the host arrays. 5. Call rocBLAS using both device pointer mode and host pointer mode, verifying that every rocBLAS call is successful by wrapping it in `ROCBLAS_CHECK_ERROR()`. 6. If `arg.unit_check` is enabled, use `UNIT_CHECK` to validate results. 7. (Deprecated) If `arg.norm_check` is enabled, calculate and print out norms. 8. If `arg.timing` is enabled, perform benchmarking (currently under refactoring). **II**. Create a C++ file with the name `_gtest.cpp` in the `gtest` subdirectory, where `` is a non-type-specific shorthand for the function(s) being tested. Examples: ``` gemm_gtest.cpp trsm_gtest.cpp blas1_gtest.cpp ``` In the C++ file, perform these steps: A. Include the header files related to the tests, as well as `type_dispatch.hpp`. For example: ```c++ #include "testing_syr.hpp" #include "type_dispatch.hpp" ``` B. Wrap the body with an anonymous namespace, to minimize namespace collisions: ```c++ namespace { ``` C. Create a templated class which accepts any number of type parameters followed by one anonymous trailing type parameter defaulted to `void` (to be used with `enable_if`). Choose the number of type parameters based on how likely in the future that the function will support a mixture of that many different types, e.g. Input type (`Ti`), Output type (`To`), Compute type (`Tc`). If the function will never support more than 1-2 type parameters, then that many can be used. But if the function may be expanded later to support mixed types, then those should be planned for ahead of time and placed in the template parameters. Unless the number of type parameters is greater than one and is always fixed, then later type parameters should default to earlier ones, so that a subset of type arguments can used, and so that code which works for functions which take one type parameter may be used for functions which take one or more type parameters. For example: ```c++ template< typename Ti, typename To = Ti, typename Tc = To, typename = void> ``` Make the primary definition of this class template derive from the `rocblas_test_invalid` class. For example: ```c++ template struct syr_testing : rocblas_test_invalid { }; ``` D. Create one or more partial specializations of the class template conditionally enabled by the type parameters matching legal combinations of types. If the first type argument is `void`, then these partial specializations must not apply, so that the default based on `rocblas_test_invalid` can perform the correct behavior when `void` is passed to indicate failure. In the partial specialization(s), derive from the `rocblas_test_valid` class. In the partial specialization(s), create a functional `operator()` which takes a `const Arguments&` parameter and calls templated test functions (usually in `include/testing_*.hpp`) with the specialization's template arguments when the `arg.function` string matches the function name. If `arg.function` does not match any function related to this test, mark it as a test failure. For example: ```c++ template struct syr_testing || std::is_same_v> > : rocblas_test_valid { void operator()(const Arguments& arg) { if(!strcmp(arg.function, "syr")) testing_syr(arg); else FAIL() << "Internal error: Test called with unknown function: " << arg.function; } }; ``` E. If necessary, create a type dispatch function for this function (or group of functions it belongs to) in `include/type_dispatch.hpp`. If possible, use one of the existing dispatch functions, even if it covers a superset of allowable types. The purpose of `type_dispatch.hpp` is to perform runtime type dispatch in a single place, rather than copying it across several test files. The type dispatch function takes a `template` template parameter of `template class` and a function parameter of type `const Arguments&`. It looks at the runtime type values in `Arguments`, and instantiates the template with one or more static type arguments, corresponding to the dynamic runtime type arguments. It treats the passed template as a functor, passing the Arguments argument to a particular instantiation of it. The combinations of types handled by this "runtime type to template type instantiation mapping" function can be general, because the type combinations which do not apply to a particular test case will have the template argument set to derive from `rocblas_test_invalid`, which will not create any unresolved instantiations. If unresolved instantiation compile or link errors occur, then the `enable_if<>` condition in step D needs to be refined to be `false` for type combinations which do not apply. The return type of this function needs to be `auto`, picking up the return type of the functor. If the runtime type combinations do not apply, then this function should return `TEST{}(arg)`, where `TEST` is the template parameter. However, this is less important than step D above in excluding invalid type combinations with `enable_if`, since this only excludes them at run-time, and they need to be excluded by step D at compile-time in order to avoid unresolved references or invalid instantiations. For example: ```c++ template