Flutter Engine
The Flutter Engine
Classes | Functions | Variables
BazelGMTestRunner.cpp File Reference
#include "gm/gm.h"
#include "include/core/SkBitmap.h"
#include "include/core/SkCanvas.h"
#include "include/core/SkColorSpace.h"
#include "include/core/SkColorType.h"
#include "include/core/SkImageInfo.h"
#include "include/core/SkStream.h"
#include "include/core/SkSurface.h"
#include "include/encode/SkPngEncoder.h"
#include "include/private/base/SkAssert.h"
#include "include/private/base/SkDebug.h"
#include "src/core/SkMD5.h"
#include "src/utils/SkJSONWriter.h"
#include "src/utils/SkOSPath.h"
#include "tools/HashAndEncode.h"
#include "tools/testrunners/common/TestRunner.h"
#include "tools/testrunners/common/compilation_mode_keys/CompilationModeKeys.h"
#include "tools/testrunners/common/surface_manager/SurfaceManager.h"
#include "tools/testrunners/gm/vias/Draw.h"
#include <algorithm>
#include <ctime>
#include <filesystem>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <regex>
#include <set>
#include <sstream>
#include <string>

Go to the source code of this file.

Classes

struct  WritePNGAndJSONFilesResult
 

Functions

static DEFINE_string (skip, "", "Space-separated list of test cases (regexps) to skip.")
 
static DEFINE_string (match, "", "Space-separated list of test cases (regexps) to run. Will run all tests if omitted.")
 
static DEFINE_string (outputDir, "", "Directory where to write any output .png and .json files. " "Optional when running under Bazel " "(e.g. \"bazel test //path/to:test\") as it defaults to " "$TEST_UNDECLARED_OUTPUTS_DIR.")
 
static DEFINE_string (knownDigestsFile, "", "Plaintext file with one MD5 hash per line. This test runner will omit from " "the output directory any images with an MD5 hash in this file.")
 
static DEFINE_string (key, "", "Space-separated key/value pairs common to all traces.")
 
static DEFINE_string (surfaceConfig, "", "Name of the Surface configuration to use (e.g. \"8888\"). This determines " "how we construct the SkSurface from which we get the SkCanvas that GMs will " "draw on. See file //tools/testrunners/common/surface_manager/SurfaceManager.h for " "details.")
 
static DEFINE_string (cpuName, "", "Contents of the \"cpu_or_gpu_value\" dimension for CPU-bound traces (e.g. \"AVX512\").")
 
static DEFINE_string (gpuName, "", "Contents of the \"cpu_or_gpu_value\" dimension for GPU-bound traces (e.g. \"RTX3060\").")
 
static DEFINE_string (via, "direct", "Name of the \"via\" to use (e.g. \"picture_serialization\"). Optional.")
 
static WritePNGAndJSONFilesResult 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)
 
static std::string draw_result_to_string (skiagm::DrawResult result)
 
void 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)
 
std::set< std::string > read_known_digests_file (std::string path)
 
int main (int argc, char **argv)
 

Variables

static bool unused
 
static int gNumSuccessfulGMs = 0
 
static int gNumFailedGMs = 0
 
static int gNumSkippedGMs = 0
 
static bool gMissingCpuOrGpuWarningLogged = false
 

Function Documentation

◆ DEFINE_string() [1/9]

static DEFINE_string ( cpuName  ,
""  ,
"Contents of the \"cpu_or_gpu_value\" dimension for CPU-bound traces (e.g. \"AVX512\")."   
)
static

◆ DEFINE_string() [2/9]

static DEFINE_string ( gpuName  ,
""  ,
"Contents of the \"cpu_or_gpu_value\" dimension for GPU-bound traces (e.g. \"RTX3060\")."   
)
static

◆ DEFINE_string() [3/9]

static DEFINE_string ( key  ,
""  ,
"Space-separated key/value pairs common to all traces."   
)
static

◆ DEFINE_string() [4/9]

static DEFINE_string ( knownDigestsFile  ,
""  ,
"Plaintext file with one MD5 hash per line. This test runner will omit from " "the output directory any images with an MD5 hash in this file."   
)
static

◆ DEFINE_string() [5/9]

static DEFINE_string ( match  ,
""  ,
"Space-separated list of test cases (regexps) to run. Will run all tests if omitted."   
)
static

◆ DEFINE_string() [6/9]

static DEFINE_string ( outputDir  ,
""  ,
"Directory where to write any output .png and .json files. " "Optional when running under Bazel " "(e.g. \"bazel test //path/to:test\") as it defaults to " "$TEST_UNDECLARED_OUTPUTS_DIR."   
)
static

◆ DEFINE_string() [7/9]

static DEFINE_string ( skip  ,
""  ,
"Space-separated list of test cases (regexps) to skip."   
)
static

◆ DEFINE_string() [8/9]

static DEFINE_string ( surfaceConfig  ,
""  ,
"Name of the Surface configuration to use (e.g. \"8888\"). This determines " "how we construct the SkSurface from which we get the SkCanvas that GMs will " "draw on. See file //tools/testrunners/common/surface_manager/SurfaceManager.h for " "details."   
)
static

◆ DEFINE_string() [9/9]

static DEFINE_string ( via  ,
"direct"  ,
"Name of the \"via\" to use (e.g. \"picture_serialization\"). Optional."   
)
static

◆ draw_result_to_string()

static std::string draw_result_to_string ( skiagm::DrawResult  result)
static

Definition at line 189 of file BazelGMTestRunner.cpp.

189 {
190 switch (result) {
192 return "Ok";
194 return "Fail";
196 return "Skip";
197 default:
199 }
200}
#define SkUNREACHABLE
Definition: SkAssert.h:135
GAsyncResult * result

◆ main()

int main ( int  argc,
char **  argv 
)

Definition at line 340 of file BazelGMTestRunner.cpp.

340 {
342
343 // When running under Bazel (e.g. "bazel test //path/to:test"), we'll store output files in
344 // $TEST_UNDECLARED_OUTPUTS_DIR unless overridden via the --outputDir flag.
345 //
346 // See https://bazel.build/reference/test-encyclopedia#initial-conditions.
347 std::string testUndeclaredOutputsDir;
348 if (char* envVar = std::getenv("TEST_UNDECLARED_OUTPUTS_DIR")) {
349 testUndeclaredOutputsDir = envVar;
350 }
351 bool isBazelTest = !testUndeclaredOutputsDir.empty();
352
353 // Parse and validate flags.
355 if (!isBazelTest) {
356 TestRunner::FlagValidators::StringNonEmpty("--outputDir", FLAGS_outputDir);
357 }
358 TestRunner::FlagValidators::StringAtMostOne("--outputDir", FLAGS_outputDir);
359 TestRunner::FlagValidators::StringAtMostOne("--knownDigestsFile", FLAGS_knownDigestsFile);
360 TestRunner::FlagValidators::StringEven("--key", FLAGS_key);
361 TestRunner::FlagValidators::StringNonEmpty("--surfaceConfig", FLAGS_surfaceConfig);
362 TestRunner::FlagValidators::StringAtMostOne("--surfaceConfig", FLAGS_surfaceConfig);
363 TestRunner::FlagValidators::StringAtMostOne("--cpuName", FLAGS_cpuName);
364 TestRunner::FlagValidators::StringAtMostOne("--gpuName", FLAGS_gpuName);
366
367 std::string outputDir =
368 FLAGS_outputDir.isEmpty() ? testUndeclaredOutputsDir : FLAGS_outputDir[0];
369
370 auto knownDigests = std::set<std::string>();
371 if (!FLAGS_knownDigestsFile.isEmpty()) {
372 knownDigests = read_known_digests_file(FLAGS_knownDigestsFile[0]);
374 "Read %zu known digests from: %s", knownDigests.size(), FLAGS_knownDigestsFile[0]);
375 }
376
377 std::map<std::string, std::string> keyValuePairs;
378 for (int i = 1; i < FLAGS_key.size(); i += 2) {
379 keyValuePairs[FLAGS_key[i - 1]] = FLAGS_key[i];
380 }
381 std::string config = FLAGS_surfaceConfig[0];
382 std::string cpuName = FLAGS_cpuName.isEmpty() ? "" : FLAGS_cpuName[0];
383 std::string gpuName = FLAGS_gpuName.isEmpty() ? "" : FLAGS_gpuName[0];
384
385 // Execute all GM registerer functions, then run all registered GMs.
387 std::string errorMsg = f();
388 if (errorMsg != "") {
389 SK_ABORT("Error while gathering GMs: %s", errorMsg.c_str());
390 }
391 }
393 std::unique_ptr<skiagm::GM> gm = f();
394
395 if (!TestRunner::ShouldRunTestCase(gm->getName().c_str(), FLAGS_match, FLAGS_skip)) {
396 TestRunner::Log("Skipping %s", gm->getName().c_str());
398 continue;
399 }
400
401 run_gm(std::move(gm), config, keyValuePairs, cpuName, gpuName, outputDir, knownDigests);
402 }
403
404 // TODO(lovisolo): If running under Bazel, print command to display output files.
405
406 TestRunner::Log(gNumFailedGMs > 0 ? "FAIL" : "PASS");
408 "%d successful GMs (images written to %s).", gNumSuccessfulGMs, outputDir.c_str());
409 TestRunner::Log("%d failed GMs.", gNumFailedGMs);
410 TestRunner::Log("%d skipped GMs.", gNumSkippedGMs);
411 return gNumFailedGMs > 0 ? 1 : 0;
412}
void 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)
static int gNumSkippedGMs
static int gNumSuccessfulGMs
std::set< std::string > read_known_digests_file(std::string path)
static int gNumFailedGMs
#define SK_ABORT(message,...)
Definition: SkAssert.h:70
static void Parse(int argc, const char *const *argv)
char ** argv
Definition: library.h:9
void StringEven(std::string name, CommandLineFlags::StringArray flag)
Definition: TestRunner.cpp:31
void StringNonEmpty(std::string name, CommandLineFlags::StringArray flag)
Definition: TestRunner.cpp:17
void StringAtMostOne(std::string name, CommandLineFlags::StringArray flag)
Definition: TestRunner.cpp:24
bool ShouldRunTestCase(const char *name, CommandLineFlags::StringArray &matchFlag, CommandLineFlags::StringArray &skipFlag)
Definition: TestRunner.cpp:113
void Log(const char *format,...) SK_PRINTF_LIKE(1
Definition: TestRunner.cpp:137
void InitAndLogCmdlineArgs(int argc, char **argv)
Definition: TestRunner.cpp:88
std::function< std::unique_ptr< skiagm::GM >()> GMFactory
Definition: gm.h:239
std::function< std::string()> GMRegistererFn
Definition: gm.h:254

◆ read_known_digests_file()

std::set< std::string > read_known_digests_file ( std::string  path)

Definition at line 313 of file BazelGMTestRunner.cpp.

313 {
314 std::set<std::string> hashes;
315 std::regex md5HashRegex("^[a-fA-F0-9]{32}$");
316 std::ifstream f(path);
317 std::string line;
318 for (int lineNum = 1; std::getline(f, line); lineNum++) {
319 // Trim left and right (https://stackoverflow.com/a/217605).
320 auto isSpace = [](unsigned char c) { return !std::isspace(c); };
321 std::string md5 = line;
322 md5.erase(md5.begin(), std::find_if(md5.begin(), md5.end(), isSpace));
323 md5.erase(std::find_if(md5.rbegin(), md5.rend(), isSpace).base(), md5.end());
324
325 if (md5 == "") continue;
326
327 if (!std::regex_match(md5, md5HashRegex)) {
328 SK_ABORT(
329 "File '%s' passed via --knownDigestsFile contains an invalid entry on line "
330 "%d: '%s'",
331 path.c_str(),
332 lineNum,
333 line.c_str());
334 }
335 hashes.insert(md5);
336 }
337 return hashes;
338}
static SkMD5::Digest md5(const SkBitmap &bm)
Definition: CodecTest.cpp:77
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot The VM snapshot data that will be memory mapped as read only SnapshotAssetPath must be present isolate snapshot The isolate snapshot data that will be memory mapped as read only SnapshotAssetPath must be present cache dir path
Definition: switches.h:57

◆ run_gm()

void 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 
)

Definition at line 210 of file BazelGMTestRunner.cpp.

216 {
217 TestRunner::Log("GM: %s", gm->getName().c_str());
218
219 // Create surface and canvas.
220 std::unique_ptr<SurfaceManager> surfaceManager = SurfaceManager::FromConfig(
221 config, SurfaceOptions{gm->getISize().width(), gm->getISize().height()});
222 if (surfaceManager == nullptr) {
223 SK_ABORT("Unknown --surfaceConfig flag value: %s.", config.c_str());
224 }
225
226 // Print warning about missing cpu_or_gpu key if necessary.
227 if ((surfaceManager->isCpuOrGpuBound() == SurfaceManager::CpuOrGpu::kCPU && cpuName == "" &&
230 "\tWarning: The surface is CPU-bound, but flag --cpuName was not provided. "
231 "Gold traces will omit keys \"cpu_or_gpu\" and \"cpu_or_gpu_value\".");
233 }
234 if ((surfaceManager->isCpuOrGpuBound() == SurfaceManager::CpuOrGpu::kGPU && gpuName == "" &&
237 "\tWarning: The surface is GPU-bound, but flag --gpuName was not provided. "
238 "Gold traces will omit keys \"cpu_or_gpu\" and \"cpu_or_gpu_value\".");
240 }
241
242 // Set up GPU.
243 TestRunner::Log("\tSetting up GPU...");
244 SkString msg;
245 skiagm::DrawResult result = gm->gpuSetup(surfaceManager->getSurface()->getCanvas(), &msg);
246
247 // Draw GM into canvas if GPU setup was successful.
251 std::string viaName = FLAGS_via.size() == 0 ? "" : (FLAGS_via[0]);
252 TestRunner::Log("\tDrawing GM via \"%s\"...", viaName.c_str());
253 output = draw(gm.get(), surfaceManager->getSurface().get(), viaName);
254 result = output.result;
255 msg = SkString(output.msg.c_str());
256 bitmap = output.bitmap;
257 }
258
259 // Keep track of results. We will exit with a non-zero exit code in the case of failures.
260 switch (result) {
262 // We don't increment numSuccessfulGMs just yet. We still need to successfully save
263 // its output bitmap to disk.
264 TestRunner::Log("\tFlushing surface...");
265 surfaceManager->flush();
266 break;
269 break;
272 break;
273 default:
275 }
276
277 // Report GM result and optional message.
278 TestRunner::Log("\tResult: %s", draw_result_to_string(result).c_str());
279 if (!msg.isEmpty()) {
280 TestRunner::Log("\tMessage: \"%s\"", msg.c_str());
281 }
282
283 // Save PNG and JSON file with MD5 hash to disk if the GM was successful.
285 std::string name = std::string(gm->getName().c_str());
286 SkString pngPath = SkOSPath::Join(outputDir.c_str(), (name + ".png").c_str());
287 SkString jsonPath = SkOSPath::Join(outputDir.c_str(), (name + ".json").c_str());
288
289 WritePNGAndJSONFilesResult pngAndJSONResult =
290 write_png_and_json_files(gm->getName().c_str(),
291 keyValuePairs,
292 gm->getGoldKeys(),
293 surfaceManager->getGoldKeyValuePairs(cpuName, gpuName),
294 bitmap,
295 pngPath.c_str(),
296 jsonPath.c_str(),
297 knownDigests);
298 if (pngAndJSONResult.status == WritePNGAndJSONFilesResult::kError) {
299 TestRunner::Log("\tERROR: %s", pngAndJSONResult.errorMsg.c_str());
301 } else if (pngAndJSONResult.status == WritePNGAndJSONFilesResult::kSkippedKnownDigest) {
302 TestRunner::Log("\tSkipping known digest: %s", pngAndJSONResult.skippedDigest.c_str());
303 } else {
305 TestRunner::Log("\tPNG file written to: %s", pngPath.c_str());
306 TestRunner::Log("\tJSON file written to: %s", jsonPath.c_str());
307 }
308 }
309}
static bool gMissingCpuOrGpuWarningLogged
static WritePNGAndJSONFilesResult 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)
static std::string draw_result_to_string(skiagm::DrawResult result)
static void draw(SkCanvas *canvas, SkRect &target, int x, int y)
Definition: aaclip.cpp:27
static SkString Join(const char *rootPath, const char *relativePath)
Definition: SkOSPath.cpp:14
bool isEmpty() const
Definition: SkString.h:130
const char * c_str() const
Definition: SkString.h:133
static std::unique_ptr< SurfaceManager > FromConfig(std::string config, SurfaceOptions surfaceOptions)
Definition: bitmap.py:1
DEF_SWITCHES_START aot vmservice shared library name
Definition: switches.h:32
DrawResult
Definition: gm.h:104
int32_t width
Definition: Draw.h:18
enum WritePNGAndJSONFilesResult::@445 status

◆ write_png_and_json_files()

static WritePNGAndJSONFilesResult 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 
)
static

Definition at line 114 of file BazelGMTestRunner.cpp.

122 {
123 HashAndEncode hashAndEncode(bitmap);
124
125 // Compute MD5 hash.
126 SkMD5 hash;
127 hashAndEncode.feedHash(&hash);
128 SkMD5::Digest digest = hash.finish();
130
131 // Skip this digest if it's known.
132 if (knownDigests.find(md5.c_str()) != knownDigests.end()) {
133 return {
135 .skippedDigest = md5.c_str(),
136 };
137 }
138
139 // Write PNG file.
140 SkFILEWStream pngFile(pngPath);
141 bool result = hashAndEncode.encodePNG(&pngFile,
142 md5.c_str(),
144 /* properties= */ CommandLineFlags::StringArray());
145 if (!result) {
146 return {
148 .errorMsg = "Error encoding or writing PNG to " + std::string(pngPath),
149 };
150 }
151
152 // Validate GM-related Gold keys.
153 if (gmGoldKeys.find("name") == gmGoldKeys.end()) {
154 SK_ABORT("gmGoldKeys does not contain key \"name\"");
155 }
156 if (gmGoldKeys.find("source_type") == gmGoldKeys.end()) {
157 SK_ABORT("gmGoldKeys does not contain key \"source_type\"");
158 }
159
160 // Validate surface-related Gold keys.
161 if (surfaceGoldKeys.find("surface_config") == surfaceGoldKeys.end()) {
162 SK_ABORT("surfaceGoldKeys does not contain key \"surface_config\"");
163 }
164
165 // Gather all Gold keys.
166 std::map<std::string, std::string> keys = {
167 {"build_system", "bazel"},
168 };
170 keys.merge(commonKeys);
171 keys.merge(surfaceGoldKeys);
172 keys.merge(gmGoldKeys);
173
174 // Write JSON file with MD5 hash and Gold key-value pairs.
175 SkFILEWStream jsonFile(jsonPath);
176 SkJSONWriter jsonWriter(&jsonFile, SkJSONWriter::Mode::kPretty);
177 jsonWriter.beginObject(); // Root object.
178 jsonWriter.appendString("md5", md5);
179 jsonWriter.beginObject("keys"); // "keys" dictionary.
180 for (auto const& [param, value] : keys) {
181 jsonWriter.appendString(param.c_str(), SkString(value));
182 }
183 jsonWriter.endObject(); // "keys" dictionary.
184 jsonWriter.endObject(); // Root object.
185
186 return {.status = WritePNGAndJSONFilesResult::kSuccess};
187}
std::map< std::string, std::string > GetCompilationModeGoldAndPerfKeyValuePairs()
static uint32_t hash(const SkShaderBase::GradientInfo &v)
Definition: SkMD5.h:19
uint8_t value
SkString toLowercaseHexString() const
Definition: SkMD5.cpp:116

Variable Documentation

◆ gMissingCpuOrGpuWarningLogged

bool gMissingCpuOrGpuWarningLogged = false
static

Definition at line 206 of file BazelGMTestRunner.cpp.

◆ gNumFailedGMs

int gNumFailedGMs = 0
static

Definition at line 203 of file BazelGMTestRunner.cpp.

◆ gNumSkippedGMs

int gNumSkippedGMs = 0
static

Definition at line 204 of file BazelGMTestRunner.cpp.

◆ gNumSuccessfulGMs

int gNumSuccessfulGMs = 0
static

Definition at line 202 of file BazelGMTestRunner.cpp.

◆ unused

bool unused
static
Initial value:
=
SkFlagInfo::CreateStringFlag("device-specific-bazel-config",
nullptr,
nullptr,
"Ignored by this test runner.",
nullptr)
static bool CreateStringFlag(const char *name, const char *shortName, CommandLineFlags::StringArray *pStrings, const char *defaultValue, const char *helpString, const char *extendedHelpString)

Definition at line 98 of file BazelGMTestRunner.cpp.