ok
Change-Id: I9d01656a9d9b7aa3ab352dd4c168b26da620a903
Reviewed-on: https://skia-review.googlesource.com/9978
Reviewed-by: Herb Derby <herb@google.com>
Commit-Queue: Mike Klein <mtklein@chromium.org>
diff --git a/BUILD.gn b/BUILD.gn
index f8e6548..e187644 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -1149,6 +1149,16 @@
]
}
+ test_app("ok") {
+ sources = [
+ "tools/ok.cpp",
+ ]
+ deps = [
+ ":gm",
+ ":skia",
+ ]
+ }
+
if (!is_debug) { # I've benchmarked debug code once too many times...
test_app("monobench") {
sources = [
diff --git a/tools/ok.cpp b/tools/ok.cpp
new file mode 100644
index 0000000..75c3273
--- /dev/null
+++ b/tools/ok.cpp
@@ -0,0 +1,349 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+// ok is an experimental test harness, maybe to replace DM. Key features:
+// * work is balanced across separate processes for stability and isolation;
+// * ok is entirely opt-in. No more maintaining huge --blacklists.
+
+#include "SkGraphics.h"
+#include "SkOSFile.h"
+#include "SkPicture.h"
+#include "SkSurface.h"
+#include "gm.h"
+#include <deque>
+#include <functional>
+#include <future>
+#include <map>
+#include <memory>
+#include <regex>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <thread>
+
+enum class Status { OK, Failed, Crashed, Skipped, None };
+
+struct Engine {
+ virtual ~Engine() {}
+ virtual bool spawn(std::function<Status(void)>) = 0;
+ virtual Status wait_one() = 0;
+};
+
+struct SerialEngine : Engine {
+ Status last = Status::None;
+
+ bool spawn(std::function<Status(void)> fn) override {
+ last = fn();
+ return true;
+ }
+
+ Status wait_one() override {
+ Status s = last;
+ last = Status::None;
+ return s;
+ }
+};
+
+struct ThreadEngine : Engine {
+ std::deque<std::future<Status>> live;
+
+ bool spawn(std::function<Status(void)> fn) override {
+ live.push_back(std::async(std::launch::async, fn));
+ return true;
+ }
+
+ Status wait_one() override {
+ if (live.empty()) {
+ return Status::None;
+ }
+ Status s = live.front().get();
+ live.pop_front();
+ return s;
+ }
+};
+
+#if defined(_MSC_VER)
+ using ForkEngine = ThreadEngine;
+#else
+ #include <sys/wait.h>
+ #include <unistd.h>
+
+ struct ForkEngine : Engine {
+ bool spawn(std::function<Status(void)> fn) override {
+ switch (fork()) {
+ case 0: exit((int)fn());
+ case -1: return false;
+ default: return true;
+ }
+ }
+
+ Status wait_one() override {
+ do {
+ int status;
+ if (wait(&status) > 0) {
+ return WIFEXITED(status) ? (Status)WEXITSTATUS(status)
+ : Status::Crashed;
+ }
+ } while (errno == EINTR);
+ return Status::None;
+ }
+ };
+#endif
+
+struct Src {
+ virtual ~Src() {}
+ virtual std::string name() = 0;
+ virtual SkISize size() = 0;
+ virtual void draw(SkCanvas*) = 0;
+};
+
+struct Stream {
+ virtual ~Stream() {}
+ virtual std::unique_ptr<Src> next() = 0;
+};
+
+struct Options {
+ std::map<std::string, std::string> kv;
+
+ explicit Options(std::string str) {
+ std::string k,v, *curr = &k;
+ for (auto c : str) {
+ switch(c) {
+ case ',': kv[k] = v;
+ curr = &(k = "");
+ break;
+ case '=': curr = &(v = "");
+ break;
+ default: *curr += c;
+ }
+ }
+ kv[k] = v;
+ }
+
+ std::string lookup(std::string k, std::string fallback) {
+ for (auto it = kv.find(k); it != kv.end(); ) {
+ return it->second;
+ }
+ return fallback;
+ }
+};
+
+struct GMStream : Stream {
+ const skiagm::GMRegistry* registry = skiagm::GMRegistry::Head();
+
+ struct GMSrc : Src {
+ skiagm::GM* (*factory)(void*);
+ std::unique_ptr<skiagm::GM> gm;
+
+ std::string name() override {
+ gm.reset(factory(nullptr));
+ return gm->getName();
+ }
+
+ SkISize size() override {
+ return gm->getISize();
+ }
+
+ void draw(SkCanvas* canvas) override {
+ canvas->clear(0xffffffff);
+ canvas->concat(gm->getInitialTransform());
+ gm->draw(canvas);
+ }
+ };
+
+ std::unique_ptr<Src> next() override {
+ if (!registry) {
+ return nullptr;
+ }
+ GMSrc src;
+ src.factory = registry->factory();
+ registry = registry->next();
+ return std::unique_ptr<Src>{new GMSrc{std::move(src)}};
+ }
+};
+
+struct SKPStream : Stream {
+ std::string dir;
+ std::vector<std::string> skps;
+
+ explicit SKPStream(Options options) : dir(options.lookup("dir", "skps")) {
+ SkOSFile::Iter it{dir.c_str(), ".skp"};
+ for (SkString path; it.next(&path); ) {
+ skps.push_back(path.c_str());
+ }
+ }
+
+ struct SKPSrc : Src {
+ std::string dir, path;
+ sk_sp<SkPicture> pic;
+
+ std::string name() override {
+ return path;
+ }
+
+ SkISize size() override {
+ auto skp = SkData::MakeFromFileName((dir+"/"+path).c_str());
+ pic = SkPicture::MakeFromData(skp.get());
+ return pic->cullRect().roundOut().size();
+ }
+
+ void draw(SkCanvas* canvas) override {
+ canvas->clear(0xffffffff);
+ pic->playback(canvas);
+ }
+ };
+
+ std::unique_ptr<Src> next() override {
+ if (skps.empty()) {
+ return nullptr;
+ }
+ SKPSrc src;
+ src.dir = dir;
+ src.path = skps.back();
+ skps.pop_back();
+ return std::unique_ptr<Src>{new SKPSrc{std::move(src)}};
+ }
+};
+
+struct {
+ const char* name;
+ std::unique_ptr<Stream> (*factory)(Options);
+} streams[] = {
+ {"gm", [](Options options) { return std::unique_ptr<Stream>{new GMStream}; }},
+ {"skp", [](Options options) { return std::unique_ptr<Stream>{new SKPStream{options}}; }},
+};
+
+int main(int argc, char** argv) {
+ SkGraphics::Init();
+
+ int jobs {1};
+ std::regex match {".*"};
+ std::regex search {".*"};
+ std::string write_dir {""};
+
+ std::unique_ptr<Stream> stream;
+
+ auto help = [&] {
+ std::string stream_types;
+ for (auto st : streams) {
+ if (!stream_types.empty()) {
+ stream_types += ", ";
+ }
+ stream_types += st.name;
+ }
+
+ printf("%s [-j N] [-m regex] [-s regex] [-w dir] [-h] stream[:k=v,k=v,...] \n"
+ " -j: Run at most N processes at any time. \n"
+ " If <0, use -N threads instead. \n"
+ " If 0, use one thread in one process. \n"
+ " If 1 (default) or -1, auto-detect N. \n"
+ " -m: Run only names matching regex exactly. \n"
+ " -s: Run only names matching regex anywhere. \n"
+ " -w: If set, write .pngs into dir. \n"
+ " -h: Print this message and exit. \n"
+ " stream: content to draw: %s \n"
+ " Some streams have options, e.g. skp:dir=skps \n",
+ argv[0], stream_types.c_str());
+ return 1;
+ };
+
+ for (int i = 1; i < argc; i++) {
+ if (0 == strcmp("-j", argv[i])) { jobs = atoi(argv[++i]); }
+ if (0 == strcmp("-m", argv[i])) { match = argv[++i] ; }
+ if (0 == strcmp("-s", argv[i])) { search = argv[++i] ; }
+ if (0 == strcmp("-w", argv[i])) { write_dir = argv[++i] ; }
+ if (0 == strcmp("-h", argv[i])) { return help(); }
+
+ for (auto st : streams) {
+ size_t len = strlen(st.name);
+ if (0 == strncmp(st.name, argv[i], len)) {
+ switch (argv[i][len]) {
+ case ':': len++;
+ case '\0': stream = st.factory(Options{argv[i]+len});
+ }
+ }
+ }
+ }
+ if (!stream) { return help(); }
+
+ std::unique_ptr<Engine> engine;
+ if (jobs == 0) { engine.reset(new SerialEngine); }
+ if (jobs > 0) { engine.reset(new ForkEngine); }
+ if (jobs < 0) { engine.reset(new ThreadEngine); jobs = -jobs; }
+
+ if (jobs == 1) { jobs = std::thread::hardware_concurrency(); }
+
+ if (!write_dir.empty()) {
+ sk_mkdir(write_dir.c_str());
+ }
+
+ int ok = 0, failed = 0, crashed = 0, skipped = 0;
+
+ auto update_stats = [&](Status s) {
+ switch (s) {
+ case Status::OK: ok++; break;
+ case Status::Failed: failed++; break;
+ case Status::Crashed: crashed++; break;
+ case Status::Skipped: skipped++; break;
+ case Status::None: return;
+ }
+ const char* leader = "\r";
+ auto print = [&](int count, const char* label) {
+ if (count) {
+ printf("%s%d %s", leader, count, label);
+ leader = ", ";
+ }
+ };
+ print(ok, "ok");
+ print(failed, "failed");
+ print(crashed, "crashed");
+ print(skipped, "skipped");
+ fflush(stdout);
+ };
+
+ auto spawn = [&](std::function<Status(void)> fn) {
+ if (--jobs < 0) {
+ update_stats(engine->wait_one());
+ }
+ while (!engine->spawn(fn)) {
+ update_stats(engine->wait_one());
+ }
+ };
+
+ for (std::unique_ptr<Src> owned = stream->next(); owned; owned = stream->next()) {
+ Src* raw = owned.release(); // Can't move std::unique_ptr into a lambda in C++11. :(
+ spawn([=] {
+ std::unique_ptr<Src> src{raw};
+
+ auto name = src->name();
+ if (!std::regex_match (name, match) ||
+ !std::regex_search(name, search)) {
+ return Status::Skipped;
+ }
+
+ auto size = src->size();
+ auto surface = SkSurface::MakeRasterN32Premul(size.width(), size.height());
+ src->draw(surface->getCanvas());
+
+ if (!write_dir.empty()) {
+ auto image = surface->makeImageSnapshot();
+ sk_sp<SkData> png{image->encode()};
+
+ std::string path = write_dir + "/" + name + ".png";
+ SkFILEWStream{path.c_str()}.write(png->data(), png->size());
+ }
+ return Status::OK;
+ });
+ }
+
+ for (Status s = Status::OK; s != Status::None; ) {
+ s = engine->wait_one();
+ update_stats(s);
+ }
+ printf("\n");
+ return (failed || crashed) ? 1 : 0;
+}