1 /*
2 * Copyright 2018 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8 #include "tools/skqp/src/skqp.h"
9
10 #include "gm/gm.h"
11
12 #include "include/core/SkGraphics.h"
13 #include "include/core/SkStream.h"
14 #include "include/core/SkSurface.h"
15 #include "include/gpu/GrContextOptions.h"
16 #include "include/gpu/GrDirectContext.h"
17 #include "src/core/SkFontMgrPriv.h"
18 #include "src/core/SkOSFile.h"
19 #include "src/utils/SkOSPath.h"
20 #include "tests/Test.h"
21 #include "tests/TestHarness.h"
22 #include "tools/Resources.h"
23 #include "tools/fonts/TestFontMgr.h"
24 #ifdef SK_GL
25 #include "tools/gpu/gl/GLTestContext.h"
26 #endif
27 #ifdef SK_VULKAN
28 #include "tools/gpu/vk/VkTestContext.h"
29 #endif
30
31 #ifdef SK_BUILD_FOR_ANDROID
32 #include <sys/system_properties.h>
33 #endif
34
35 #include <algorithm>
36 #include <regex>
37
38 static constexpr char kUnitTestReportPath[] = "unit_tests.txt";
39 static constexpr char kUnitTestsPath[] = "skqp/unittests.txt";
40
41 // Kind of like Python's readlines(), but without any allocation.
42 // Calls `lineFn` on each line.
read_lines(const void * data,size_t size,const std::function<void (std::string_view)> & lineFn)43 static void read_lines(const void* data,
44 size_t size,
45 const std::function<void(std::string_view)>& lineFn) {
46 const char* start = (const char*)data;
47 const char* end = start + size;
48 const char* ptr = start;
49 while (ptr < end) {
50 while (*ptr++ != '\n' && ptr < end) {}
51 size_t len = ptr - start;
52 lineFn(std::string_view(start, len));
53 start = ptr;
54 }
55 }
56
57 namespace {
58
59 // Parses the contents of the `skqp/unittests.txt` file.
60 // Each line in the exclusion list is a regular expression that matches against test names.
61 // Matches indicate tests that should be excluded. Lines may start with # to indicate a comment.
62 class ExclusionList {
63 public:
ExclusionList()64 ExclusionList() {}
65
66 void initialize(SkQPAssetManager* assetManager, sk_sp<SkData> dat, int enforcedAndroidAPILevel);
67
isExcluded(const std::string & name) const68 bool isExcluded(const std::string& name) const {
69 for (const auto& entry : fEntries) {
70 if (std::regex_match(name, entry.regexPattern)) {
71 return fEnforcedAndroidAPILevel < entry.excludeUntilAndroidAPILevel;
72 }
73 }
74 return false;
75 }
76
77 private:
78 int fEnforcedAndroidAPILevel;
79
80 struct ExclusionEntry {
81 std::regex regexPattern;
82 int excludeUntilAndroidAPILevel;
83 };
84
85 std::vector<ExclusionEntry> fEntries;
86 };
87 }
88
89 // Returns a list of every unit test to be run.
get_unit_tests(const ExclusionList & exclusionList)90 static std::vector<SkQP::UnitTest> get_unit_tests(const ExclusionList& exclusionList) {
91 std::vector<SkQP::UnitTest> unitTests;
92 for (const skiatest::Test& test : skiatest::TestRegistry::Range()) {
93 if (!test.fNeedsGpu) {
94 continue;
95 }
96 if (exclusionList.isExcluded(test.fName)) {
97 continue;
98 }
99 unitTests.push_back(&test);
100 }
101 auto lt = [](SkQP::UnitTest u, SkQP::UnitTest v) { return strcmp(u->fName, v->fName) < 0; };
102 std::sort(unitTests.begin(), unitTests.end(), lt);
103 return unitTests;
104 }
105
106 // Returns a list of every SkSL error test to be run.
get_sksl_error_tests(SkQPAssetManager * assetManager,const ExclusionList & exclusionList)107 static std::vector<SkQP::SkSLErrorTest> get_sksl_error_tests(SkQPAssetManager* assetManager,
108 const ExclusionList& exclusionList) {
109 std::vector<SkQP::SkSLErrorTest> skslErrorTests;
110
111 auto iterateFn = [&](const char* directory, const char* extension) {
112 std::vector<std::string> paths = assetManager->iterateDir(directory, extension);
113 for (const std::string& path : paths) {
114 SkString name = SkOSPath::Basename(path.c_str());
115 if (exclusionList.isExcluded(name.c_str())) {
116 continue;
117 }
118 sk_sp<SkData> shaderText = GetResourceAsData(path.c_str());
119 if (!shaderText) {
120 continue;
121 }
122 skslErrorTests.push_back({
123 name.c_str(),
124 std::string(static_cast<const char*>(shaderText->data()), shaderText->size())
125 });
126 }
127 };
128
129 // Android only supports runtime shaders, not fragment shaders, color filters or blenders.
130 iterateFn("sksl/errors/", ".rts");
131 iterateFn("sksl/runtime_errors/", ".rts");
132
133 auto lt = [](const SkQP::SkSLErrorTest& a, const SkQP::SkSLErrorTest& b) {
134 return a.name < b.name;
135 };
136 std::sort(skslErrorTests.begin(), skslErrorTests.end(), lt);
137 return skslErrorTests;
138 }
139
initialize(SkQPAssetManager * assetManager,sk_sp<SkData> dat,int enforcedAndroidAPILevel)140 void ExclusionList::initialize(SkQPAssetManager* assetManager,
141 sk_sp<SkData> dat,
142 int enforcedAndroidAPILevel) {
143 fEnforcedAndroidAPILevel = enforcedAndroidAPILevel;
144 fEntries = {};
145
146 //TODO: explore refactoring this code to collect the test lists only once in SkQP::init
147 ExclusionList noExclusions;
148 const std::vector<SkQP::UnitTest> unitTestList = get_unit_tests(noExclusions);
149 const std::vector<SkQP::SkSLErrorTest> skslTestList = get_sksl_error_tests(assetManager,
150 noExclusions);
151
152 // function to check whether or not the provided regex matches an existing test
153 auto testExists = [&unitTestList, &skslTestList](const std::regex& exclusionRegex) {
154 for (const auto& test : unitTestList) {
155 if (std::regex_match(std::string(test->fName), exclusionRegex)) {
156 return true;
157 }
158 }
159 for (const auto& test : skslTestList) {
160 if (std::regex_match(test.name, exclusionRegex)) {
161 return true;
162 }
163 }
164 return false;
165 };
166
167 read_lines(dat->data(), dat->size(), [this, &testExists](std::string_view line) {
168 if (!line.empty() && line.back() == '\n') {
169 // Strip line endings.
170 line.remove_suffix(1);
171 }
172 // Only add non-empty strings, and ignore comments.
173 if (line.empty() || line.front() == '#') {
174 return;
175 }
176
177 std::string_view testName = line;
178 int excludeUntilAndroidAPILevel = fEnforcedAndroidAPILevel + 1;
179
180 // Check to see if the test has a min Android API level defined
181 auto commaLocation = line.find_first_of(',');
182 if (commaLocation != std::string::npos) {
183 testName = line.substr(0, commaLocation);
184 std::string apiString(line.substr(commaLocation + 1));
185 excludeUntilAndroidAPILevel = std::stoi(apiString);
186 }
187
188 const std::string exclusionString(testName);
189 const std::regex exclusionRegex(exclusionString);
190
191 // Throw an error if there are no unit or sksl tests that match the exclusion
192 if (!testExists(exclusionRegex)) {
193 SK_ABORT("Exclusion list contains tests not found in the test registry: %s",
194 exclusionString.c_str());
195 }
196
197 fEntries.push_back({exclusionRegex, excludeUntilAndroidAPILevel});
198 });
199 }
200
make_test_context(SkQP::SkiaBackend backend)201 static std::unique_ptr<sk_gpu_test::TestContext> make_test_context(SkQP::SkiaBackend backend) {
202 using U = std::unique_ptr<sk_gpu_test::TestContext>;
203 switch (backend) {
204 // TODO(halcanary): Fuchsia will have SK_SUPPORT_GPU and SK_VULKAN, but *not* SK_GL.
205 #ifdef SK_GL
206 case SkQP::SkiaBackend::kGL:
207 return U(sk_gpu_test::CreatePlatformGLTestContext(kGL_GrGLStandard, nullptr));
208 case SkQP::SkiaBackend::kGLES:
209 return U(sk_gpu_test::CreatePlatformGLTestContext(kGLES_GrGLStandard, nullptr));
210 #endif
211 #ifdef SK_VULKAN
212 case SkQP::SkiaBackend::kVulkan:
213 return U(sk_gpu_test::CreatePlatformVkTestContext(nullptr));
214 #endif
215 default:
216 return nullptr;
217 }
218 }
219
context_options(skiagm::GM * gm=nullptr)220 static GrContextOptions context_options(skiagm::GM* gm = nullptr) {
221 GrContextOptions grContextOptions;
222 grContextOptions.fAllowPathMaskCaching = true;
223 grContextOptions.fDisableDriverCorrectnessWorkarounds = true;
224 if (gm) {
225 gm->modifyGrContextOptions(&grContextOptions);
226 }
227 return grContextOptions;
228 }
229
get_backends()230 static std::vector<SkQP::SkiaBackend> get_backends() {
231 std::vector<SkQP::SkiaBackend> result;
232 SkQP::SkiaBackend backends[] = {
233 #ifdef SK_GL
234 #ifndef SK_BUILD_FOR_ANDROID
235 SkQP::SkiaBackend::kGL, // Used for testing on desktop machines.
236 #endif
237 SkQP::SkiaBackend::kGLES,
238 #endif // SK_GL
239 #ifdef SK_VULKAN
240 SkQP::SkiaBackend::kVulkan,
241 #endif
242 };
243 for (SkQP::SkiaBackend backend : backends) {
244 std::unique_ptr<sk_gpu_test::TestContext> testCtx = make_test_context(backend);
245 if (testCtx) {
246 testCtx->makeCurrent();
247 if (nullptr != testCtx->makeContext(context_options())) {
248 result.push_back(backend);
249 }
250 }
251 }
252 SkASSERT_RELEASE(result.size() > 0);
253 return result;
254 }
255
print_backend_info(const char * dstPath,const std::vector<SkQP::SkiaBackend> & backends)256 static void print_backend_info(const char* dstPath,
257 const std::vector<SkQP::SkiaBackend>& backends) {
258 #ifdef SK_ENABLE_DUMP_GPU
259 SkFILEWStream out(dstPath);
260 out.writeText("[\n");
261 for (SkQP::SkiaBackend backend : backends) {
262 if (std::unique_ptr<sk_gpu_test::TestContext> testCtx = make_test_context(backend)) {
263 testCtx->makeCurrent();
264 if (sk_sp<GrDirectContext> ctx = testCtx->makeContext(context_options())) {
265 SkString info = ctx->dump();
266 // remove null
267 out.write(info.c_str(), info.size());
268 out.writeText(",\n");
269 }
270 }
271 }
272 out.writeText("]\n");
273 #endif
274 }
275
276 ////////////////////////////////////////////////////////////////////////////////
277
CurrentTestHarness()278 TestHarness CurrentTestHarness() {
279 return TestHarness::kSkQP;
280 }
281
282 ////////////////////////////////////////////////////////////////////////////////
283
GetUnitTestName(SkQP::UnitTest t)284 const char* SkQP::GetUnitTestName(SkQP::UnitTest t) { return t->fName; }
285
SkQP()286 SkQP::SkQP() {}
287
~SkQP()288 SkQP::~SkQP() {}
289
init(SkQPAssetManager * assetManager,const char * reportDirectory)290 void SkQP::init(SkQPAssetManager* assetManager, const char* reportDirectory) {
291 SkASSERT_RELEASE(assetManager);
292 fReportDirectory = reportDirectory;
293
294 SkGraphics::Init();
295 gSkFontMgr_DefaultFactory = &ToolUtils::MakePortableFontMgr;
296
297 int minAndroidAPILevel = 0;
298 #ifdef SK_BUILD_FOR_ANDROID
299 char firstAPIVersionStr[PROP_VALUE_MAX];
300 int strLength = __system_property_get("ro.product.first_api_level", firstAPIVersionStr);
301 // Defaults to zero since most checks care if it is greater than a specific value. So this will
302 // just default to it being less.
303 minAndroidAPILevel = (strLength == 0) ? 0 : atoi(firstAPIVersionStr);
304 #endif
305
306 // Load the exclusion list `skqp/unittests.txt`, if it exists.
307 // The list is checked in at platform_tools/android/apps/skqp/src/main/assets/skqp/unittests.txt
308 ExclusionList exclusionList;
309 if (sk_sp<SkData> dat = assetManager->open(kUnitTestsPath)) {
310 exclusionList.initialize(assetManager, dat, minAndroidAPILevel);
311 }
312
313 fUnitTests = get_unit_tests(exclusionList);
314 fSkSLErrorTests = get_sksl_error_tests(assetManager, exclusionList);
315 fSupportedBackends = get_backends();
316
317 print_backend_info((fReportDirectory + "/grdump.txt").c_str(), fSupportedBackends);
318 }
319
executeTest(SkQP::UnitTest test)320 std::vector<std::string> SkQP::executeTest(SkQP::UnitTest test) {
321 struct : public skiatest::Reporter {
322 std::vector<std::string> fErrors;
323 void reportFailed(const skiatest::Failure& failure) override {
324 SkString desc = failure.toString();
325 fErrors.push_back(std::string(desc.c_str(), desc.size()));
326 }
327 } r;
328 GrContextOptions options;
329 options.fDisableDriverCorrectnessWorkarounds = true;
330 if (test->fContextOptionsProc) {
331 test->fContextOptionsProc(&options);
332 }
333 test->fProc(&r, options);
334 fTestResults.push_back(TestResult{test->fName, r.fErrors});
335 return r.fErrors;
336 }
337
338 ////////////////////////////////////////////////////////////////////////////////
339
340 template <typename T>
write(SkWStream * wStream,const T & text)341 inline void write(SkWStream* wStream, const T& text) {
342 wStream->write(text.c_str(), text.size());
343 }
344
makeReport()345 void SkQP::makeReport() {
346 if (!sk_isdir(fReportDirectory.c_str())) {
347 SkDebugf("Report destination does not exist: '%s'\n", fReportDirectory.c_str());
348 return;
349 }
350 SkFILEWStream report(SkOSPath::Join(fReportDirectory.c_str(), kUnitTestReportPath).c_str());
351 SkASSERT_RELEASE(report.isValid());
352 for (const SkQP::TestResult& result : fTestResults) {
353 report.writeText(result.name.c_str());
354 if (result.errors.empty()) {
355 report.writeText(" PASSED\n* * *\n");
356 } else {
357 write(&report, SkStringPrintf(" FAILED (%zu errors)\n", result.errors.size()));
358 for (const std::string& err : result.errors) {
359 write(&report, err);
360 report.newline();
361 }
362 report.writeText("* * *\n");
363 }
364 }
365 }
366