Add option to gm: write out images into a hierarchy, rather than a flat set of files
BUG=https://code.google.com/p/skia/issues/detail?id=743
Review URL: https://codereview.appspot.com/6810047

git-svn-id: http://skia.googlecode.com/svn/trunk@6167 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/gm/gmmain.cpp b/gm/gmmain.cpp
index bf03214..742fb46 100644
--- a/gm/gmmain.cpp
+++ b/gm/gmmain.cpp
@@ -16,6 +16,7 @@
 #include "SkGraphics.h"
 #include "SkImageDecoder.h"
 #include "SkImageEncoder.h"
+#include "SkOSFile.h"
 #include "SkPicture.h"
 #include "SkRefCnt.h"
 #include "SkStream.h"
@@ -64,13 +65,6 @@
 const static ErrorBitfield ERROR_READING_REFERENCE_IMAGE = 0x08;
 const static ErrorBitfield ERROR_WRITING_REFERENCE_IMAGE = 0x10;
 
-// TODO: This should be defined as "\\" on Windows, but this is the way this
-// file has been working for a long time. We can fix it later.
-const static char* PATH_SEPARATOR = "/";
-
-// If true, emit a messange when we can't find a reference image to compare
-static bool gNotifyMissingReadReference;
-
 using namespace skiagm;
 
 class Iter {
@@ -156,9 +150,20 @@
 
 class GMMain {
 public:
-    static SkString make_name(const char shortName[], const char configName[]) {
-        SkString name(shortName);
-        name.appendf("_%s", configName);
+    GMMain() {
+        // Set default values of member variables, which tool_main()
+        // may override.
+        fNotifyMissingReadReference = true;
+        fUseFileHierarchy = false;
+    }
+
+    SkString make_name(const char shortName[], const char configName[]) {
+        SkString name;
+        if (fUseFileHierarchy) {
+            name.appendf("%s%c%s", configName, SkPATH_SEPARATOR, shortName);
+        } else {
+            name.appendf("%s_%s", shortName, configName);
+        }
         return name;
     }
 
@@ -167,12 +172,11 @@
                                   const SkString& name,
                                   const char suffix[]) {
         SkString filename(path);
-        if (filename.endsWith(PATH_SEPARATOR)) {
+        if (filename.endsWith(SkPATH_SEPARATOR)) {
             filename.remove(filename.size() - 1, 1);
         }
-        filename.append(pathSuffix);
-        filename.append(PATH_SEPARATOR);
-        filename.appendf("%s.%s", name.c_str(), suffix);
+        filename.appendf("%s%c%s.%s", pathSuffix, SkPATH_SEPARATOR,
+                         name.c_str(), suffix);
         return filename;
     }
 
@@ -489,7 +493,7 @@
     // Returns a description of the difference between "bitmap" and
     // the reference bitmap, or ERROR_READING_REFERENCE_IMAGE if
     // unable to read the reference bitmap from disk.
-    static ErrorBitfield compare_to_reference_image_on_disk(
+    ErrorBitfield compare_to_reference_image_on_disk(
       const char readPath [], const SkString& name, SkBitmap &bitmap,
       const char diffPath [], const char renderModeDescriptor []) {
         SkString path = make_filename(readPath, "", name, "png");
@@ -503,7 +507,7 @@
                                                         diffPath,
                                                         renderModeDescriptor);
         } else {
-            if (gNotifyMissingReadReference) {
+            if (fNotifyMissingReadReference) {
                 fprintf(stderr, "FAILED to read %s\n", path.c_str());
             }
             return ERROR_READING_REFERENCE_IMAGE;
@@ -515,15 +519,15 @@
     // both NULL (and thus no images are read from or written to disk).
     // So I don't trust that the renderModeDescriptor is being used for
     // anything other than debug output these days.
-    static ErrorBitfield handle_test_results(GM* gm,
-                                             const ConfigData& gRec,
-                                             const char writePath [],
-                                             const char readPath [],
-                                             const char diffPath [],
-                                             const char renderModeDescriptor [],
-                                             SkBitmap& bitmap,
-                                             SkDynamicMemoryWStream* pdf,
-                                             const SkBitmap* referenceBitmap) {
+    ErrorBitfield handle_test_results(GM* gm,
+                                      const ConfigData& gRec,
+                                      const char writePath [],
+                                      const char readPath [],
+                                      const char diffPath [],
+                                      const char renderModeDescriptor [],
+                                      SkBitmap& bitmap,
+                                      SkDynamicMemoryWStream* pdf,
+                                      const SkBitmap* referenceBitmap) {
         SkString name = make_name(gm->shortName(), gRec.fName);
         ErrorBitfield retval = ERROR_NONE;
 
@@ -580,14 +584,14 @@
     // Test: draw into a bitmap or pdf.
     // Depending on flags, possibly compare to an expected image
     // and possibly output a diff image if it fails to match.
-    static ErrorBitfield test_drawing(GM* gm,
-                                      const ConfigData& gRec,
-                                      const char writePath [],
-                                      const char readPath [],
-                                      const char diffPath [],
-                                      GrContext* context,
-                                      GrRenderTarget* rt,
-                                      SkBitmap* bitmap) {
+    ErrorBitfield test_drawing(GM* gm,
+                               const ConfigData& gRec,
+                               const char writePath [],
+                               const char readPath [],
+                               const char diffPath [],
+                               GrContext* context,
+                               GrRenderTarget* rt,
+                               SkBitmap* bitmap) {
         SkDynamicMemoryWStream document;
 
         if (gRec.fBackend == kRaster_Backend ||
@@ -612,12 +616,12 @@
                                    "", *bitmap, &document, NULL);
     }
 
-    static ErrorBitfield test_deferred_drawing(GM* gm,
-                                               const ConfigData& gRec,
-                                               const SkBitmap& referenceBitmap,
-                                               const char diffPath [],
-                                               GrContext* context,
-                                               GrRenderTarget* rt) {
+    ErrorBitfield test_deferred_drawing(GM* gm,
+                                        const ConfigData& gRec,
+                                        const SkBitmap& referenceBitmap,
+                                        const char diffPath [],
+                                        GrContext* context,
+                                        GrRenderTarget* rt) {
         SkDynamicMemoryWStream document;
 
         if (gRec.fBackend == kRaster_Backend ||
@@ -635,11 +639,11 @@
         return ERROR_NONE;
     }
 
-    static ErrorBitfield test_pipe_playback(GM* gm,
-                                            const ConfigData& gRec,
-                                            const SkBitmap& referenceBitmap,
-                                            const char readPath [],
-                                            const char diffPath []) {
+    ErrorBitfield test_pipe_playback(GM* gm,
+                                     const ConfigData& gRec,
+                                     const SkBitmap& referenceBitmap,
+                                     const char readPath [],
+                                     const char diffPath []) {
         ErrorBitfield errors = ERROR_NONE;
         for (size_t i = 0; i < SK_ARRAY_COUNT(gPipeWritingFlagCombos); ++i) {
             SkBitmap bitmap;
@@ -664,7 +668,7 @@
         return errors;
     }
 
-    static ErrorBitfield test_tiled_pipe_playback(
+    ErrorBitfield test_tiled_pipe_playback(
       GM* gm, const ConfigData& gRec, const SkBitmap& referenceBitmap,
       const char readPath [], const char diffPath []) {
         ErrorBitfield errors = ERROR_NONE;
@@ -690,6 +694,17 @@
         }
         return errors;
     }
+
+    //
+    // member variables.
+    // They are public for now, to allow easier setting by tool_main().
+    //
+
+    // if true, emit a message when we can't find a reference image to compare
+    bool fNotifyMissingReadReference;
+
+    bool fUseFileHierarchy;
+
 }; // end of GMMain class definition
 
 #if SK_SUPPORT_GPU
@@ -733,6 +748,8 @@
 };
 
 static void usage(const char * argv0) {
+    // TODO: rearrange into alphabetical order
+    // TODO: add documentation for missing options
     SkDebugf("%s\n", argv0);
     SkDebugf("    [-w writePath] [-r readPath] [-d diffPath] [-i resourcePath]\n");
     SkDebugf("    [-wp writePicturePath]\n");
@@ -745,7 +762,7 @@
     }
     SkDebugf(" ]\n");
     SkDebugf("    [--noreplay] [--nopipe] [--noserialize] [--forceBWtext] [--nopdf] \n"
-             "    [--tiledPipe] \n"
+             "    [--tiledPipe] [--hierarchy | --nohierarchy]\n"
              "    [--nodeferred] [--match substring] [--notexturecache]\n"
              "    [-h|--help]\n"
              );
@@ -762,6 +779,7 @@
     SkDebugf(
              "    --noserialize: do not exercise SkPicture serialization & deserialization.\n");
     SkDebugf("    --forceBWtext: disable text anti-aliasing.\n");
+    SkDebugf("    --hierarchy: use multilevel directory structure when reading/writing files.\n");
     SkDebugf("    --nopdf: skip the pdf rendering test pass.\n");
     SkDebugf("    --nodeferred: skip the deferred rendering test pass.\n");
     SkDebugf("    --match foo: will only run tests that substring match foo.\n");
@@ -872,11 +890,10 @@
     int moduloIndex = -1;
     int moduloCount = -1;
 
-    gNotifyMissingReadReference = true;
-
     const char* const commandName = argv[0];
     char* const* stop = argv + argc;
     for (++argv; argv < stop; ++argv) {
+        // TODO: rearrange options into alphabetical order
         if (strcmp(*argv, "-w") == 0) {
             argv++;
             if (argv < stop && **argv) {
@@ -927,9 +944,13 @@
             }
             moduloCount = atoi(*argv);
         } else if (strcmp(*argv, "--disable-missing-warning") == 0) {
-            gNotifyMissingReadReference = false;
+            gmmain.fNotifyMissingReadReference = false;
         } else if (strcmp(*argv, "--enable-missing-warning") == 0) {
-            gNotifyMissingReadReference = true;
+            gmmain.fNotifyMissingReadReference = true;
+        } else if (strcmp(*argv, "--hierarchy") == 0) {
+            gmmain.fUseFileHierarchy = true;
+        } else if (strcmp(*argv, "--nohierarchy") == 0) {
+            gmmain.fUseFileHierarchy = false;
         } else if (strcmp(*argv, "--serialize") == 0) {
             // Leaving in this option so that a user need not modify
             // their command line arguments to still run.
@@ -1023,6 +1044,24 @@
     int gmIndex = -1;
     SkString moduloStr;
 
+    // If we will be writing out files, prepare subdirectories.
+    if (writePath) {
+        if (!sk_mkdir(writePath)) {
+            return -1;
+        }
+        if (gmmain.fUseFileHierarchy) {
+            for (int i = 0; i < configs.count(); i++) {
+                ConfigData config = gRec[configs[i]];
+                SkString subdir;
+                subdir.appendf("%s%c%s", writePath, SkPATH_SEPARATOR,
+                               config.fName);
+                if (!sk_mkdir(subdir.c_str())) {
+                    return -1;
+                }
+            }
+        }
+    }
+
     Iter iter;
     GM* gm;
     while ((gm = iter.next()) != NULL) {
@@ -1050,6 +1089,7 @@
 
         for (int i = 0; i < configs.count(); i++) {
             ConfigData config = gRec[configs[i]];
+
             // Skip any tests that we don't even need to try.
             if ((kPDF_Backend == config.fBackend) &&
                 (!doPDF || (gmFlags & GM::kSkipPDF_Flag)))