• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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