/* * 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/base/SkNoncopyable.h" #include "include/private/base/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) namespace skgpu { enum class ContextType; } #endif #if defined(SK_GANESH) #include "tools/gpu/GrContextFactory.h" // IWYU pragma: export (because it is used by a macro) #else namespace sk_gpu_test { class ContextInfo; } #endif #include #include #include 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 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 { SkASSERT(this->fTestType == TestType::kCPU || this->fTestType == TestType::kCPUSerial); TRACE_EVENT1("test_cpu", TRACE_FUNC, "name", this->fName/*these are static*/); this->fCPUProc(r); } void ganesh(skiatest::Reporter* r, const GrContextOptions& options) const { SkASSERT(this->fTestType == TestType::kGanesh); TRACE_EVENT1("test_ganesh", TRACE_FUNC, "name", this->fName/*these are static*/); this->fGaneshProc(r, options); } void graphite(skiatest::Reporter* r, const graphite::TestOptions& options) const { SkASSERT(this->fTestType == TestType::kGraphite); TRACE_EVENT1("test_graphite", TRACE_FUNC, "name", this->fName/*these are static*/); this->fGraphiteProc(r, options); } 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; #if defined(SK_GANESH) 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*); 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, opt_filter, cond, ctsEnforcement) \ static void test_##name(skiatest::Reporter*, skgpu::graphite::Context*, \ skiatest::graphite::GraphiteTestContext*); \ 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) #define DEF_CONDITIONAL_GRAPHITE_TEST_FOR_ALL_CONTEXTS(name, reporter, graphite_ctx, \ test_ctx, cond, ctsEnforcement) \ DEF_CONDITIONAL_GRAPHITE_TEST_FOR_CONTEXTS(name, nullptr, reporter, graphite_ctx, test_ctx, \ nullptr, cond, ctsEnforcement) #define DEF_CONDITIONAL_GRAPHITE_TEST_FOR_RENDERING_CONTEXTS( \ name, reporter, graphite_context, test_context, cond, ctsEnforcement) \ DEF_CONDITIONAL_GRAPHITE_TEST_FOR_CONTEXTS(name, \ skgpu::IsRenderingContext, \ reporter, \ graphite_context, \ test_context, \ 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, nullptr, 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); \ if (!testDoc) { \ INFOF(REPORTER, "PDF disabled; %s test skipped.", #TEST_NAME); \ return; \ } \ } while (false) #endif