| /* |
| * Copyright 2022 Google LLC |
| * |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| #include "include/core/SkData.h" |
| #include "include/core/SkRefCnt.h" |
| #include "include/core/SkString.h" |
| #include "include/private/SkSLProgramKind.h" |
| #include "src/core/SkOSFile.h" |
| #include "src/core/SkTHash.h" |
| #include "src/sksl/SkSLCompiler.h" |
| #include "src/sksl/SkSLProgramSettings.h" |
| #include "src/sksl/SkSLUtil.h" |
| #include "src/sksl/ir/SkSLProgram.h" // IWYU pragma: keep |
| #include "src/utils/SkOSPath.h" |
| #include "tests/Test.h" |
| #include "tools/Resources.h" |
| |
| #include <cstddef> |
| #include <functional> |
| #include <initializer_list> |
| #include <memory> |
| #include <sstream> |
| #include <string> |
| #include <string_view> |
| #include <utility> |
| #include <vector> |
| |
| static std::vector<std::string> get_expected_errors(const char* shaderString) { |
| // Error expectations are embedded in the source with a special *%%* marker, like so: |
| // |
| // /*%%* |
| // expected 'foo', but found 'bar' |
| // 'baz' is not a valid identifier |
| // *%%*/ |
| // |
| // Extract them from the shader text. |
| std::vector<std::string> expectedErrors; |
| constexpr char kExpectedErrorsStart[] = "/*%%*"; |
| constexpr char kExpectedErrorsEnd[] = "*%%*/"; |
| if (const char* startPtr = strstr(shaderString, kExpectedErrorsStart)) { |
| startPtr += strlen(kExpectedErrorsStart); |
| if (const char* endPtr = strstr(startPtr, kExpectedErrorsEnd)) { |
| // Store the text between these delimiters in an array of expected errors. |
| std::stringstream stream{std::string{startPtr, endPtr}}; |
| while (stream.good()) { |
| expectedErrors.push_back({}); |
| std::getline(stream, expectedErrors.back(), '\n'); |
| if (expectedErrors.back().empty()) { |
| expectedErrors.pop_back(); |
| } |
| } |
| } |
| } |
| |
| return expectedErrors; |
| } |
| |
| static void check_expected_errors(skiatest::Reporter* r, |
| const char* testFile, |
| const std::vector<std::string>& expectedErrors, |
| std::string reportedErrors) { |
| // Verify that the SkSL compiler actually emitted the expected error messages. |
| // The list of expectations isn't necessarily exhaustive, though. |
| std::string originalErrors = reportedErrors; |
| bool reportOriginalErrors = false; |
| for (const std::string& expectedError : expectedErrors) { |
| // If this error wasn't reported, trigger an error. |
| size_t pos = reportedErrors.find(expectedError.c_str()); |
| if (pos == std::string::npos) { |
| ERRORF(r, "%s: Expected an error that wasn't reported:\n%s\n", |
| SkOSPath::Basename(testFile).c_str(), expectedError.c_str()); |
| reportOriginalErrors = true; |
| } else { |
| // We found the error that we expected to have. Remove that error from our report, and |
| // everything preceding it as well. This ensures that we don't match the same error |
| // twice, and that errors are reported in the order we expect. |
| reportedErrors.erase(0, pos + expectedError.size()); |
| } |
| } |
| |
| if (reportOriginalErrors) { |
| ERRORF(r, "%s: The following errors were reported:\n%s\n", |
| SkOSPath::Basename(testFile).c_str(), originalErrors.c_str()); |
| } |
| } |
| |
| static void test_expect_fail(skiatest::Reporter* r, const char* testFile, SkSL::ProgramKind kind) { |
| // In a size-optimized build, there are a handful of errors which report differently, or not at |
| // all. Skip over those tests. |
| static const auto* kTestsToSkip = new SkTHashSet<std::string_view>{ |
| // These are tests that have been deleted, but which may still show up (and fail) on bots, |
| // because the resources directory isn't properly cleaned up. (skbug.com/12987) |
| "sksl/errors/InvalidThreadgroupRTS.rts", |
| "sksl/errors/StaticIfTest.sksl", |
| "sksl/errors/StaticSwitchConditionalBreak.sksl", |
| "sksl/errors/StaticSwitchTest.sksl", |
| "sksl/errors/StaticSwitchWithConditionalBreak.sksl", |
| "sksl/errors/StaticSwitchWithConditionalContinue.sksl", |
| "sksl/errors/StaticSwitchWithConditionalReturn.sksl", |
| |
| "sksl/errors/ComputeUniform.compute", |
| "sksl/errors/DuplicateBinding.compute", |
| "sksl/errors/InvalidThreadgroupCompute.compute", |
| "sksl/errors/UnspecifiedBinding.compute", |
| |
| "sksl/runtime_errors/ReservedNameISampler2D.rts", |
| |
| #ifdef SK_ENABLE_OPTIMIZE_SIZE |
| "sksl/errors/ArrayInlinedIndexOutOfRange.sksl", |
| "sksl/errors/MatrixInlinedIndexOutOfRange.sksl", |
| "sksl/errors/OverflowInlinedLiteral.sksl", |
| "sksl/errors/VectorInlinedIndexOutOfRange.sksl", |
| #endif |
| }; |
| if (kTestsToSkip->contains(testFile)) { |
| INFOF(r, "%s: skipped in SK_ENABLE_OPTIMIZE_SIZE mode", testFile); |
| return; |
| } |
| |
| sk_sp<SkData> shaderData = GetResourceAsData(testFile); |
| if (!shaderData) { |
| ERRORF(r, "%s: Unable to load file", SkOSPath::Basename(testFile).c_str()); |
| return; |
| } |
| |
| std::string shaderString{reinterpret_cast<const char*>(shaderData->bytes()), |
| shaderData->size()}; |
| |
| std::vector<std::string> expectedErrors = get_expected_errors(shaderString.c_str()); |
| |
| // Compile the code. |
| SkSL::Compiler compiler(SkSL::ShaderCapsFactory::Standalone()); |
| SkSL::ProgramSettings settings; |
| std::unique_ptr<SkSL::Program> program = compiler.convertProgram(kind, std::move(shaderString), |
| settings); |
| |
| // If the code actually generated a working program, we've already failed. |
| if (program) { |
| ERRORF(r, "%s: Expected failure, but compiled successfully", |
| SkOSPath::Basename(testFile).c_str()); |
| return; |
| } |
| |
| check_expected_errors(r, testFile, expectedErrors, compiler.errorText()); |
| } |
| |
| static void iterate_dir(const char* directory, |
| const char* extension, |
| const std::function<void(const char*)>& run) { |
| SkString resourceDirectory = GetResourcePath(directory); |
| SkOSFile::Iter iter(resourceDirectory.c_str(), extension); |
| SkString name; |
| |
| while (iter.next(&name, /*getDir=*/false)) { |
| SkString path(SkOSPath::Join(directory, name.c_str())); |
| run(path.c_str()); |
| } |
| } |
| |
| DEF_TEST(SkSLErrorTest, r) { |
| iterate_dir("sksl/errors/", ".sksl", [&](const char* path) { |
| test_expect_fail(r, path, SkSL::ProgramKind::kFragment); |
| }); |
| iterate_dir("sksl/errors/", ".rts", [&](const char* path) { |
| test_expect_fail(r, path, SkSL::ProgramKind::kRuntimeShader); |
| }); |
| iterate_dir("sksl/errors/", ".compute", [&](const char* path) { |
| test_expect_fail(r, path, SkSL::ProgramKind::kCompute); |
| }); |
| } |
| |
| DEF_TEST(SkSLRuntimeShaderErrorTest, r) { |
| iterate_dir("sksl/runtime_errors/", ".rts", [&](const char* path) { |
| test_expect_fail(r, path, SkSL::ProgramKind::kRuntimeShader); |
| }); |
| } |
| |
| DEF_TEST(SkSLRuntimeColorFilterErrorTest, r) { |
| iterate_dir("sksl/runtime_errors/", ".rtcf", [&](const char* path) { |
| test_expect_fail(r, path, SkSL::ProgramKind::kRuntimeColorFilter); |
| }); |
| } |
| |
| DEF_TEST(SkSLRuntimeBlenderErrorTest, r) { |
| iterate_dir("sksl/runtime_errors/", ".rtb", [&](const char* path) { |
| test_expect_fail(r, path, SkSL::ProgramKind::kRuntimeBlender); |
| }); |
| } |