| /* | |
| * Copyright 2011 Google Inc. | |
| * | |
| * Use of this source code is governed by a BSD-style license that can be | |
| * found in the LICENSE file. | |
| */ | |
| #ifndef skiatest_Test_DEFINED | |
| #define skiatest_Test_DEFINED | |
| #include "include/core/SkString.h" | |
| #include "include/core/SkTypes.h" | |
| #include "include/private/SkNoncopyable.h" | |
| #include "include/private/SkTArray.h" | |
| #include "src/core/SkTraceEvent.h" | |
| #include "tests/CtsEnforcement.h" | |
| #include "tests/TestType.h" | |
| #include "tools/Registry.h" | |
| #include "tools/timer/TimeUtils.h" | |
| #if defined(SK_GANESH) || defined(SK_GRAPHITE) | |
| #include "tools/gpu/ContextType.h" | |
| #endif | |
| #if defined(SK_GANESH) | |
| #include "tools/ganesh/GrContextFactory.h" // IWYU pragma: export (because it is used by a macro) | |
| #else | |
| namespace sk_gpu_test { class ContextInfo; } | |
| #endif | |
| #include <atomic> | |
| #include <cstdint> | |
| #include <string> | |
| struct GrContextOptions; | |
| namespace skgpu::graphite { | |
| class Context; | |
| struct ContextOptions; | |
| } | |
| namespace skiatest { | |
| namespace graphite { | |
| class GraphiteTestContext; | |
| struct TestOptions; | |
| } | |
| SkString GetTmpDir(); | |
| struct Failure { | |
| Failure(const char* f, int l, const char* c, const SkString& m) | |
| : fileName(f), lineNo(l), condition(c), message(m) {} | |
| const char* fileName; | |
| int lineNo; | |
| const char* condition; | |
| SkString message; | |
| SkString toString() const; | |
| }; | |
| class Reporter : SkNoncopyable { | |
| public: | |
| virtual ~Reporter() {} | |
| virtual void bumpTestCount(); | |
| virtual void reportFailed(const skiatest::Failure&) = 0; | |
| virtual bool allowExtendedTest() const; | |
| virtual bool verbose() const; | |
| virtual void* stats() const { return nullptr; } | |
| void reportFailedWithContext(const skiatest::Failure&); | |
| /** | |
| * Show additional context (e.g. subtest name) on failure assertions. | |
| */ | |
| void push(const SkString& message) { | |
| fContextStack.push_back(message); | |
| } | |
| void push(const std::string& message) { | |
| fContextStack.push_back(SkString(message)); | |
| } | |
| /** | |
| * Remove additional context from failure assertions. | |
| */ | |
| void pop() { | |
| fContextStack.pop_back(); | |
| } | |
| private: | |
| skia_private::TArray<SkString> fContextStack; | |
| }; | |
| #define REPORT_FAILURE(reporter, cond, message) \ | |
| reporter->reportFailedWithContext(skiatest::Failure(__FILE__, __LINE__, cond, message)) | |
| /** | |
| * Use this stack-allocated object to push and then automatically pop the context | |
| * (e.g. subtest name) for a test. | |
| */ | |
| class ReporterContext : SkNoncopyable { | |
| public: | |
| ReporterContext(Reporter* reporter, const SkString& message) : fReporter(reporter) { | |
| fReporter->push(message); | |
| } | |
| ReporterContext(Reporter* reporter, const std::string& message) : fReporter(reporter) { | |
| fReporter->push(message); | |
| } | |
| ~ReporterContext() { | |
| fReporter->pop(); | |
| } | |
| private: | |
| Reporter* fReporter; | |
| }; | |
| using CPUTestProc = void (*)(Reporter*); | |
| using GaneshTestProc = void (*)(Reporter*, const GrContextOptions&); | |
| using GaneshContextOptionsProc = void (*)(GrContextOptions*); | |
| using GraphiteTestProc = void (*)(Reporter*, const graphite::TestOptions&); | |
| using GraphiteContextOptionsProc = void (*)(skgpu::graphite::ContextOptions*); | |
| struct Test { | |
| static Test MakeCPU(const char* name, CPUTestProc proc) { | |
| return Test{name, TestType::kCPU, CtsEnforcement::kNever, | |
| proc, nullptr, nullptr, nullptr, nullptr}; | |
| } | |
| static Test MakeCPUSerial(const char* name, CPUTestProc proc) { | |
| return Test{name, TestType::kCPUSerial, CtsEnforcement::kNever, | |
| proc, nullptr, nullptr, nullptr, nullptr}; | |
| } | |
| static Test MakeGanesh(const char* name, | |
| CtsEnforcement ctsEnforcement, | |
| GaneshTestProc proc, | |
| GaneshContextOptionsProc optionsProc = nullptr) { | |
| return Test{name, TestType::kGanesh, ctsEnforcement, | |
| nullptr, proc, nullptr, optionsProc, nullptr}; | |
| } | |
| static Test MakeGraphite(const char* name, | |
| CtsEnforcement ctsEnforcement, | |
| GraphiteTestProc proc, | |
| GraphiteContextOptionsProc optionsProc = nullptr) { | |
| return Test{name, TestType::kGraphite, ctsEnforcement, | |
| nullptr, nullptr, proc, nullptr, optionsProc}; | |
| } | |
| const char* fName; | |
| TestType fTestType; | |
| CtsEnforcement fCTSEnforcement; | |
| CPUTestProc fCPUProc = nullptr; | |
| GaneshTestProc fGaneshProc = nullptr; | |
| GraphiteTestProc fGraphiteProc = nullptr; | |
| GaneshContextOptionsProc fGaneshContextOptionsProc = nullptr; | |
| GraphiteContextOptionsProc fGraphiteContextOptionsProc = nullptr; | |
| void modifyGrContextOptions(GrContextOptions* options) { | |
| if (fGaneshContextOptionsProc) { | |
| (*fGaneshContextOptionsProc)(options); | |
| } | |
| } | |
| void modifyGraphiteContextOptions(skgpu::graphite::ContextOptions* options) { | |
| if (fGraphiteContextOptionsProc) { | |
| (*fGraphiteContextOptionsProc)(options); | |
| } | |
| } | |
| void cpu(skiatest::Reporter* r) const; | |
| void ganesh(skiatest::Reporter* r, const GrContextOptions& options) const; | |
| void graphite(skiatest::Reporter* r, const graphite::TestOptions& options) const; | |
| private: | |
| Test(const char* name, | |
| TestType testType, | |
| CtsEnforcement ctsEnforcement, | |
| CPUTestProc cpuProc, | |
| GaneshTestProc ganeshProc, | |
| GraphiteTestProc graphiteProc, | |
| GaneshContextOptionsProc ganeshOptionsProc, | |
| GraphiteContextOptionsProc graphiteOptionsProc) | |
| : fName(name) | |
| , fTestType(testType) | |
| , fCTSEnforcement(ctsEnforcement) | |
| , fCPUProc(cpuProc) | |
| , fGaneshProc(ganeshProc) | |
| , fGraphiteProc(graphiteProc) | |
| , fGaneshContextOptionsProc(ganeshOptionsProc) | |
| , fGraphiteContextOptionsProc(graphiteOptionsProc) {} | |
| }; | |
| using TestRegistry = sk_tools::Registry<Test>; | |
| #if defined(SK_GANESH) || defined(SK_GRAPHITE) | |
| using GpuContextType = skgpu::ContextType; | |
| #else | |
| using GpuContextType = nullptr_t; | |
| #endif | |
| typedef void GrContextTestFn(Reporter*, const sk_gpu_test::ContextInfo&); | |
| typedef bool ContextTypeFilterFn(GpuContextType); | |
| // We want to run the same test against potentially multiple Ganesh backends. Test runners should | |
| // implement this function by calling the testFn with a fresh ContextInfo if that backend matches | |
| // the provided filter. If filter is nullptr, then all compiled-in Ganesh backends should be used. | |
| // The reporter and opts arguments are piped in from Test::run. | |
| void RunWithGaneshTestContexts(GrContextTestFn* testFn, ContextTypeFilterFn* filter, | |
| Reporter* reporter, const GrContextOptions& options); | |
| // These context filters should be implemented by test runners and return true if the backend was | |
| // compiled in (i.e. is supported) and matches the criteria indicated by the name of the filter. | |
| extern bool IsGLContextType(GpuContextType); | |
| extern bool IsVulkanContextType(GpuContextType); | |
| extern bool IsMetalContextType(GpuContextType); | |
| extern bool IsDawnContextType(GpuContextType); | |
| extern bool IsDirect3DContextType(GpuContextType); | |
| extern bool IsMockContextType(GpuContextType); | |
| namespace graphite { | |
| using GraphiteTestFn = void(Reporter*, | |
| skgpu::graphite::Context*, | |
| skiatest::graphite::GraphiteTestContext*, | |
| const TestOptions&); | |
| void RunWithGraphiteTestContexts(GraphiteTestFn*, | |
| ContextTypeFilterFn* filter, | |
| Reporter*, | |
| const TestOptions&); | |
| } // namespace graphite | |
| /** Timer provides wall-clock duration since its creation. */ | |
| class Timer { | |
| public: | |
| /** Starts the timer. */ | |
| Timer(); | |
| /** Nanoseconds since creation. */ | |
| double elapsedNs() const; | |
| /** Milliseconds since creation. */ | |
| double elapsedMs() const; | |
| /** Milliseconds since creation as an integer. | |
| Behavior is undefined for durations longer than TimeUtils::MSecMax. | |
| */ | |
| TimeUtils::MSec elapsedMsInt() const; | |
| private: | |
| double fStartNanos; | |
| }; | |
| } // namespace skiatest | |
| /* | |
| Use the following macros to make use of the skiatest classes, e.g. | |
| #include "tests/Test.h" | |
| DEF_TEST(TestName, reporter) { | |
| ... | |
| REPORTER_ASSERT(reporter, x == 15); | |
| ... | |
| REPORTER_ASSERT(reporter, x == 15, "x should be 15"); | |
| ... | |
| if (x != 15) { | |
| ERRORF(reporter, "x should be 15, but is %d", x); | |
| return; | |
| } | |
| ... | |
| } | |
| */ | |
| #define REPORTER_ASSERT(r, cond, ...) \ | |
| do { \ | |
| if (!(cond)) { \ | |
| REPORT_FAILURE(r, #cond, SkStringPrintf(__VA_ARGS__)); \ | |
| } \ | |
| } while (0) | |
| #define ERRORF(r, ...) \ | |
| do { \ | |
| REPORT_FAILURE(r, "", SkStringPrintf(__VA_ARGS__)); \ | |
| } while (0) | |
| #define INFOF(REPORTER, ...) \ | |
| do { \ | |
| if ((REPORTER)->verbose()) { \ | |
| SkDebugf(__VA_ARGS__); \ | |
| } \ | |
| } while (0) | |
| using skiatest::Test; | |
| #define DEF_CONDITIONAL_TEST(name, reporter, condition) \ | |
| static void test_##name(skiatest::Reporter*); \ | |
| skiatest::TestRegistry name##TestRegistry(Test::MakeCPU(#name, test_##name), condition); \ | |
| void test_##name(skiatest::Reporter* reporter) | |
| #define DEF_TEST(name, reporter) DEF_CONDITIONAL_TEST(name, reporter, true) | |
| #define DEF_TEST_DISABLED(name, reporter) DEF_CONDITIONAL_TEST(name, reporter, false) | |
| #ifdef SK_BUILD_FOR_UNIX | |
| #define UNIX_ONLY_TEST DEF_TEST | |
| #else | |
| #define UNIX_ONLY_TEST DEF_TEST_DISABLED | |
| #endif | |
| #define DEF_SERIAL_TEST(name, reporter) \ | |
| static void test_##name(skiatest::Reporter*); \ | |
| skiatest::TestRegistry name##TestRegistry(Test::MakeCPUSerial(#name, test_##name)); \ | |
| void test_##name(skiatest::Reporter* reporter) | |
| #define DEF_GRAPHITE_TEST(name, reporter, ctsEnforcement) \ | |
| static void test_##name(skiatest::Reporter*); \ | |
| static void test_graphite_##name(skiatest::Reporter* reporter, \ | |
| const skiatest::graphite::TestOptions&) { \ | |
| test_##name(reporter); \ | |
| } \ | |
| skiatest::TestRegistry name##TestRegistry(Test::MakeGraphite(#name, ctsEnforcement, \ | |
| test_graphite_##name)); \ | |
| void test_##name(skiatest::Reporter* reporter) | |
| #define DEF_CONDITIONAL_GRAPHITE_TEST_FOR_CONTEXTS( \ | |
| name, context_filter, reporter, graphite_ctx, test_ctx, options_name, \ | |
| opt_filter, cond, ctsEnforcement) \ | |
| static void test_##name(skiatest::Reporter*, \ | |
| skgpu::graphite::Context*, \ | |
| skiatest::graphite::GraphiteTestContext*, \ | |
| const skiatest::graphite::TestOptions&); \ | |
| static void test_graphite_contexts_##name(skiatest::Reporter* _reporter, \ | |
| const skiatest::graphite::TestOptions& _options) { \ | |
| skiatest::graphite::RunWithGraphiteTestContexts(test_##name, context_filter, \ | |
| _reporter, _options); \ | |
| } \ | |
| skiatest::TestRegistry name##TestRegistry( \ | |
| Test::MakeGraphite(#name, ctsEnforcement, test_graphite_contexts_##name, opt_filter), \ | |
| cond); \ | |
| void test_##name(skiatest::Reporter* reporter, \ | |
| skgpu::graphite::Context* graphite_ctx, \ | |
| skiatest::graphite::GraphiteTestContext* test_ctx, \ | |
| const skiatest::graphite::TestOptions& options_name) | |
| #define DEF_CONDITIONAL_GRAPHITE_TEST_FOR_ALL_CONTEXTS( \ | |
| name, reporter, graphite_ctx, test_ctx, cond, ctsEnforcement) \ | |
| DEF_CONDITIONAL_GRAPHITE_TEST_FOR_CONTEXTS(name, \ | |
| /* filter= */ nullptr, \ | |
| reporter, \ | |
| graphite_ctx, \ | |
| test_ctx, \ | |
| /* anonymous options */, \ | |
| /* opt_filter= */ nullptr, \ | |
| cond, \ | |
| ctsEnforcement) | |
| #define DEF_CONDITIONAL_GRAPHITE_TEST_FOR_RENDERING_CONTEXTS( \ | |
| name, reporter, graphite_ctx, test_ctx, cond, ctsEnforcement) \ | |
| DEF_CONDITIONAL_GRAPHITE_TEST_FOR_CONTEXTS(name, \ | |
| skgpu::IsRenderingContext, \ | |
| reporter, \ | |
| graphite_ctx, \ | |
| test_ctx, \ | |
| /* anonymous options */, \ | |
| /* opt_filter= */ nullptr, \ | |
| cond, \ | |
| ctsEnforcement) | |
| #define DEF_GRAPHITE_TEST_FOR_CONTEXTS( \ | |
| name, context_filter, reporter, graphite_ctx, test_ctx, ctsEnforcement) \ | |
| DEF_CONDITIONAL_GRAPHITE_TEST_FOR_CONTEXTS(name, \ | |
| context_filter, \ | |
| reporter, \ | |
| graphite_ctx, \ | |
| test_ctx, \ | |
| /* anonymous options */, \ | |
| /* opt_filter= */ nullptr, \ | |
| /* condition= */ true, \ | |
| ctsEnforcement) | |
| #define DEF_GRAPHITE_TEST_FOR_ALL_CONTEXTS(name, reporter, graphite_ctx, ctsEnforcement) \ | |
| DEF_CONDITIONAL_GRAPHITE_TEST_FOR_ALL_CONTEXTS(name, reporter, graphite_ctx, \ | |
| /*anonymous test_ctx*/, true, ctsEnforcement) | |
| #define DEF_GRAPHITE_TEST_FOR_RENDERING_CONTEXTS(name, reporter, graphite_context, ctsEnforcement) \ | |
| DEF_CONDITIONAL_GRAPHITE_TEST_FOR_RENDERING_CONTEXTS( \ | |
| name, reporter, graphite_context, /*anonymous test_ctx*/, true, ctsEnforcement) | |
| #define DEF_GRAPHITE_TEST_FOR_VULKAN_CONTEXT(name, reporter, graphite_context, ctsEnforcement) \ | |
| DEF_GRAPHITE_TEST_FOR_CONTEXTS(name, skiatest::IsVulkanContextType, reporter, \ | |
| graphite_context, /*anonymous test_ctx*/, ctsEnforcement) | |
| #define DEF_GRAPHITE_TEST_FOR_METAL_CONTEXT(name, reporter, graphite_context, test_context) \ | |
| DEF_GRAPHITE_TEST_FOR_CONTEXTS(name, skiatest::IsMetalContextType, reporter, graphite_context, \ | |
| test_context, CtsEnforcement::kNever) | |
| #define DEF_GRAPHITE_TEST_FOR_DAWN_CONTEXT(name, reporter, graphite_context, test_context) \ | |
| DEF_GRAPHITE_TEST_FOR_CONTEXTS(name, skiatest::IsDawnContextType, reporter, graphite_context, \ | |
| test_context, CtsEnforcement::kNever) | |
| #define DEF_GANESH_TEST(name, reporter, options, ctsEnforcement) \ | |
| static void test_##name(skiatest::Reporter*, const GrContextOptions&); \ | |
| skiatest::TestRegistry name##TestRegistry( \ | |
| Test::MakeGanesh(#name, ctsEnforcement, test_##name, nullptr)); \ | |
| void test_##name(skiatest::Reporter* reporter, const GrContextOptions& options) | |
| #define DEF_CONDITIONAL_GANESH_TEST_FOR_CONTEXTS( \ | |
| name, context_filter, reporter, context_info, options_filter, condition, ctsEnforcement) \ | |
| static void test_##name(skiatest::Reporter*, const sk_gpu_test::ContextInfo&); \ | |
| static void test_gpu_contexts_##name(skiatest::Reporter* reporter, \ | |
| const GrContextOptions& options) { \ | |
| skiatest::RunWithGaneshTestContexts(test_##name, context_filter, reporter, options); \ | |
| } \ | |
| skiatest::TestRegistry name##TestRegistry( \ | |
| Test::MakeGanesh(#name, ctsEnforcement, test_gpu_contexts_##name, options_filter), \ | |
| condition); \ | |
| void test_##name(skiatest::Reporter* reporter, const sk_gpu_test::ContextInfo& context_info) | |
| #define DEF_CONDITIONAL_GANESH_TEST_FOR_ALL_CONTEXTS( \ | |
| name, reporter, context_info, condition, ctsEnforcement) \ | |
| DEF_CONDITIONAL_GANESH_TEST_FOR_CONTEXTS( \ | |
| name, nullptr, reporter, context_info, nullptr, condition, ctsEnforcement) | |
| #define DEF_CONDITIONAL_GANESH_TEST_FOR_RENDERING_CONTEXTS( \ | |
| name, reporter, context_info, condition, ctsEnforcement) \ | |
| DEF_CONDITIONAL_GANESH_TEST_FOR_CONTEXTS(name, \ | |
| skgpu::IsRenderingContext, \ | |
| reporter, \ | |
| context_info, \ | |
| nullptr, \ | |
| condition, \ | |
| ctsEnforcement) | |
| #define DEF_GANESH_TEST_FOR_CONTEXTS( \ | |
| name, context_filter, reporter, context_info, options_filter, ctsEnforcement) \ | |
| DEF_CONDITIONAL_GANESH_TEST_FOR_CONTEXTS( \ | |
| name, context_filter, reporter, context_info, options_filter, true, ctsEnforcement) | |
| #define DEF_GANESH_TEST_FOR_ALL_CONTEXTS(name, reporter, context_info, ctsEnforcement) \ | |
| DEF_GANESH_TEST_FOR_CONTEXTS(name, nullptr, reporter, context_info, nullptr, ctsEnforcement) | |
| #define DEF_GANESH_TEST_FOR_RENDERING_CONTEXTS(name, reporter, context_info, ctsEnforcement) \ | |
| DEF_GANESH_TEST_FOR_CONTEXTS( \ | |
| name, skgpu::IsRenderingContext, reporter, context_info, nullptr, ctsEnforcement) | |
| #define DEF_GANESH_TEST_FOR_ALL_GL_CONTEXTS(name, reporter, context_info, ctsEnforcement) \ | |
| DEF_GANESH_TEST_FOR_CONTEXTS( \ | |
| name, skiatest::IsGLContextType, reporter, context_info, nullptr, ctsEnforcement) | |
| #define DEF_GANESH_TEST_FOR_GL_CONTEXT(name, reporter, context_info, ctsEnforcement) \ | |
| DEF_GANESH_TEST_FOR_CONTEXTS(name, \ | |
| &skiatest::IsGLContextType, \ | |
| reporter, \ | |
| context_info, \ | |
| nullptr, \ | |
| ctsEnforcement) | |
| #define DEF_GANESH_TEST_FOR_MOCK_CONTEXT(name, reporter, context_info) \ | |
| DEF_GANESH_TEST_FOR_CONTEXTS(name, \ | |
| &skiatest::IsMockContextType, \ | |
| reporter, \ | |
| context_info, \ | |
| nullptr, \ | |
| CtsEnforcement::kNever) | |
| #define DEF_GANESH_TEST_FOR_VULKAN_CONTEXT(name, reporter, context_info, ctsEnforcement) \ | |
| DEF_GANESH_TEST_FOR_CONTEXTS( \ | |
| name, &skiatest::IsVulkanContextType, reporter, context_info, nullptr, ctsEnforcement) | |
| #define DEF_GANESH_TEST_FOR_METAL_CONTEXT(name, reporter, context_info) \ | |
| DEF_GANESH_TEST_FOR_CONTEXTS(name, \ | |
| &skiatest::IsMetalContextType, \ | |
| reporter, \ | |
| context_info, \ | |
| nullptr, \ | |
| CtsEnforcement::kNever) | |
| #define DEF_GANESH_TEST_FOR_D3D_CONTEXT(name, reporter, context_info) \ | |
| DEF_GANESH_TEST_FOR_CONTEXTS(name, \ | |
| &skiatest::IsDirect3DContextType, \ | |
| reporter, \ | |
| context_info, \ | |
| nullptr, \ | |
| CtsEnforcement::kNever) | |
| #define DEF_GANESH_TEST_FOR_DAWN_CONTEXT(name, reporter, context_info) \ | |
| DEF_GANESH_TEST_FOR_CONTEXTS(name, \ | |
| &skiatest::IsDawnContextType, \ | |
| reporter, \ | |
| context_info, \ | |
| nullptr, \ | |
| CtsEnforcement::kNever) | |
| #define REQUIRE_PDF_DOCUMENT(TEST_NAME, REPORTER) \ | |
| do { \ | |
| SkNullWStream testStream; \ | |
| auto testDoc = SkPDF::MakeDocument(&testStream, SkPDF::JPEG::MetadataWithCallbacks()); \ | |
| if (!testDoc) { \ | |
| INFOF(REPORTER, "PDF disabled; %s test skipped.", #TEST_NAME); \ | |
| return; \ | |
| } \ | |
| } while (false) | |
| #endif |