• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 "include/private/SkSLProgramKind.h"
12 #include "src/core/SkOSFile.h"
13 #include "src/core/SkTHash.h"
14 #include "src/sksl/SkSLCompiler.h"
15 #include "src/sksl/SkSLProgramSettings.h"
16 #include "src/sksl/SkSLUtil.h"
17 #include "src/sksl/ir/SkSLProgram.h"  // IWYU pragma: keep
18 #include "src/utils/SkOSPath.h"
19 #include "tests/Test.h"
20 #include "tools/Resources.h"
21 
22 #include <cstddef>
23 #include <functional>
24 #include <initializer_list>
25 #include <memory>
26 #include <sstream>
27 #include <string>
28 #include <string_view>
29 #include <utility>
30 #include <vector>
31 
get_expected_errors(const char * shaderString)32 static std::vector<std::string> get_expected_errors(const char* shaderString) {
33     // Error expectations are embedded in the source with a special *%%* marker, like so:
34     //
35     //     /*%%*
36     //     expected 'foo', but found 'bar'
37     //     'baz' is not a valid identifier
38     //     *%%*/
39     //
40     // Extract them from the shader text.
41     std::vector<std::string> expectedErrors;
42     constexpr char kExpectedErrorsStart[] = "/*%%*";
43     constexpr char kExpectedErrorsEnd[] = "*%%*/";
44     if (const char* startPtr = strstr(shaderString, kExpectedErrorsStart)) {
45         startPtr += strlen(kExpectedErrorsStart);
46         if (const char* endPtr = strstr(startPtr, kExpectedErrorsEnd)) {
47             // Store the text between these delimiters in an array of expected errors.
48             std::stringstream stream{std::string{startPtr, endPtr}};
49             while (stream.good()) {
50                 expectedErrors.push_back({});
51                 std::getline(stream, expectedErrors.back(), '\n');
52                 if (expectedErrors.back().empty()) {
53                     expectedErrors.pop_back();
54                 }
55             }
56         }
57     }
58 
59     return expectedErrors;
60 }
61 
check_expected_errors(skiatest::Reporter * r,const char * testFile,const std::vector<std::string> & expectedErrors,std::string reportedErrors)62 static void check_expected_errors(skiatest::Reporter* r,
63                                   const char* testFile,
64                                   const std::vector<std::string>& expectedErrors,
65                                   std::string reportedErrors) {
66     // Verify that the SkSL compiler actually emitted the expected error messages.
67     // The list of expectations isn't necessarily exhaustive, though.
68     std::string originalErrors = reportedErrors;
69     bool reportOriginalErrors = false;
70     for (const std::string& expectedError : expectedErrors) {
71         // If this error wasn't reported, trigger an error.
72         size_t pos = reportedErrors.find(expectedError.c_str());
73         if (pos == std::string::npos) {
74             ERRORF(r, "%s: Expected an error that wasn't reported:\n%s\n",
75                    SkOSPath::Basename(testFile).c_str(), expectedError.c_str());
76             reportOriginalErrors = true;
77         } else {
78             // We found the error that we expected to have. Remove that error from our report, and
79             // everything preceding it as well. This ensures that we don't match the same error
80             // twice, and that errors are reported in the order we expect.
81             reportedErrors.erase(0, pos + expectedError.size());
82         }
83     }
84 
85     if (reportOriginalErrors) {
86         ERRORF(r, "%s: The following errors were reported:\n%s\n",
87                SkOSPath::Basename(testFile).c_str(), originalErrors.c_str());
88     }
89 }
90 
test_expect_fail(skiatest::Reporter * r,const char * testFile,SkSL::ProgramKind kind)91 static void test_expect_fail(skiatest::Reporter* r, const char* testFile, SkSL::ProgramKind kind) {
92     // In a size-optimized build, there are a handful of errors which report differently, or not at
93     // all. Skip over those tests.
94     static const auto* kTestsToSkip = new SkTHashSet<std::string_view>{
95         // These are tests that have been deleted, but which may still show up (and fail) on bots,
96         // because the resources directory isn't properly cleaned up. (skbug.com/12987)
97         "sksl/errors/InvalidThreadgroupRTS.rts",
98         "sksl/errors/StaticIfTest.sksl",
99         "sksl/errors/StaticSwitchConditionalBreak.sksl",
100         "sksl/errors/StaticSwitchTest.sksl",
101         "sksl/errors/StaticSwitchWithConditionalBreak.sksl",
102         "sksl/errors/StaticSwitchWithConditionalContinue.sksl",
103         "sksl/errors/StaticSwitchWithConditionalReturn.sksl",
104 
105         "sksl/errors/ComputeUniform.compute",
106         "sksl/errors/DuplicateBinding.compute",
107         "sksl/errors/InvalidThreadgroupCompute.compute",
108         "sksl/errors/UnspecifiedBinding.compute",
109 
110         "sksl/runtime_errors/ReservedNameISampler2D.rts",
111 
112 #ifdef SK_ENABLE_OPTIMIZE_SIZE
113         "sksl/errors/ArrayInlinedIndexOutOfRange.sksl",
114         "sksl/errors/MatrixInlinedIndexOutOfRange.sksl",
115         "sksl/errors/OverflowInlinedLiteral.sksl",
116         "sksl/errors/VectorInlinedIndexOutOfRange.sksl",
117 #endif
118     };
119     if (kTestsToSkip->contains(testFile)) {
120         INFOF(r, "%s: skipped in SK_ENABLE_OPTIMIZE_SIZE mode", testFile);
121         return;
122     }
123 
124     sk_sp<SkData> shaderData = GetResourceAsData(testFile);
125     if (!shaderData) {
126         ERRORF(r, "%s: Unable to load file", SkOSPath::Basename(testFile).c_str());
127         return;
128     }
129 
130     std::string shaderString{reinterpret_cast<const char*>(shaderData->bytes()),
131                              shaderData->size()};
132 
133     std::vector<std::string> expectedErrors = get_expected_errors(shaderString.c_str());
134 
135     // Compile the code.
136     SkSL::Compiler compiler(SkSL::ShaderCapsFactory::Standalone());
137     SkSL::ProgramSettings settings;
138     std::unique_ptr<SkSL::Program> program = compiler.convertProgram(kind, std::move(shaderString),
139                                                                      settings);
140 
141     // If the code actually generated a working program, we've already failed.
142     if (program) {
143         ERRORF(r, "%s: Expected failure, but compiled successfully",
144                   SkOSPath::Basename(testFile).c_str());
145         return;
146     }
147 
148     check_expected_errors(r, testFile, expectedErrors, compiler.errorText());
149 }
150 
iterate_dir(const char * directory,const char * extension,const std::function<void (const char *)> & run)151 static void iterate_dir(const char* directory,
152                         const char* extension,
153                         const std::function<void(const char*)>& run) {
154     SkString resourceDirectory = GetResourcePath(directory);
155     SkOSFile::Iter iter(resourceDirectory.c_str(), extension);
156     SkString name;
157 
158     while (iter.next(&name, /*getDir=*/false)) {
159         SkString path(SkOSPath::Join(directory, name.c_str()));
160         run(path.c_str());
161     }
162 }
163 
DEF_TEST(SkSLErrorTest,r)164 DEF_TEST(SkSLErrorTest, r) {
165     iterate_dir("sksl/errors/", ".sksl", [&](const char* path) {
166         test_expect_fail(r, path, SkSL::ProgramKind::kFragment);
167     });
168     iterate_dir("sksl/errors/", ".rts", [&](const char* path) {
169         test_expect_fail(r, path, SkSL::ProgramKind::kRuntimeShader);
170     });
171     iterate_dir("sksl/errors/", ".compute", [&](const char* path) {
172         test_expect_fail(r, path, SkSL::ProgramKind::kCompute);
173     });
174 }
175 
DEF_TEST(SkSLRuntimeShaderErrorTest,r)176 DEF_TEST(SkSLRuntimeShaderErrorTest, r) {
177     iterate_dir("sksl/runtime_errors/", ".rts", [&](const char* path) {
178         test_expect_fail(r, path, SkSL::ProgramKind::kRuntimeShader);
179     });
180 }
181 
DEF_TEST(SkSLRuntimeColorFilterErrorTest,r)182 DEF_TEST(SkSLRuntimeColorFilterErrorTest, r) {
183     iterate_dir("sksl/runtime_errors/", ".rtcf", [&](const char* path) {
184         test_expect_fail(r, path, SkSL::ProgramKind::kRuntimeColorFilter);
185     });
186 }
187 
DEF_TEST(SkSLRuntimeBlenderErrorTest,r)188 DEF_TEST(SkSLRuntimeBlenderErrorTest, r) {
189     iterate_dir("sksl/runtime_errors/", ".rtb", [&](const char* path) {
190         test_expect_fail(r, path, SkSL::ProgramKind::kRuntimeBlender);
191     });
192 }
193