Grok 12.0.1
hwy_gtest.h
Go to the documentation of this file.
1// Copyright 2021 Google LLC
2// SPDX-License-Identifier: Apache-2.0
3//
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8// http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16#ifndef HWY_TESTS_HWY_GTEST_H_
17#define HWY_TESTS_HWY_GTEST_H_
18
19// Adapter/replacement for GUnit to run tests for all targets.
20
21#include "hwy/base.h"
22
23// Allow opting out of GUnit.
24#ifndef HWY_TEST_STANDALONE
25// GUnit and its dependencies no longer support MSVC.
26#if HWY_COMPILER_MSVC
27#define HWY_TEST_STANDALONE 1
28#else
29#define HWY_TEST_STANDALONE 0
30#endif // HWY_COMPILER_MSVC
31#endif // HWY_TEST_STANDALONE
32
33#include <stdint.h>
34
35#include <string>
36#include <tuple>
37
38#if !HWY_TEST_STANDALONE
39#include "gtest/gtest.h" // IWYU pragma: export
40#endif
41#include "hwy/highway.h"
42
43namespace hwy {
44
45#if !HWY_TEST_STANDALONE
46
47// googletest before 1.10 didn't define INSTANTIATE_TEST_SUITE_P() but instead
48// used INSTANTIATE_TEST_CASE_P which is now deprecated.
49#ifdef INSTANTIATE_TEST_SUITE_P
50#define HWY_GTEST_INSTANTIATE_TEST_SUITE_P INSTANTIATE_TEST_SUITE_P
51#else
52#define HWY_GTEST_INSTANTIATE_TEST_SUITE_P INSTANTIATE_TEST_CASE_P
53#endif
54
55// Helper class to run parametric tests using the hwy target as parameter. To
56// use this define the following in your test:
57// class MyTestSuite : public TestWithParamTarget {
58// ...
59// };
60// HWY_TARGET_INSTANTIATE_TEST_SUITE_P(MyTestSuite);
61// TEST_P(MyTestSuite, MyTest) { ... }
62class TestWithParamTarget : public testing::TestWithParam<int64_t> {
63 protected:
64 void SetUp() override { SetSupportedTargetsForTest(GetParam()); }
65
66 void TearDown() override {
67 // Check that the parametric test calls SupportedTargets() when the source
68 // was compiled with more than one target. In the single-target case only
69 // static dispatch will be used anyway.
70#if (HWY_TARGETS & (HWY_TARGETS - 1)) != 0
71 EXPECT_TRUE(GetChosenTarget().IsInitialized())
72 << "This hwy target parametric test doesn't use dynamic-dispatch and "
73 "doesn't need to be parametric.";
74#endif
76 }
77};
78
79// Function to convert the test parameter of a TestWithParamTarget for
80// displaying it in the gtest test name.
81static inline std::string TestParamTargetName(
82 const testing::TestParamInfo<int64_t>& info) {
83 return TargetName(info.param);
84}
85
86#define HWY_TARGET_INSTANTIATE_TEST_SUITE_P(suite) \
87 HWY_GTEST_INSTANTIATE_TEST_SUITE_P( \
88 suite##Group, suite, \
89 testing::ValuesIn(::hwy::SupportedAndGeneratedTargets()), \
90 ::hwy::TestParamTargetName)
91
92// Helper class similar to TestWithParamTarget to run parametric tests that
93// depend on the target and another parametric test. If you need to use multiple
94// extra parameters use a std::tuple<> of them and ::testing::Generate(...) as
95// the generator. To use this class define the following in your test:
96// class MyTestSuite : public TestWithParamTargetT<int> {
97// ...
98// };
99// HWY_TARGET_INSTANTIATE_TEST_SUITE_P_T(MyTestSuite, ::testing::Range(0, 9));
100// TEST_P(MyTestSuite, MyTest) { ... GetParam() .... }
101template <typename T>
103 : public ::testing::TestWithParam<std::tuple<int64_t, T>> {
104 public:
105 // Expose the parametric type here so it can be used by the
106 // HWY_TARGET_INSTANTIATE_TEST_SUITE_P_T macro.
107 using HwyParamType = T;
108
109 protected:
110 void SetUp() override {
111 SetSupportedTargetsForTest(std::get<0>(
112 ::testing::TestWithParam<std::tuple<int64_t, T>>::GetParam()));
113 }
114
115 void TearDown() override {
116 // Check that the parametric test calls SupportedTargets() when the source
117 // was compiled with more than one target. In the single-target case only
118 // static dispatch will be used anyway.
119#if (HWY_TARGETS & (HWY_TARGETS - 1)) != 0
120 EXPECT_TRUE(GetChosenTarget().IsInitialized())
121 << "This hwy target parametric test doesn't use dynamic-dispatch and "
122 "doesn't need to be parametric.";
123#endif
125 }
126
128 return std::get<1>(
129 ::testing::TestWithParam<std::tuple<int64_t, T>>::GetParam());
130 }
131};
132
133template <typename T>
135 const testing::TestParamInfo<std::tuple<int64_t, T>>& info) {
136 return std::string(TargetName(std::get<0>(info.param))) + "_" +
137 ::testing::PrintToString(std::get<1>(info.param));
138}
139
140#define HWY_TARGET_INSTANTIATE_TEST_SUITE_P_T(suite, generator) \
141 HWY_GTEST_INSTANTIATE_TEST_SUITE_P( \
142 suite##Group, suite, \
143 ::testing::Combine( \
144 testing::ValuesIn(::hwy::SupportedAndGeneratedTargets()), \
145 generator), \
146 ::hwy::TestParamTargetNameAndT<suite::HwyParamType>)
147
148// Helper macro to export a function and define a test that tests it. This is
149// equivalent to do a HWY_EXPORT of a void(void) function and run it in a test:
150// class MyTestSuite : public TestWithParamTarget {
151// ...
152// };
153// HWY_TARGET_INSTANTIATE_TEST_SUITE_P(MyTestSuite);
154// HWY_EXPORT_AND_TEST_P(MyTestSuite, MyTest);
155#define HWY_EXPORT_AND_TEST_P(suite, func_name) \
156 HWY_EXPORT(func_name); \
157 TEST_P(suite, func_name) { HWY_DYNAMIC_DISPATCH(func_name)(); } \
158 static_assert(true, "For requiring trailing semicolon")
159
160#define HWY_EXPORT_AND_TEST_P_T(suite, func_name) \
161 HWY_EXPORT(func_name); \
162 TEST_P(suite, func_name) { HWY_DYNAMIC_DISPATCH(func_name)(GetParam()); } \
163 static_assert(true, "For requiring trailing semicolon")
164
165#define HWY_BEFORE_TEST(suite) \
166 class suite : public hwy::TestWithParamTarget {}; \
167 HWY_TARGET_INSTANTIATE_TEST_SUITE_P(suite); \
168 static_assert(true, "For requiring trailing semicolon")
169
170#define HWY_AFTER_TEST() static_assert(true, "For requiring trailing semicolon")
171
172#define HWY_TEST_MAIN() static_assert(true, "For requiring trailing semicolon")
173
174#else // HWY_TEST_STANDALONE
175
176// Cannot be a function, otherwise the HWY_EXPORT table defined here will not
177// be visible to HWY_DYNAMIC_DISPATCH.
178#define HWY_EXPORT_AND_TEST_P(suite, func_name) \
179 HWY_EXPORT(func_name); \
180 hwy::SetSupportedTargetsForTest(0); \
181 for (int64_t target : hwy::SupportedAndGeneratedTargets()) { \
182 hwy::SetSupportedTargetsForTest(target); \
183 fprintf(stderr, "=== %s for %s:\n", #func_name, hwy::TargetName(target)); \
184 HWY_DYNAMIC_DISPATCH(func_name)(); \
185 } \
186 /* Disable the mask after the test. */ \
187 hwy::SetSupportedTargetsForTest(0); \
188 static_assert(true, "For requiring trailing semicolon")
189
190// HWY_BEFORE_TEST may reside inside a namespace, but HWY_AFTER_TEST will define
191// a main() at namespace scope that wants to call into that namespace, so stash
192// the function address in a singleton defined in namespace hwy.
193using VoidFunc = void (*)(void);
194
195VoidFunc& GetRunAll() {
196 static VoidFunc func;
197 return func;
198}
199
200struct RegisterRunAll {
201 RegisterRunAll(VoidFunc func) { hwy::GetRunAll() = func; }
202};
203
204#define HWY_BEFORE_TEST(suite) \
205 void RunAll(); \
206 static hwy::RegisterRunAll HWY_CONCAT(reg_, suite)(&RunAll); \
207 void RunAll() { \
208 static_assert(true, "For requiring trailing semicolon")
209
210// Must be followed by semicolon, then a closing brace for ONE namespace.
211#define HWY_AFTER_TEST() \
212 } /* RunAll*/ \
213 } /* namespace */ \
214 int main(int /*argc*/, char** /*argv*/) { \
215 hwy::GetRunAll()(); \
216 fprintf(stderr, "Success.\n"); \
217 return 0
218
219// -------------------- Non-SIMD test cases:
220
221struct FuncAndName {
222 VoidFunc func;
223 const char* name;
224};
225
226// Singleton of registered tests to be run by HWY_TEST_MAIN
227std::vector<FuncAndName>& GetFuncAndNames() {
228 static std::vector<FuncAndName> vec;
229 return vec;
230}
231
232// For use by TEST; adds to the list.
233struct RegisterTest {
234 RegisterTest(VoidFunc func, const char* name) {
235 hwy::GetFuncAndNames().push_back({func, name});
236 }
237};
238
239// Registers a function to be called by `HWY_TEST_MAIN`. `suite` is unused.
240#define TEST(suite, func) \
241 void func(); \
242 static hwy::RegisterTest HWY_CONCAT(reg_, func)({&func, #func}); \
243 void func()
244
245// Expands to a main() that calls all TEST. Must reside at namespace scope.
246#define HWY_TEST_MAIN() \
247 int main() { \
248 for (const auto& func_and_name : hwy::GetFuncAndNames()) { \
249 fprintf(stderr, "=== %s:\n", func_and_name.name); \
250 func_and_name.func(); \
251 } \
252 fprintf(stderr, "Success.\n"); \
253 return 0; \
254 } \
255 static_assert(true, "For requiring trailing semicolon")
256
257#endif // HWY_TEST_STANDALONE
258
259} // namespace hwy
260
261#endif // HWY_TESTS_HWY_GTEST_H_
Definition hwy_gtest.h:103
void TearDown() override
Definition hwy_gtest.h:115
T GetParam()
Definition hwy_gtest.h:127
T HwyParamType
Definition hwy_gtest.h:107
void SetUp() override
Definition hwy_gtest.h:110
Definition hwy_gtest.h:62
void TearDown() override
Definition hwy_gtest.h:66
void SetUp() override
Definition hwy_gtest.h:64
Definition abort.h:8
static std::string TestParamTargetName(const testing::TestParamInfo< int64_t > &info)
Definition hwy_gtest.h:81
static HWY_MAYBE_UNUSED const char * TargetName(int64_t target)
Definition targets.h:85
std::string TestParamTargetNameAndT(const testing::TestParamInfo< std::tuple< int64_t, T > > &info)
Definition hwy_gtest.h:134
HWY_DLLEXPORT ChosenTarget & GetChosenTarget()
HWY_DLLEXPORT void SetSupportedTargetsForTest(int64_t targets)