xref: /aosp_15_r20/external/skia/tools/testrunners/gm/BazelGMTestRunner.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1*c8dee2aaSAndroid Build Coastguard Worker /*
2*c8dee2aaSAndroid Build Coastguard Worker  * Copyright 2023 Google LLC
3*c8dee2aaSAndroid Build Coastguard Worker  *
4*c8dee2aaSAndroid Build Coastguard Worker  * Use of this source code is governed by a BSD-style license that can be
5*c8dee2aaSAndroid Build Coastguard Worker  * found in the LICENSE file.
6*c8dee2aaSAndroid Build Coastguard Worker  *
7*c8dee2aaSAndroid Build Coastguard Worker  * This program runs all GMs registered via macros such as DEF_GM, and for each GM, it saves the
8*c8dee2aaSAndroid Build Coastguard Worker  * resulting SkBitmap as a .png file to disk, along with a .json file with the hash of the pixels.
9*c8dee2aaSAndroid Build Coastguard Worker  */
10*c8dee2aaSAndroid Build Coastguard Worker 
11*c8dee2aaSAndroid Build Coastguard Worker #include "gm/gm.h"
12*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkBitmap.h"
13*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkCanvas.h"
14*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkColorSpace.h"
15*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkColorType.h"
16*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkImageInfo.h"
17*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkStream.h"
18*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkSurface.h"
19*c8dee2aaSAndroid Build Coastguard Worker #include "include/encode/SkPngEncoder.h"
20*c8dee2aaSAndroid Build Coastguard Worker #include "include/private/base/SkAssert.h"
21*c8dee2aaSAndroid Build Coastguard Worker #include "include/private/base/SkDebug.h"
22*c8dee2aaSAndroid Build Coastguard Worker #include "src/core/SkMD5.h"
23*c8dee2aaSAndroid Build Coastguard Worker #include "src/utils/SkJSONWriter.h"
24*c8dee2aaSAndroid Build Coastguard Worker #include "src/utils/SkOSPath.h"
25*c8dee2aaSAndroid Build Coastguard Worker #include "tools/HashAndEncode.h"
26*c8dee2aaSAndroid Build Coastguard Worker #include "tools/testrunners/common/TestRunner.h"
27*c8dee2aaSAndroid Build Coastguard Worker #include "tools/testrunners/common/compilation_mode_keys/CompilationModeKeys.h"
28*c8dee2aaSAndroid Build Coastguard Worker #include "tools/testrunners/common/surface_manager/SurfaceManager.h"
29*c8dee2aaSAndroid Build Coastguard Worker #include "tools/testrunners/gm/vias/Draw.h"
30*c8dee2aaSAndroid Build Coastguard Worker 
31*c8dee2aaSAndroid Build Coastguard Worker #include <algorithm>
32*c8dee2aaSAndroid Build Coastguard Worker #include <ctime>
33*c8dee2aaSAndroid Build Coastguard Worker #include <filesystem>
34*c8dee2aaSAndroid Build Coastguard Worker #include <fstream>
35*c8dee2aaSAndroid Build Coastguard Worker #include <iomanip>
36*c8dee2aaSAndroid Build Coastguard Worker #include <iostream>
37*c8dee2aaSAndroid Build Coastguard Worker #include <regex>
38*c8dee2aaSAndroid Build Coastguard Worker #include <set>
39*c8dee2aaSAndroid Build Coastguard Worker #include <sstream>
40*c8dee2aaSAndroid Build Coastguard Worker #include <string>
41*c8dee2aaSAndroid Build Coastguard Worker 
42*c8dee2aaSAndroid Build Coastguard Worker // TODO(lovisolo): Add flag --omitDigestIfHashInFile (provides the known hashes file).
43*c8dee2aaSAndroid Build Coastguard Worker 
44*c8dee2aaSAndroid Build Coastguard Worker static DEFINE_string(skip, "", "Space-separated list of test cases (regexps) to skip.");
45*c8dee2aaSAndroid Build Coastguard Worker static DEFINE_string(
46*c8dee2aaSAndroid Build Coastguard Worker         match,
47*c8dee2aaSAndroid Build Coastguard Worker         "",
48*c8dee2aaSAndroid Build Coastguard Worker         "Space-separated list of test cases (regexps) to run. Will run all tests if omitted.");
49*c8dee2aaSAndroid Build Coastguard Worker 
50*c8dee2aaSAndroid Build Coastguard Worker // When running under Bazel and overriding the output directory, you might encounter errors such
51*c8dee2aaSAndroid Build Coastguard Worker // as "No such file or directory" and "Read-only file system". The former can happen when running
52*c8dee2aaSAndroid Build Coastguard Worker // on RBE because the passed in output dir might not exist on the remote worker, whereas the latter
53*c8dee2aaSAndroid Build Coastguard Worker // can happen when running locally in sandboxed mode, which is the default strategy when running
54*c8dee2aaSAndroid Build Coastguard Worker // outside of RBE. One possible workaround is to run the test as a local subprocess, which can be
55*c8dee2aaSAndroid Build Coastguard Worker // done by passing flag --strategy=TestRunner=local to Bazel.
56*c8dee2aaSAndroid Build Coastguard Worker //
57*c8dee2aaSAndroid Build Coastguard Worker // Reference: https://bazel.build/docs/user-manual#execution-strategy.
58*c8dee2aaSAndroid Build Coastguard Worker static DEFINE_string(outputDir,
59*c8dee2aaSAndroid Build Coastguard Worker                      "",
60*c8dee2aaSAndroid Build Coastguard Worker                      "Directory where to write any output .png and .json files. "
61*c8dee2aaSAndroid Build Coastguard Worker                      "Optional when running under Bazel "
62*c8dee2aaSAndroid Build Coastguard Worker                      "(e.g. \"bazel test //path/to:test\") as it defaults to "
63*c8dee2aaSAndroid Build Coastguard Worker                      "$TEST_UNDECLARED_OUTPUTS_DIR.");
64*c8dee2aaSAndroid Build Coastguard Worker 
65*c8dee2aaSAndroid Build Coastguard Worker static DEFINE_string(knownDigestsFile,
66*c8dee2aaSAndroid Build Coastguard Worker                      "",
67*c8dee2aaSAndroid Build Coastguard Worker                      "Plaintext file with one MD5 hash per line. This test runner will omit from "
68*c8dee2aaSAndroid Build Coastguard Worker                      "the output directory any images with an MD5 hash in this file.");
69*c8dee2aaSAndroid Build Coastguard Worker 
70*c8dee2aaSAndroid Build Coastguard Worker static DEFINE_string(key, "", "Space-separated key/value pairs common to all traces.");
71*c8dee2aaSAndroid Build Coastguard Worker 
72*c8dee2aaSAndroid Build Coastguard Worker // We named this flag --surfaceConfig rather than --config to avoid confusion with the --config
73*c8dee2aaSAndroid Build Coastguard Worker // Bazel flag.
74*c8dee2aaSAndroid Build Coastguard Worker static DEFINE_string(
75*c8dee2aaSAndroid Build Coastguard Worker         surfaceConfig,
76*c8dee2aaSAndroid Build Coastguard Worker         "",
77*c8dee2aaSAndroid Build Coastguard Worker         "Name of the Surface configuration to use (e.g. \"8888\"). This determines "
78*c8dee2aaSAndroid Build Coastguard Worker         "how we construct the SkSurface from which we get the SkCanvas that GMs will "
79*c8dee2aaSAndroid Build Coastguard Worker         "draw on. See file //tools/testrunners/common/surface_manager/SurfaceManager.h for "
80*c8dee2aaSAndroid Build Coastguard Worker         "details.");
81*c8dee2aaSAndroid Build Coastguard Worker 
82*c8dee2aaSAndroid Build Coastguard Worker static DEFINE_string(
83*c8dee2aaSAndroid Build Coastguard Worker         cpuName,
84*c8dee2aaSAndroid Build Coastguard Worker         "",
85*c8dee2aaSAndroid Build Coastguard Worker         "Contents of the \"cpu_or_gpu_value\" dimension for CPU-bound traces (e.g. \"AVX512\").");
86*c8dee2aaSAndroid Build Coastguard Worker 
87*c8dee2aaSAndroid Build Coastguard Worker static DEFINE_string(
88*c8dee2aaSAndroid Build Coastguard Worker         gpuName,
89*c8dee2aaSAndroid Build Coastguard Worker         "",
90*c8dee2aaSAndroid Build Coastguard Worker         "Contents of the \"cpu_or_gpu_value\" dimension for GPU-bound traces (e.g. \"RTX3060\").");
91*c8dee2aaSAndroid Build Coastguard Worker 
92*c8dee2aaSAndroid Build Coastguard Worker static DEFINE_string(via,
93*c8dee2aaSAndroid Build Coastguard Worker                      "direct",  // Equivalent to running DM without a via.
94*c8dee2aaSAndroid Build Coastguard Worker                      "Name of the \"via\" to use (e.g. \"picture_serialization\"). Optional.");
95*c8dee2aaSAndroid Build Coastguard Worker 
96*c8dee2aaSAndroid Build Coastguard Worker // Set in //bazel/devicesrc but only consumed by adb_test_runner.go. We cannot use the
97*c8dee2aaSAndroid Build Coastguard Worker // DEFINE_string macro because the flag name includes dashes.
98*c8dee2aaSAndroid Build Coastguard Worker [[maybe_unused]] static bool unused =
99*c8dee2aaSAndroid Build Coastguard Worker         SkFlagInfo::CreateStringFlag("device-specific-bazel-config",
100*c8dee2aaSAndroid Build Coastguard Worker                                      nullptr,
101*c8dee2aaSAndroid Build Coastguard Worker                                      new CommandLineFlags::StringArray(),
102*c8dee2aaSAndroid Build Coastguard Worker                                      nullptr,
103*c8dee2aaSAndroid Build Coastguard Worker                                      "Ignored by this test runner.",
104*c8dee2aaSAndroid Build Coastguard Worker                                      nullptr);
105*c8dee2aaSAndroid Build Coastguard Worker 
106*c8dee2aaSAndroid Build Coastguard Worker // Return type for function write_png_and_json_files().
107*c8dee2aaSAndroid Build Coastguard Worker struct WritePNGAndJSONFilesResult {
108*c8dee2aaSAndroid Build Coastguard Worker     enum { kSuccess, kSkippedKnownDigest, kError } status;
109*c8dee2aaSAndroid Build Coastguard Worker     std::string errorMsg = "";
110*c8dee2aaSAndroid Build Coastguard Worker     std::string skippedDigest = "";
111*c8dee2aaSAndroid Build Coastguard Worker };
112*c8dee2aaSAndroid Build Coastguard Worker 
113*c8dee2aaSAndroid Build Coastguard Worker // Takes a SkBitmap and writes the resulting PNG and MD5 hash into the given files.
write_png_and_json_files(std::string name,std::map<std::string,std::string> commonKeys,std::map<std::string,std::string> gmGoldKeys,std::map<std::string,std::string> surfaceGoldKeys,const SkBitmap & bitmap,const char * pngPath,const char * jsonPath,std::set<std::string> knownDigests)114*c8dee2aaSAndroid Build Coastguard Worker static WritePNGAndJSONFilesResult write_png_and_json_files(
115*c8dee2aaSAndroid Build Coastguard Worker         std::string name,
116*c8dee2aaSAndroid Build Coastguard Worker         std::map<std::string, std::string> commonKeys,
117*c8dee2aaSAndroid Build Coastguard Worker         std::map<std::string, std::string> gmGoldKeys,
118*c8dee2aaSAndroid Build Coastguard Worker         std::map<std::string, std::string> surfaceGoldKeys,
119*c8dee2aaSAndroid Build Coastguard Worker         const SkBitmap& bitmap,
120*c8dee2aaSAndroid Build Coastguard Worker         const char* pngPath,
121*c8dee2aaSAndroid Build Coastguard Worker         const char* jsonPath,
122*c8dee2aaSAndroid Build Coastguard Worker         std::set<std::string> knownDigests) {
123*c8dee2aaSAndroid Build Coastguard Worker     HashAndEncode hashAndEncode(bitmap);
124*c8dee2aaSAndroid Build Coastguard Worker 
125*c8dee2aaSAndroid Build Coastguard Worker     // Compute MD5 hash.
126*c8dee2aaSAndroid Build Coastguard Worker     SkMD5 hash;
127*c8dee2aaSAndroid Build Coastguard Worker     hashAndEncode.feedHash(&hash);
128*c8dee2aaSAndroid Build Coastguard Worker     SkMD5::Digest digest = hash.finish();
129*c8dee2aaSAndroid Build Coastguard Worker     SkString md5 = digest.toLowercaseHexString();
130*c8dee2aaSAndroid Build Coastguard Worker 
131*c8dee2aaSAndroid Build Coastguard Worker     // Skip this digest if it's known.
132*c8dee2aaSAndroid Build Coastguard Worker     if (knownDigests.find(md5.c_str()) != knownDigests.end()) {
133*c8dee2aaSAndroid Build Coastguard Worker         return {
134*c8dee2aaSAndroid Build Coastguard Worker                 .status = WritePNGAndJSONFilesResult::kSkippedKnownDigest,
135*c8dee2aaSAndroid Build Coastguard Worker                 .skippedDigest = md5.c_str(),
136*c8dee2aaSAndroid Build Coastguard Worker         };
137*c8dee2aaSAndroid Build Coastguard Worker     }
138*c8dee2aaSAndroid Build Coastguard Worker 
139*c8dee2aaSAndroid Build Coastguard Worker     // Write PNG file.
140*c8dee2aaSAndroid Build Coastguard Worker     SkFILEWStream pngFile(pngPath);
141*c8dee2aaSAndroid Build Coastguard Worker     bool result = hashAndEncode.encodePNG(&pngFile,
142*c8dee2aaSAndroid Build Coastguard Worker                                           md5.c_str(),
143*c8dee2aaSAndroid Build Coastguard Worker                                           /* key= */ CommandLineFlags::StringArray(),
144*c8dee2aaSAndroid Build Coastguard Worker                                           /* properties= */ CommandLineFlags::StringArray());
145*c8dee2aaSAndroid Build Coastguard Worker     if (!result) {
146*c8dee2aaSAndroid Build Coastguard Worker         return {
147*c8dee2aaSAndroid Build Coastguard Worker                 .status = WritePNGAndJSONFilesResult::kError,
148*c8dee2aaSAndroid Build Coastguard Worker                 .errorMsg = "Error encoding or writing PNG to " + std::string(pngPath),
149*c8dee2aaSAndroid Build Coastguard Worker         };
150*c8dee2aaSAndroid Build Coastguard Worker     }
151*c8dee2aaSAndroid Build Coastguard Worker 
152*c8dee2aaSAndroid Build Coastguard Worker     // Validate GM-related Gold keys.
153*c8dee2aaSAndroid Build Coastguard Worker     if (gmGoldKeys.find("name") == gmGoldKeys.end()) {
154*c8dee2aaSAndroid Build Coastguard Worker         SK_ABORT("gmGoldKeys does not contain key \"name\"");
155*c8dee2aaSAndroid Build Coastguard Worker     }
156*c8dee2aaSAndroid Build Coastguard Worker     if (gmGoldKeys.find("source_type") == gmGoldKeys.end()) {
157*c8dee2aaSAndroid Build Coastguard Worker         SK_ABORT("gmGoldKeys does not contain key \"source_type\"");
158*c8dee2aaSAndroid Build Coastguard Worker     }
159*c8dee2aaSAndroid Build Coastguard Worker 
160*c8dee2aaSAndroid Build Coastguard Worker     // Validate surface-related Gold keys.
161*c8dee2aaSAndroid Build Coastguard Worker     if (surfaceGoldKeys.find("surface_config") == surfaceGoldKeys.end()) {
162*c8dee2aaSAndroid Build Coastguard Worker         SK_ABORT("surfaceGoldKeys does not contain key \"surface_config\"");
163*c8dee2aaSAndroid Build Coastguard Worker     }
164*c8dee2aaSAndroid Build Coastguard Worker 
165*c8dee2aaSAndroid Build Coastguard Worker     // Gather all Gold keys.
166*c8dee2aaSAndroid Build Coastguard Worker     std::map<std::string, std::string> keys = {
167*c8dee2aaSAndroid Build Coastguard Worker             {"build_system", "bazel"},
168*c8dee2aaSAndroid Build Coastguard Worker     };
169*c8dee2aaSAndroid Build Coastguard Worker     keys.merge(GetCompilationModeGoldAndPerfKeyValuePairs());
170*c8dee2aaSAndroid Build Coastguard Worker     keys.merge(commonKeys);
171*c8dee2aaSAndroid Build Coastguard Worker     keys.merge(surfaceGoldKeys);
172*c8dee2aaSAndroid Build Coastguard Worker     keys.merge(gmGoldKeys);
173*c8dee2aaSAndroid Build Coastguard Worker 
174*c8dee2aaSAndroid Build Coastguard Worker     // Write JSON file with MD5 hash and Gold key-value pairs.
175*c8dee2aaSAndroid Build Coastguard Worker     SkFILEWStream jsonFile(jsonPath);
176*c8dee2aaSAndroid Build Coastguard Worker     SkJSONWriter jsonWriter(&jsonFile, SkJSONWriter::Mode::kPretty);
177*c8dee2aaSAndroid Build Coastguard Worker     jsonWriter.beginObject();  // Root object.
178*c8dee2aaSAndroid Build Coastguard Worker     jsonWriter.appendString("md5", md5);
179*c8dee2aaSAndroid Build Coastguard Worker     jsonWriter.beginObject("keys");  // "keys" dictionary.
180*c8dee2aaSAndroid Build Coastguard Worker     for (auto const& [param, value] : keys) {
181*c8dee2aaSAndroid Build Coastguard Worker         jsonWriter.appendString(param.c_str(), SkString(value));
182*c8dee2aaSAndroid Build Coastguard Worker     }
183*c8dee2aaSAndroid Build Coastguard Worker     jsonWriter.endObject();  // "keys" dictionary.
184*c8dee2aaSAndroid Build Coastguard Worker     jsonWriter.endObject();  // Root object.
185*c8dee2aaSAndroid Build Coastguard Worker 
186*c8dee2aaSAndroid Build Coastguard Worker     return {.status = WritePNGAndJSONFilesResult::kSuccess};
187*c8dee2aaSAndroid Build Coastguard Worker }
188*c8dee2aaSAndroid Build Coastguard Worker 
draw_result_to_string(skiagm::DrawResult result)189*c8dee2aaSAndroid Build Coastguard Worker static std::string draw_result_to_string(skiagm::DrawResult result) {
190*c8dee2aaSAndroid Build Coastguard Worker     switch (result) {
191*c8dee2aaSAndroid Build Coastguard Worker         case skiagm::DrawResult::kOk:
192*c8dee2aaSAndroid Build Coastguard Worker             return "Ok";
193*c8dee2aaSAndroid Build Coastguard Worker         case skiagm::DrawResult::kFail:
194*c8dee2aaSAndroid Build Coastguard Worker             return "Fail";
195*c8dee2aaSAndroid Build Coastguard Worker         case skiagm::DrawResult::kSkip:
196*c8dee2aaSAndroid Build Coastguard Worker             return "Skip";
197*c8dee2aaSAndroid Build Coastguard Worker         default:
198*c8dee2aaSAndroid Build Coastguard Worker             SkUNREACHABLE;
199*c8dee2aaSAndroid Build Coastguard Worker     }
200*c8dee2aaSAndroid Build Coastguard Worker }
201*c8dee2aaSAndroid Build Coastguard Worker 
202*c8dee2aaSAndroid Build Coastguard Worker static int gNumSuccessfulGMs = 0;
203*c8dee2aaSAndroid Build Coastguard Worker static int gNumFailedGMs = 0;
204*c8dee2aaSAndroid Build Coastguard Worker static int gNumSkippedGMs = 0;
205*c8dee2aaSAndroid Build Coastguard Worker 
206*c8dee2aaSAndroid Build Coastguard Worker static bool gMissingCpuOrGpuWarningLogged = false;
207*c8dee2aaSAndroid Build Coastguard Worker 
208*c8dee2aaSAndroid Build Coastguard Worker // Runs a GM under the given surface config, and saves its output PNG file (and accompanying JSON
209*c8dee2aaSAndroid Build Coastguard Worker // file with metadata) to the given output directory.
run_gm(std::unique_ptr<skiagm::GM> gm,std::string config,std::map<std::string,std::string> keyValuePairs,std::string cpuName,std::string gpuName,std::string outputDir,std::set<std::string> knownDigests)210*c8dee2aaSAndroid Build Coastguard Worker void run_gm(std::unique_ptr<skiagm::GM> gm,
211*c8dee2aaSAndroid Build Coastguard Worker             std::string config,
212*c8dee2aaSAndroid Build Coastguard Worker             std::map<std::string, std::string> keyValuePairs,
213*c8dee2aaSAndroid Build Coastguard Worker             std::string cpuName,
214*c8dee2aaSAndroid Build Coastguard Worker             std::string gpuName,
215*c8dee2aaSAndroid Build Coastguard Worker             std::string outputDir,
216*c8dee2aaSAndroid Build Coastguard Worker             std::set<std::string> knownDigests) {
217*c8dee2aaSAndroid Build Coastguard Worker     TestRunner::Log("GM: %s", gm->getName().c_str());
218*c8dee2aaSAndroid Build Coastguard Worker 
219*c8dee2aaSAndroid Build Coastguard Worker     // Create surface and canvas.
220*c8dee2aaSAndroid Build Coastguard Worker     std::unique_ptr<SurfaceManager> surfaceManager = SurfaceManager::FromConfig(
221*c8dee2aaSAndroid Build Coastguard Worker             config, SurfaceOptions{gm->getISize().width(), gm->getISize().height()});
222*c8dee2aaSAndroid Build Coastguard Worker     if (surfaceManager == nullptr) {
223*c8dee2aaSAndroid Build Coastguard Worker         SK_ABORT("Unknown --surfaceConfig flag value: %s.", config.c_str());
224*c8dee2aaSAndroid Build Coastguard Worker     }
225*c8dee2aaSAndroid Build Coastguard Worker 
226*c8dee2aaSAndroid Build Coastguard Worker     // Print warning about missing cpu_or_gpu key if necessary.
227*c8dee2aaSAndroid Build Coastguard Worker     if ((surfaceManager->isCpuOrGpuBound() == SurfaceManager::CpuOrGpu::kCPU && cpuName == "" &&
228*c8dee2aaSAndroid Build Coastguard Worker          !gMissingCpuOrGpuWarningLogged)) {
229*c8dee2aaSAndroid Build Coastguard Worker         TestRunner::Log(
230*c8dee2aaSAndroid Build Coastguard Worker                 "\tWarning: The surface is CPU-bound, but flag --cpuName was not provided. "
231*c8dee2aaSAndroid Build Coastguard Worker                 "Gold traces will omit keys \"cpu_or_gpu\" and \"cpu_or_gpu_value\".");
232*c8dee2aaSAndroid Build Coastguard Worker         gMissingCpuOrGpuWarningLogged = true;
233*c8dee2aaSAndroid Build Coastguard Worker     }
234*c8dee2aaSAndroid Build Coastguard Worker     if ((surfaceManager->isCpuOrGpuBound() == SurfaceManager::CpuOrGpu::kGPU && gpuName == "" &&
235*c8dee2aaSAndroid Build Coastguard Worker          !gMissingCpuOrGpuWarningLogged)) {
236*c8dee2aaSAndroid Build Coastguard Worker         TestRunner::Log(
237*c8dee2aaSAndroid Build Coastguard Worker                 "\tWarning: The surface is GPU-bound, but flag --gpuName was not provided. "
238*c8dee2aaSAndroid Build Coastguard Worker                 "Gold traces will omit keys \"cpu_or_gpu\" and \"cpu_or_gpu_value\".");
239*c8dee2aaSAndroid Build Coastguard Worker         gMissingCpuOrGpuWarningLogged = true;
240*c8dee2aaSAndroid Build Coastguard Worker     }
241*c8dee2aaSAndroid Build Coastguard Worker 
242*c8dee2aaSAndroid Build Coastguard Worker     // Set up GPU.
243*c8dee2aaSAndroid Build Coastguard Worker     TestRunner::Log("\tSetting up GPU...");
244*c8dee2aaSAndroid Build Coastguard Worker     SkString msg;
245*c8dee2aaSAndroid Build Coastguard Worker     skiagm::DrawResult result = gm->gpuSetup(surfaceManager->getSurface()->getCanvas(), &msg);
246*c8dee2aaSAndroid Build Coastguard Worker 
247*c8dee2aaSAndroid Build Coastguard Worker     // Draw GM into canvas if GPU setup was successful.
248*c8dee2aaSAndroid Build Coastguard Worker     SkBitmap bitmap;
249*c8dee2aaSAndroid Build Coastguard Worker     if (result == skiagm::DrawResult::kOk) {
250*c8dee2aaSAndroid Build Coastguard Worker         GMOutput output;
251*c8dee2aaSAndroid Build Coastguard Worker         std::string viaName = FLAGS_via.size() == 0 ? "" : (FLAGS_via[0]);
252*c8dee2aaSAndroid Build Coastguard Worker         TestRunner::Log("\tDrawing GM via \"%s\"...", viaName.c_str());
253*c8dee2aaSAndroid Build Coastguard Worker         output = draw(gm.get(), surfaceManager->getSurface().get(), viaName);
254*c8dee2aaSAndroid Build Coastguard Worker         result = output.result;
255*c8dee2aaSAndroid Build Coastguard Worker         msg = SkString(output.msg.c_str());
256*c8dee2aaSAndroid Build Coastguard Worker         bitmap = output.bitmap;
257*c8dee2aaSAndroid Build Coastguard Worker     }
258*c8dee2aaSAndroid Build Coastguard Worker 
259*c8dee2aaSAndroid Build Coastguard Worker     // Keep track of results. We will exit with a non-zero exit code in the case of failures.
260*c8dee2aaSAndroid Build Coastguard Worker     switch (result) {
261*c8dee2aaSAndroid Build Coastguard Worker         case skiagm::DrawResult::kOk:
262*c8dee2aaSAndroid Build Coastguard Worker             // We don't increment numSuccessfulGMs just yet. We still need to successfully save
263*c8dee2aaSAndroid Build Coastguard Worker             // its output bitmap to disk.
264*c8dee2aaSAndroid Build Coastguard Worker             TestRunner::Log("\tFlushing surface...");
265*c8dee2aaSAndroid Build Coastguard Worker             surfaceManager->flush();
266*c8dee2aaSAndroid Build Coastguard Worker             break;
267*c8dee2aaSAndroid Build Coastguard Worker         case skiagm::DrawResult::kFail:
268*c8dee2aaSAndroid Build Coastguard Worker             gNumFailedGMs++;
269*c8dee2aaSAndroid Build Coastguard Worker             break;
270*c8dee2aaSAndroid Build Coastguard Worker         case skiagm::DrawResult::kSkip:
271*c8dee2aaSAndroid Build Coastguard Worker             gNumSkippedGMs++;
272*c8dee2aaSAndroid Build Coastguard Worker             break;
273*c8dee2aaSAndroid Build Coastguard Worker         default:
274*c8dee2aaSAndroid Build Coastguard Worker             SkUNREACHABLE;
275*c8dee2aaSAndroid Build Coastguard Worker     }
276*c8dee2aaSAndroid Build Coastguard Worker 
277*c8dee2aaSAndroid Build Coastguard Worker     // Report GM result and optional message.
278*c8dee2aaSAndroid Build Coastguard Worker     TestRunner::Log("\tResult: %s", draw_result_to_string(result).c_str());
279*c8dee2aaSAndroid Build Coastguard Worker     if (!msg.isEmpty()) {
280*c8dee2aaSAndroid Build Coastguard Worker         TestRunner::Log("\tMessage: \"%s\"", msg.c_str());
281*c8dee2aaSAndroid Build Coastguard Worker     }
282*c8dee2aaSAndroid Build Coastguard Worker 
283*c8dee2aaSAndroid Build Coastguard Worker     // Save PNG and JSON file with MD5 hash to disk if the GM was successful.
284*c8dee2aaSAndroid Build Coastguard Worker     if (result == skiagm::DrawResult::kOk) {
285*c8dee2aaSAndroid Build Coastguard Worker         std::string name = std::string(gm->getName().c_str());
286*c8dee2aaSAndroid Build Coastguard Worker         SkString pngPath = SkOSPath::Join(outputDir.c_str(), (name + ".png").c_str());
287*c8dee2aaSAndroid Build Coastguard Worker         SkString jsonPath = SkOSPath::Join(outputDir.c_str(), (name + ".json").c_str());
288*c8dee2aaSAndroid Build Coastguard Worker 
289*c8dee2aaSAndroid Build Coastguard Worker         WritePNGAndJSONFilesResult pngAndJSONResult =
290*c8dee2aaSAndroid Build Coastguard Worker                 write_png_and_json_files(gm->getName().c_str(),
291*c8dee2aaSAndroid Build Coastguard Worker                                          keyValuePairs,
292*c8dee2aaSAndroid Build Coastguard Worker                                          gm->getGoldKeys(),
293*c8dee2aaSAndroid Build Coastguard Worker                                          surfaceManager->getGoldKeyValuePairs(cpuName, gpuName),
294*c8dee2aaSAndroid Build Coastguard Worker                                          bitmap,
295*c8dee2aaSAndroid Build Coastguard Worker                                          pngPath.c_str(),
296*c8dee2aaSAndroid Build Coastguard Worker                                          jsonPath.c_str(),
297*c8dee2aaSAndroid Build Coastguard Worker                                          knownDigests);
298*c8dee2aaSAndroid Build Coastguard Worker         if (pngAndJSONResult.status == WritePNGAndJSONFilesResult::kError) {
299*c8dee2aaSAndroid Build Coastguard Worker             TestRunner::Log("\tERROR: %s", pngAndJSONResult.errorMsg.c_str());
300*c8dee2aaSAndroid Build Coastguard Worker             gNumFailedGMs++;
301*c8dee2aaSAndroid Build Coastguard Worker         } else if (pngAndJSONResult.status == WritePNGAndJSONFilesResult::kSkippedKnownDigest) {
302*c8dee2aaSAndroid Build Coastguard Worker             TestRunner::Log("\tSkipping known digest: %s", pngAndJSONResult.skippedDigest.c_str());
303*c8dee2aaSAndroid Build Coastguard Worker         } else {
304*c8dee2aaSAndroid Build Coastguard Worker             gNumSuccessfulGMs++;
305*c8dee2aaSAndroid Build Coastguard Worker             TestRunner::Log("\tPNG file written to: %s", pngPath.c_str());
306*c8dee2aaSAndroid Build Coastguard Worker             TestRunner::Log("\tJSON file written to: %s", jsonPath.c_str());
307*c8dee2aaSAndroid Build Coastguard Worker         }
308*c8dee2aaSAndroid Build Coastguard Worker     }
309*c8dee2aaSAndroid Build Coastguard Worker }
310*c8dee2aaSAndroid Build Coastguard Worker 
311*c8dee2aaSAndroid Build Coastguard Worker // Reads a plaintext file with "known digests" (i.e. digests that are known positives or negatives
312*c8dee2aaSAndroid Build Coastguard Worker // in Gold) and returns the digests (MD5 hashes) as a set of strings.
read_known_digests_file(std::string path)313*c8dee2aaSAndroid Build Coastguard Worker std::set<std::string> read_known_digests_file(std::string path) {
314*c8dee2aaSAndroid Build Coastguard Worker     std::set<std::string> hashes;
315*c8dee2aaSAndroid Build Coastguard Worker     std::regex md5HashRegex("^[a-fA-F0-9]{32}$");
316*c8dee2aaSAndroid Build Coastguard Worker     std::ifstream f(path);
317*c8dee2aaSAndroid Build Coastguard Worker     std::string line;
318*c8dee2aaSAndroid Build Coastguard Worker     for (int lineNum = 1; std::getline(f, line); lineNum++) {
319*c8dee2aaSAndroid Build Coastguard Worker         // Trim left and right (https://stackoverflow.com/a/217605).
320*c8dee2aaSAndroid Build Coastguard Worker         auto isSpace = [](unsigned char c) { return !std::isspace(c); };
321*c8dee2aaSAndroid Build Coastguard Worker         std::string md5 = line;
322*c8dee2aaSAndroid Build Coastguard Worker         md5.erase(md5.begin(), std::find_if(md5.begin(), md5.end(), isSpace));
323*c8dee2aaSAndroid Build Coastguard Worker         md5.erase(std::find_if(md5.rbegin(), md5.rend(), isSpace).base(), md5.end());
324*c8dee2aaSAndroid Build Coastguard Worker 
325*c8dee2aaSAndroid Build Coastguard Worker         if (md5 == "") continue;
326*c8dee2aaSAndroid Build Coastguard Worker 
327*c8dee2aaSAndroid Build Coastguard Worker         if (!std::regex_match(md5, md5HashRegex)) {
328*c8dee2aaSAndroid Build Coastguard Worker             SK_ABORT(
329*c8dee2aaSAndroid Build Coastguard Worker                     "File '%s' passed via --knownDigestsFile contains an invalid entry on line "
330*c8dee2aaSAndroid Build Coastguard Worker                     "%d: '%s'",
331*c8dee2aaSAndroid Build Coastguard Worker                     path.c_str(),
332*c8dee2aaSAndroid Build Coastguard Worker                     lineNum,
333*c8dee2aaSAndroid Build Coastguard Worker                     line.c_str());
334*c8dee2aaSAndroid Build Coastguard Worker         }
335*c8dee2aaSAndroid Build Coastguard Worker         hashes.insert(md5);
336*c8dee2aaSAndroid Build Coastguard Worker     }
337*c8dee2aaSAndroid Build Coastguard Worker     return hashes;
338*c8dee2aaSAndroid Build Coastguard Worker }
339*c8dee2aaSAndroid Build Coastguard Worker 
main(int argc,char ** argv)340*c8dee2aaSAndroid Build Coastguard Worker int main(int argc, char** argv) {
341*c8dee2aaSAndroid Build Coastguard Worker     TestRunner::InitAndLogCmdlineArgs(argc, argv);
342*c8dee2aaSAndroid Build Coastguard Worker 
343*c8dee2aaSAndroid Build Coastguard Worker     // When running under Bazel (e.g. "bazel test //path/to:test"), we'll store output files in
344*c8dee2aaSAndroid Build Coastguard Worker     // $TEST_UNDECLARED_OUTPUTS_DIR unless overridden via the --outputDir flag.
345*c8dee2aaSAndroid Build Coastguard Worker     //
346*c8dee2aaSAndroid Build Coastguard Worker     // See https://bazel.build/reference/test-encyclopedia#initial-conditions.
347*c8dee2aaSAndroid Build Coastguard Worker     std::string testUndeclaredOutputsDir;
348*c8dee2aaSAndroid Build Coastguard Worker     if (char* envVar = std::getenv("TEST_UNDECLARED_OUTPUTS_DIR")) {
349*c8dee2aaSAndroid Build Coastguard Worker         testUndeclaredOutputsDir = envVar;
350*c8dee2aaSAndroid Build Coastguard Worker     }
351*c8dee2aaSAndroid Build Coastguard Worker     bool isBazelTest = !testUndeclaredOutputsDir.empty();
352*c8dee2aaSAndroid Build Coastguard Worker 
353*c8dee2aaSAndroid Build Coastguard Worker     // Parse and validate flags.
354*c8dee2aaSAndroid Build Coastguard Worker     CommandLineFlags::Parse(argc, argv);
355*c8dee2aaSAndroid Build Coastguard Worker     if (!isBazelTest) {
356*c8dee2aaSAndroid Build Coastguard Worker         TestRunner::FlagValidators::StringNonEmpty("--outputDir", FLAGS_outputDir);
357*c8dee2aaSAndroid Build Coastguard Worker     }
358*c8dee2aaSAndroid Build Coastguard Worker     TestRunner::FlagValidators::StringAtMostOne("--outputDir", FLAGS_outputDir);
359*c8dee2aaSAndroid Build Coastguard Worker     TestRunner::FlagValidators::StringAtMostOne("--knownDigestsFile", FLAGS_knownDigestsFile);
360*c8dee2aaSAndroid Build Coastguard Worker     TestRunner::FlagValidators::StringEven("--key", FLAGS_key);
361*c8dee2aaSAndroid Build Coastguard Worker     TestRunner::FlagValidators::StringNonEmpty("--surfaceConfig", FLAGS_surfaceConfig);
362*c8dee2aaSAndroid Build Coastguard Worker     TestRunner::FlagValidators::StringAtMostOne("--surfaceConfig", FLAGS_surfaceConfig);
363*c8dee2aaSAndroid Build Coastguard Worker     TestRunner::FlagValidators::StringAtMostOne("--cpuName", FLAGS_cpuName);
364*c8dee2aaSAndroid Build Coastguard Worker     TestRunner::FlagValidators::StringAtMostOne("--gpuName", FLAGS_gpuName);
365*c8dee2aaSAndroid Build Coastguard Worker     TestRunner::FlagValidators::StringAtMostOne("--via", FLAGS_via);
366*c8dee2aaSAndroid Build Coastguard Worker 
367*c8dee2aaSAndroid Build Coastguard Worker     std::string outputDir =
368*c8dee2aaSAndroid Build Coastguard Worker             FLAGS_outputDir.isEmpty() ? testUndeclaredOutputsDir : FLAGS_outputDir[0];
369*c8dee2aaSAndroid Build Coastguard Worker 
370*c8dee2aaSAndroid Build Coastguard Worker     auto knownDigests = std::set<std::string>();
371*c8dee2aaSAndroid Build Coastguard Worker     if (!FLAGS_knownDigestsFile.isEmpty()) {
372*c8dee2aaSAndroid Build Coastguard Worker         knownDigests = read_known_digests_file(FLAGS_knownDigestsFile[0]);
373*c8dee2aaSAndroid Build Coastguard Worker         TestRunner::Log(
374*c8dee2aaSAndroid Build Coastguard Worker                 "Read %zu known digests from: %s", knownDigests.size(), FLAGS_knownDigestsFile[0]);
375*c8dee2aaSAndroid Build Coastguard Worker     }
376*c8dee2aaSAndroid Build Coastguard Worker 
377*c8dee2aaSAndroid Build Coastguard Worker     std::map<std::string, std::string> keyValuePairs;
378*c8dee2aaSAndroid Build Coastguard Worker     for (int i = 1; i < FLAGS_key.size(); i += 2) {
379*c8dee2aaSAndroid Build Coastguard Worker         keyValuePairs[FLAGS_key[i - 1]] = FLAGS_key[i];
380*c8dee2aaSAndroid Build Coastguard Worker     }
381*c8dee2aaSAndroid Build Coastguard Worker     std::string config = FLAGS_surfaceConfig[0];
382*c8dee2aaSAndroid Build Coastguard Worker     std::string cpuName = FLAGS_cpuName.isEmpty() ? "" : FLAGS_cpuName[0];
383*c8dee2aaSAndroid Build Coastguard Worker     std::string gpuName = FLAGS_gpuName.isEmpty() ? "" : FLAGS_gpuName[0];
384*c8dee2aaSAndroid Build Coastguard Worker 
385*c8dee2aaSAndroid Build Coastguard Worker     // Execute all GM registerer functions, then run all registered GMs.
386*c8dee2aaSAndroid Build Coastguard Worker     for (const skiagm::GMRegistererFn& f : skiagm::GMRegistererFnRegistry::Range()) {
387*c8dee2aaSAndroid Build Coastguard Worker         std::string errorMsg = f();
388*c8dee2aaSAndroid Build Coastguard Worker         if (errorMsg != "") {
389*c8dee2aaSAndroid Build Coastguard Worker             SK_ABORT("Error while gathering GMs: %s", errorMsg.c_str());
390*c8dee2aaSAndroid Build Coastguard Worker         }
391*c8dee2aaSAndroid Build Coastguard Worker     }
392*c8dee2aaSAndroid Build Coastguard Worker     for (const skiagm::GMFactory& f : skiagm::GMRegistry::Range()) {
393*c8dee2aaSAndroid Build Coastguard Worker         std::unique_ptr<skiagm::GM> gm = f();
394*c8dee2aaSAndroid Build Coastguard Worker 
395*c8dee2aaSAndroid Build Coastguard Worker         if (!TestRunner::ShouldRunTestCase(gm->getName().c_str(), FLAGS_match, FLAGS_skip)) {
396*c8dee2aaSAndroid Build Coastguard Worker             TestRunner::Log("Skipping %s", gm->getName().c_str());
397*c8dee2aaSAndroid Build Coastguard Worker             gNumSkippedGMs++;
398*c8dee2aaSAndroid Build Coastguard Worker             continue;
399*c8dee2aaSAndroid Build Coastguard Worker         }
400*c8dee2aaSAndroid Build Coastguard Worker 
401*c8dee2aaSAndroid Build Coastguard Worker         run_gm(std::move(gm), config, keyValuePairs, cpuName, gpuName, outputDir, knownDigests);
402*c8dee2aaSAndroid Build Coastguard Worker     }
403*c8dee2aaSAndroid Build Coastguard Worker 
404*c8dee2aaSAndroid Build Coastguard Worker     // TODO(lovisolo): If running under Bazel, print command to display output files.
405*c8dee2aaSAndroid Build Coastguard Worker 
406*c8dee2aaSAndroid Build Coastguard Worker     TestRunner::Log(gNumFailedGMs > 0 ? "FAIL" : "PASS");
407*c8dee2aaSAndroid Build Coastguard Worker     TestRunner::Log(
408*c8dee2aaSAndroid Build Coastguard Worker             "%d successful GMs (images written to %s).", gNumSuccessfulGMs, outputDir.c_str());
409*c8dee2aaSAndroid Build Coastguard Worker     TestRunner::Log("%d failed GMs.", gNumFailedGMs);
410*c8dee2aaSAndroid Build Coastguard Worker     TestRunner::Log("%d skipped GMs.", gNumSkippedGMs);
411*c8dee2aaSAndroid Build Coastguard Worker     return gNumFailedGMs > 0 ? 1 : 0;
412*c8dee2aaSAndroid Build Coastguard Worker }
413