xref: /aosp_15_r20/external/skia/tests/SkSLErrorTest.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
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 
8 #include "include/core/SkData.h"
9 #include "include/core/SkRefCnt.h"
10 #include "include/core/SkString.h"
11 #include "src/base/SkNoDestructor.h"
12 #include "src/core/SkOSFile.h"
13 #include "src/core/SkTHash.h"
14 #include "src/sksl/SkSLCompiler.h"
15 #include "src/sksl/SkSLProgramKind.h"
16 #include "src/sksl/SkSLProgramSettings.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 
32 using namespace skia_private;
33 
get_expected_errors(const char * shaderString)34 static 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 
check_expected_errors(skiatest::Reporter * r,const char * testFile,const std::vector<std::string> & expectedErrors,std::string reportedErrors)64 static void check_expected_errors(skiatest::Reporter* r,
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 
test_expect_fail(skiatest::Reporter * r,const char * testFile,SkSL::ProgramKind kind)93 static 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         "sksl/runtime_errors/ProgramTooLarge_BlocklessLoops.rts",
119         "sksl/runtime_errors/ProgramTooLarge_Extreme.rts",
120         "sksl/runtime_errors/ProgramTooLarge_FlatLoop.rts",
121         "sksl/runtime_errors/ProgramTooLarge_Functions.rts",
122         "sksl/runtime_errors/ProgramTooLarge_NestedLoops.rts",
123         "sksl/runtime_errors/ProgramTooLarge_SplitLoops.rts",
124 
125 #ifdef SK_ENABLE_OPTIMIZE_SIZE
126         "sksl/errors/ArrayInlinedIndexOutOfRange.sksl",
127         "sksl/errors/MatrixInlinedIndexOutOfRange.sksl",
128         "sksl/errors/OverflowInlinedLiteral.sksl",
129         "sksl/errors/VectorInlinedIndexOutOfRange.sksl",
130 #endif
131     }};
132     if (kTestsToSkip->contains(testFile)) {
133         INFOF(r, "%s: skipped in SK_ENABLE_OPTIMIZE_SIZE mode", testFile);
134         return;
135     }
136 
137     sk_sp<SkData> shaderData = GetResourceAsData(testFile);
138     if (!shaderData) {
139         ERRORF(r, "%s: Unable to load file", SkOSPath::Basename(testFile).c_str());
140         return;
141     }
142 
143     std::string shaderString{reinterpret_cast<const char*>(shaderData->bytes()),
144                              shaderData->size()};
145 
146     std::vector<std::string> expectedErrors = get_expected_errors(shaderString.c_str());
147 
148     // Compile the code.
149     SkSL::Compiler compiler;
150     SkSL::ProgramSettings settings;
151     std::unique_ptr<SkSL::Program> program = compiler.convertProgram(kind, std::move(shaderString),
152                                                                      settings);
153 
154     // If the code actually generated a working program, we've already failed.
155     if (program) {
156         ERRORF(r, "%s: Expected failure, but compiled successfully",
157                   SkOSPath::Basename(testFile).c_str());
158         return;
159     }
160 
161     check_expected_errors(r, testFile, expectedErrors, compiler.errorText());
162 }
163 
iterate_dir(const char * directory,const char * extension,const std::function<void (const char *)> & run)164 static void iterate_dir(const char* directory,
165                         const char* extension,
166                         const std::function<void(const char*)>& run) {
167     SkString resourceDirectory = GetResourcePath(directory);
168     SkOSFile::Iter iter(resourceDirectory.c_str(), extension);
169     SkString name;
170 
171     while (iter.next(&name, /*getDir=*/false)) {
172         SkString path(SkOSPath::Join(directory, name.c_str()));
173         run(path.c_str());
174     }
175 }
176 
DEF_TEST(SkSLErrorTest,r)177 DEF_TEST(SkSLErrorTest, r) {
178     iterate_dir("sksl/errors/", ".sksl", [&](const char* path) {
179         test_expect_fail(r, path, SkSL::ProgramKind::kFragment);
180     });
181 }
182 
DEF_TEST(SkSLComputeErrorTest,r)183 DEF_TEST(SkSLComputeErrorTest, r) {
184     iterate_dir("sksl/errors/", ".compute", [&](const char* path) {
185         test_expect_fail(r, path, SkSL::ProgramKind::kCompute);
186     });
187 }
188 
DEF_TEST(SkSLMeshVertexErrorTest,r)189 DEF_TEST(SkSLMeshVertexErrorTest, r) {
190     iterate_dir("sksl/errors/", ".mvert", [&](const char* path) {
191         test_expect_fail(r, path, SkSL::ProgramKind::kMeshVertex);
192     });
193 }
194 
DEF_TEST(SkSLMeshFragmentErrorTest,r)195 DEF_TEST(SkSLMeshFragmentErrorTest, r) {
196     iterate_dir("sksl/errors/", ".mfrag", [&](const char* path) {
197         test_expect_fail(r, path, SkSL::ProgramKind::kMeshFragment);
198     });
199 }
200 
DEF_TEST(SkSLRuntimeShaderErrorTest,r)201 DEF_TEST(SkSLRuntimeShaderErrorTest, r) {
202     iterate_dir("sksl/errors/", ".rts", [&](const char* path) {
203         test_expect_fail(r, path, SkSL::ProgramKind::kRuntimeShader);
204     });
205     iterate_dir("sksl/runtime_errors/", ".rts", [&](const char* path) {
206         test_expect_fail(r, path, SkSL::ProgramKind::kRuntimeShader);
207     });
208 }
209 
DEF_TEST(SkSLPrivateRuntimeShaderErrorTest,r)210 DEF_TEST(SkSLPrivateRuntimeShaderErrorTest, r) {
211     iterate_dir("sksl/errors/", ".privrts", [&](const char* path) {
212         test_expect_fail(r, path, SkSL::ProgramKind::kPrivateRuntimeShader);
213     });
214     iterate_dir("sksl/runtime_errors/", ".privrts", [&](const char* path) {
215         test_expect_fail(r, path, SkSL::ProgramKind::kPrivateRuntimeShader);
216     });
217 }
218 
DEF_TEST(SkSLRuntimeColorFilterErrorTest,r)219 DEF_TEST(SkSLRuntimeColorFilterErrorTest, r) {
220     iterate_dir("sksl/runtime_errors/", ".rtcf", [&](const char* path) {
221         test_expect_fail(r, path, SkSL::ProgramKind::kRuntimeColorFilter);
222     });
223 }
224 
DEF_TEST(SkSLRuntimeBlenderErrorTest,r)225 DEF_TEST(SkSLRuntimeBlenderErrorTest, r) {
226     iterate_dir("sksl/runtime_errors/", ".rtb", [&](const char* path) {
227         test_expect_fail(r, path, SkSL::ProgramKind::kRuntimeBlender);
228     });
229 }
230