Flutter Engine
The Flutter Engine
SkSLErrorTest.cpp
Go to the documentation of this file.
1/*
2 * Copyright 2022 Google LLC
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
12#include "src/core/SkOSFile.h"
13#include "src/core/SkTHash.h"
17#include "src/sksl/SkSLUtil.h"
18#include "src/sksl/ir/SkSLProgram.h" // IWYU pragma: keep
19#include "src/utils/SkOSPath.h"
20#include "tests/Test.h"
21#include "tools/Resources.h"
22
23#include <cstring>
24#include <functional>
25#include <memory>
26#include <sstream>
27#include <string>
28#include <string_view>
29#include <utility>
30#include <vector>
31
32using namespace skia_private;
33
34static std::vector<std::string> get_expected_errors(const char* shaderString) {
35 // Error expectations are embedded in the source with a special *%%* marker, like so:
36 //
37 // /*%%*
38 // expected 'foo', but found 'bar'
39 // 'baz' is not a valid identifier
40 // *%%*/
41 //
42 // Extract them from the shader text.
43 std::vector<std::string> expectedErrors;
44 constexpr char kExpectedErrorsStart[] = "/*%%*";
45 constexpr char kExpectedErrorsEnd[] = "*%%*/";
46 if (const char* startPtr = strstr(shaderString, kExpectedErrorsStart)) {
47 startPtr += strlen(kExpectedErrorsStart);
48 if (const char* endPtr = strstr(startPtr, kExpectedErrorsEnd)) {
49 // Store the text between these delimiters in an array of expected errors.
50 std::stringstream stream{std::string{startPtr, endPtr}};
51 while (stream.good()) {
52 expectedErrors.push_back({});
53 std::getline(stream, expectedErrors.back(), '\n');
54 if (expectedErrors.back().empty()) {
55 expectedErrors.pop_back();
56 }
57 }
58 }
59 }
60
61 return expectedErrors;
62}
63
65 const char* testFile,
66 const std::vector<std::string>& expectedErrors,
67 std::string reportedErrors) {
68 // Verify that the SkSL compiler actually emitted the expected error messages.
69 // The list of expectations isn't necessarily exhaustive, though.
70 std::string originalErrors = reportedErrors;
71 bool reportOriginalErrors = false;
72 for (const std::string& expectedError : expectedErrors) {
73 // If this error wasn't reported, trigger an error.
74 size_t pos = reportedErrors.find(expectedError.c_str());
75 if (pos == std::string::npos) {
76 ERRORF(r, "%s: Expected an error that wasn't reported:\n%s\n",
77 SkOSPath::Basename(testFile).c_str(), expectedError.c_str());
78 reportOriginalErrors = true;
79 } else {
80 // We found the error that we expected to have. Remove that error from our report, and
81 // everything preceding it as well. This ensures that we don't match the same error
82 // twice, and that errors are reported in the order we expect.
83 reportedErrors.erase(0, pos + expectedError.size());
84 }
85 }
86
87 if (reportOriginalErrors) {
88 ERRORF(r, "%s: The following errors were reported:\n%s\n",
89 SkOSPath::Basename(testFile).c_str(), originalErrors.c_str());
90 }
91}
92
93static void test_expect_fail(skiatest::Reporter* r, const char* testFile, SkSL::ProgramKind kind) {
94 // In a size-optimized build, there are a handful of errors which report differently, or not at
95 // all. Skip over those tests.
96 static const SkNoDestructor<THashSet<std::string_view>> kTestsToSkip{{
97 // These are tests that have been deleted, but which may still show up (and fail) on tasks,
98 // because the resources directory isn't properly cleaned up. (b/40044088)
99 "sksl/errors/InvalidBackendBindingFlagsGL.sksl",
100 "sksl/errors/InvalidThreadgroupRTS.rts",
101 "sksl/errors/LastFragColorWithoutCaps.sksl",
102 "sksl/errors/MeshFragmentWithShader.mfrag",
103 "sksl/errors/MeshFragmentWithBlender.mfrag",
104 "sksl/errors/MeshFragmentWithColorFilter.mfrag",
105 "sksl/errors/StaticIfTest.sksl",
106 "sksl/errors/StaticSwitchConditionalBreak.sksl",
107 "sksl/errors/StaticSwitchTest.sksl",
108 "sksl/errors/StaticSwitchWithConditionalBreak.sksl",
109 "sksl/errors/StaticSwitchWithConditionalContinue.sksl",
110 "sksl/errors/StaticSwitchWithConditionalReturn.sksl",
111
112 "sksl/errors/ComputeUniform.compute",
113 "sksl/errors/DuplicateBinding.compute",
114 "sksl/errors/InvalidThreadgroupCompute.compute",
115 "sksl/errors/UnspecifiedBinding.compute",
116
117 "sksl/runtime_errors/ReservedNameISampler2D.rts",
118
119#ifdef SK_ENABLE_OPTIMIZE_SIZE
120 "sksl/errors/ArrayInlinedIndexOutOfRange.sksl",
121 "sksl/errors/MatrixInlinedIndexOutOfRange.sksl",
122 "sksl/errors/OverflowInlinedLiteral.sksl",
123 "sksl/errors/VectorInlinedIndexOutOfRange.sksl",
124#endif
125 }};
126 if (kTestsToSkip->contains(testFile)) {
127 INFOF(r, "%s: skipped in SK_ENABLE_OPTIMIZE_SIZE mode", testFile);
128 return;
129 }
130
131 sk_sp<SkData> shaderData = GetResourceAsData(testFile);
132 if (!shaderData) {
133 ERRORF(r, "%s: Unable to load file", SkOSPath::Basename(testFile).c_str());
134 return;
135 }
136
137 std::string shaderString{reinterpret_cast<const char*>(shaderData->bytes()),
138 shaderData->size()};
139
140 std::vector<std::string> expectedErrors = get_expected_errors(shaderString.c_str());
141
142 // Compile the code.
145 std::unique_ptr<SkSL::Program> program = compiler.convertProgram(kind, std::move(shaderString),
146 settings);
147
148 // If the code actually generated a working program, we've already failed.
149 if (program) {
150 ERRORF(r, "%s: Expected failure, but compiled successfully",
151 SkOSPath::Basename(testFile).c_str());
152 return;
153 }
154
155 check_expected_errors(r, testFile, expectedErrors, compiler.errorText());
156}
157
158static void iterate_dir(const char* directory,
159 const char* extension,
160 const std::function<void(const char*)>& run) {
161 SkString resourceDirectory = GetResourcePath(directory);
162 SkOSFile::Iter iter(resourceDirectory.c_str(), extension);
164
165 while (iter.next(&name, /*getDir=*/false)) {
166 SkString path(SkOSPath::Join(directory, name.c_str()));
167 run(path.c_str());
168 }
169}
170
171DEF_TEST(SkSLErrorTest, r) {
172 iterate_dir("sksl/errors/", ".sksl", [&](const char* path) {
174 });
175}
176
177DEF_TEST(SkSLComputeErrorTest, r) {
178 iterate_dir("sksl/errors/", ".compute", [&](const char* path) {
180 });
181}
182
183DEF_TEST(SkSLMeshVertexErrorTest, r) {
184 iterate_dir("sksl/errors/", ".mvert", [&](const char* path) {
186 });
187}
188
189DEF_TEST(SkSLMeshFragmentErrorTest, r) {
190 iterate_dir("sksl/errors/", ".mfrag", [&](const char* path) {
192 });
193}
194
195DEF_TEST(SkSLRuntimeShaderErrorTest, r) {
196 iterate_dir("sksl/errors/", ".rts", [&](const char* path) {
198 });
199 iterate_dir("sksl/runtime_errors/", ".rts", [&](const char* path) {
201 });
202}
203
204DEF_TEST(SkSLPrivateRuntimeShaderErrorTest, r) {
205 iterate_dir("sksl/errors/", ".privrts", [&](const char* path) {
207 });
208 iterate_dir("sksl/runtime_errors/", ".privrts", [&](const char* path) {
210 });
211}
212
213DEF_TEST(SkSLRuntimeColorFilterErrorTest, r) {
214 iterate_dir("sksl/runtime_errors/", ".rtcf", [&](const char* path) {
216 });
217}
218
219DEF_TEST(SkSLRuntimeBlenderErrorTest, r) {
220 iterate_dir("sksl/runtime_errors/", ".rtb", [&](const char* path) {
222 });
223}
SkPoint pos
sk_sp< SkData > GetResourceAsData(const char *resource)
Definition: Resources.cpp:42
SkString GetResourcePath(const char *resource)
Definition: Resources.cpp:23
static void iterate_dir(const char *directory, const char *extension, const std::function< void(const char *)> &run)
static void check_expected_errors(skiatest::Reporter *r, const char *testFile, const std::vector< std::string > &expectedErrors, std::string reportedErrors)
static void test_expect_fail(skiatest::Reporter *r, const char *testFile, SkSL::ProgramKind kind)
static std::vector< std::string > get_expected_errors(const char *shaderString)
DEF_TEST(SkSLErrorTest, r)
#define INFOF(REPORTER,...)
Definition: Test.h:298
#define ERRORF(r,...)
Definition: Test.h:293
const uint8_t * bytes() const
Definition: SkData.h:43
size_t size() const
Definition: SkData.h:30
SK_SPI bool next(SkString *name, bool getDir=false)
static SkString Join(const char *rootPath, const char *relativePath)
Definition: SkOSPath.cpp:14
static SkString Basename(const char *fullPath)
Definition: SkOSPath.cpp:23
const char * c_str() const
Definition: SkString.h:133
Dart_NativeFunction function
Definition: fuchsia.cc:51
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot The VM snapshot data that will be memory mapped as read only SnapshotAssetPath must be present isolate snapshot The isolate snapshot data that will be memory mapped as read only SnapshotAssetPath must be present cache dir path
Definition: switches.h:57
DEF_SWITCHES_START aot vmservice shared library name
Definition: switches.h:32
compiler
Definition: malisc.py:17
Definition: run.py:1
def run(cmd)
Definition: run.py:14