Add option to gm: write out images into a hierarchy, rather than a flat set of files
authorepoger@google.com <epoger@google.com@2bbb7eff-a529-9590-31e7-b0007b416f81>
Mon, 29 Oct 2012 16:42:11 +0000 (16:42 +0000)
committerepoger@google.com <epoger@google.com@2bbb7eff-a529-9590-31e7-b0007b416f81>
Mon, 29 Oct 2012 16:42:11 +0000 (16:42 +0000)
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

gm/gmmain.cpp
include/core/SkOSFile.h
include/core/SkString.h
src/core/SkString.cpp
src/ports/SkOSFile_stdio.cpp
tests/StringTest.cpp

index bf03214..742fb46 100644 (file)
@@ -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_DIMENSION_MISMATCH      = 0x04;
 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 @@ static PipeFlagComboData gPipeWritingFlagCombos[] = {
 
 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 @@ public:
                                   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 @@ public:
     // 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 @@ public:
                                                         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 @@ public:
     // 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 @@ public:
     // 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 @@ public:
                                    "", *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 @@ public:
         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 @@ public:
         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 @@ public:
         }
         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 const ConfigData gRec[] = {
 };
 
 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 @@ static void usage(const char * argv0) {
     }
     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 @@ static void usage(const char * argv0) {
     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 tool_main(int argc, char** argv) {
     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 @@ int tool_main(int argc, char** argv) {
             }
             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 tool_main(int argc, char** argv) {
     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 @@ int tool_main(int argc, char** argv) {
 
         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)))
index b5477cd..79551ae 100644 (file)
@@ -7,7 +7,8 @@
  */
 
 
-//
+// TODO: add unittests for all these operations
+
 #ifndef SkOSFile_DEFINED
 #define SkOSFile_DEFINED
 
@@ -24,6 +25,12 @@ enum SkFILE_Flags {
     kWrite_SkFILE_Flag  = 0x02
 };
 
+#ifdef _WIN32
+const static char SkPATH_SEPARATOR = '\\';
+#else
+const static char SkPATH_SEPARATOR = '/';
+#endif
+
 SkFILE* sk_fopen(const char path[], SkFILE_Flags);
 void    sk_fclose(SkFILE*);
 
@@ -39,6 +46,17 @@ void    sk_fflush(SkFILE*);
 int     sk_fseek( SkFILE*, size_t, int );
 size_t  sk_ftell( SkFILE* );
 
+// Returns true if something (file, directory, ???) exists at this path.
+bool    sk_exists(const char *path);
+
+// Returns true if a directory exists at this path.
+bool    sk_isdir(const char *path);
+
+// Create a new directory at this path; returns true if successful.
+// If the directory already existed, this will return true.
+// Description of the error, if any, will be written to stderr.
+bool    sk_mkdir(const char* path);
+
 class SkOSFile {
 public:
     class Iter {
@@ -79,4 +97,3 @@ private:
 };
 
 #endif
-
index 20f16c4..94dcf8b 100644 (file)
 /*  Some helper functions for C strings
 */
 
-static bool SkStrStartsWith(const char string[], const char prefix[]) {
+static bool SkStrStartsWith(const char string[], const char prefixStr[]) {
     SkASSERT(string);
-    SkASSERT(prefix);
-    return !strncmp(string, prefix, strlen(prefix));
+    SkASSERT(prefixStr);
+    return !strncmp(string, prefixStr, strlen(prefixStr));
 }
-bool SkStrEndsWith(const char string[], const char suffix[]);
+static bool SkStrStartsWith(const char string[], const char prefixChar) {
+    SkASSERT(string);
+    return (prefixChar == *string);
+}
+
+bool SkStrEndsWith(const char string[], const char suffixStr[]);
+bool SkStrEndsWith(const char string[], const char suffixChar);
+
 int SkStrStartsWithOneOf(const char string[], const char prefixes[]);
+
 static bool SkStrContains(const char string[], const char substring[]) {
     SkASSERT(string);
     SkASSERT(substring);
     return (NULL != strstr(string, substring));
 }
+static bool SkStrContains(const char string[], const char subchar) {
+    SkASSERT(string);
+    return (NULL != strchr(string, subchar));
+}
 
 #define SkStrAppendS32_MaxSize  11
 char*   SkStrAppendS32(char buffer[], int32_t);
@@ -82,15 +94,24 @@ public:
     bool equals(const char text[]) const;
     bool equals(const char text[], size_t len) const;
 
-    bool startsWith(const char prefix[]) const {
-        return SkStrStartsWith(fRec->data(), prefix);
+    bool startsWith(const char prefixStr[]) const {
+        return SkStrStartsWith(fRec->data(), prefixStr);
+    }
+    bool startsWith(const char prefixChar) const {
+        return SkStrStartsWith(fRec->data(), prefixChar);
     }
-    bool endsWith(const char suffix[]) const {
-        return SkStrEndsWith(fRec->data(), suffix);
+    bool endsWith(const char suffixStr[]) const {
+        return SkStrEndsWith(fRec->data(), suffixStr);
+    }
+    bool endsWith(const char suffixChar) const {
+        return SkStrEndsWith(fRec->data(), suffixChar);
     }
     bool contains(const char substring[]) const {
         return SkStrContains(fRec->data(), substring);
     }
+    bool contains(const char subchar) const {
+        return SkStrContains(fRec->data(), subchar);
+    }
 
     friend bool operator==(const SkString& a, const SkString& b) {
         return a.equals(b);
index fee614c..5d1b8ce 100644 (file)
@@ -36,13 +36,23 @@ static const size_t kBufferSize = 512;
 
 ///////////////////////////////////////////////////////////////////////////////
 
-bool SkStrEndsWith(const char string[], const char suffix[]) {
+bool SkStrEndsWith(const char string[], const char suffixStr[]) {
     SkASSERT(string);
-    SkASSERT(suffix);
+    SkASSERT(suffixStr);
     size_t  strLen = strlen(string);
-    size_t  suffixLen = strlen(suffix);
+    size_t  suffixLen = strlen(suffixStr);
     return  strLen >= suffixLen &&
-            !strncmp(string + strLen - suffixLen, suffix, suffixLen);
+            !strncmp(string + strLen - suffixLen, suffixStr, suffixLen);
+}
+
+bool SkStrEndsWith(const char string[], const char suffixChar) {
+    SkASSERT(string);
+    size_t  strLen = strlen(string);
+    if (0 == strLen) {
+        return false;
+    } else {
+        return (suffixChar == string[strLen-1]);
+    }
 }
 
 int SkStrStartsWithOneOf(const char string[], const char prefixes[]) {
@@ -602,4 +612,3 @@ SkString SkStringPrintf(const char* format, ...) {
 
 #undef VSNPRINTF
 #undef SNPRINTF
-
index 1254394..6c825b4 100644 (file)
@@ -9,8 +9,17 @@
 
 #include "SkOSFile.h"
 
-#include <stdio.h>
 #include <errno.h>
+#include <stdio.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#ifdef _WIN32
+#include <direct.h>
+#include <io.h>
+#else
+#include <unistd.h>
+#endif
 
 SkFILE* sk_fopen(const char path[], SkFILE_Flags flags)
 {
@@ -98,3 +107,46 @@ void sk_fclose(SkFILE* f)
     ::fclose((FILE*)f);
 }
 
+bool sk_exists(const char *path)
+{
+#ifdef _WIN32
+    return (0 == _access(path, 0));
+#else
+    return (0 == access(path, 0));
+#endif
+}
+
+bool sk_isdir(const char *path)
+{
+    struct stat status;
+    if (0 != stat(path, &status)) {
+        return false;
+    }
+    return (status.st_mode & S_IFDIR);
+}
+
+bool sk_mkdir(const char* path)
+{
+    if (sk_isdir(path)) {
+        return true;
+    }
+    if (sk_exists(path)) {
+        fprintf(stderr,
+                "sk_mkdir: path '%s' already exists but is not a directory\n",
+                path);
+        return false;
+    }
+
+    int retval;
+#ifdef _WIN32
+    retval = _mkdir(path);
+#else
+    retval = mkdir(path, 0777);
+#endif
+    if (0 == retval) {
+        return true;
+    } else {
+        fprintf(stderr, "sk_mkdir: error %d creating dir '%s'\n", errno, path);
+        return false;
+    }
+}
index 5ae718f..2c5026c 100644 (file)
@@ -57,10 +57,14 @@ static void TestString(skiatest::Reporter* reporter) {
     REPORTER_ASSERT(reporter, !a.equals("help"));
 
     REPORTER_ASSERT(reporter,  a.startsWith("hell"));
+    REPORTER_ASSERT(reporter,  a.startsWith('h'));
     REPORTER_ASSERT(reporter, !a.startsWith( "ell"));
+    REPORTER_ASSERT(reporter, !a.startsWith( 'e'));
     REPORTER_ASSERT(reporter,  a.startsWith(""));
     REPORTER_ASSERT(reporter,  a.endsWith("llo"));
+    REPORTER_ASSERT(reporter,  a.endsWith('o'));
     REPORTER_ASSERT(reporter, !a.endsWith("ll" ));
+    REPORTER_ASSERT(reporter, !a.endsWith('l'));
     REPORTER_ASSERT(reporter,  a.endsWith(""));
     REPORTER_ASSERT(reporter,  a.contains("he"));
     REPORTER_ASSERT(reporter,  a.contains("ll"));
@@ -68,6 +72,8 @@ static void TestString(skiatest::Reporter* reporter) {
     REPORTER_ASSERT(reporter,  a.contains("hello"));
     REPORTER_ASSERT(reporter, !a.contains("hellohello"));
     REPORTER_ASSERT(reporter,  a.contains(""));
+    REPORTER_ASSERT(reporter,  a.contains('e'));
+    REPORTER_ASSERT(reporter, !a.contains('z'));
 
     SkString    e(a);
     SkString    f("hello");