aboutsummaryrefslogtreecommitdiffhomepage
path: root/gm/gm_expectations.h
blob: 2efdd4b0437714e1b412d03646fc7fb6d8a4f449 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
/*
 * Copyright 2013 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */
#ifndef gm_expectations_DEFINED
#define gm_expectations_DEFINED

#include <stdarg.h>
#include "gm.h"
#include "SkBitmap.h"
#include "SkBitmapChecksummer.h"
#include "SkData.h"
#include "SkImageDecoder.h"
#include "SkOSFile.h"
#include "SkRefCnt.h"
#include "SkStream.h"
#include "SkTArray.h"

#ifdef SK_BUILD_FOR_WIN
    // json includes xlocale which generates warning 4530 because we're compiling without
    // exceptions; see https://code.google.com/p/skia/issues/detail?id=1067
    #pragma warning(push)
    #pragma warning(disable : 4530)
#endif
#include "json/reader.h"
#include "json/value.h"
#ifdef SK_BUILD_FOR_WIN
    #pragma warning(pop)
#endif

#define DEBUGFAIL_SEE_STDERR SkDEBUGFAIL("see stderr for message")

const static char kJsonKey_ActualResults[]   = "actual-results";
const static char kJsonKey_ActualResults_Failed[]        = "failed";
const static char kJsonKey_ActualResults_FailureIgnored[]= "failure-ignored";
const static char kJsonKey_ActualResults_NoComparison[]  = "no-comparison";
const static char kJsonKey_ActualResults_Succeeded[]     = "succeeded";
const static char kJsonKey_ActualResults_AnyStatus_Checksum[]    = "checksum";

const static char kJsonKey_ExpectedResults[] = "expected-results";
const static char kJsonKey_ExpectedResults_Checksums[]     = "checksums";
const static char kJsonKey_ExpectedResults_IgnoreFailure[] = "ignore-failure";

namespace skiagm {

    // The actual type we use to represent a checksum is hidden in here.
    typedef Json::UInt64 Checksum;
    static inline Json::Value asJsonValue(Checksum checksum) {
        return checksum;
    }
    static inline Checksum asChecksum(Json::Value jsonValue) {
        return jsonValue.asUInt64();
    }

    static void gm_fprintf(FILE *stream, const char format[], ...) {
        va_list args;
        va_start(args, format);
        fprintf(stream, "GM: ");
        vfprintf(stream, format, args);
        va_end(args);
    }

    static SkString make_filename(const char path[],
                                  const char renderModeDescriptor[],
                                  const char *name,
                                  const char suffix[]) {
        SkString filename(path);
        if (filename.endsWith(SkPATH_SEPARATOR)) {
            filename.remove(filename.size() - 1, 1);
        }
        filename.appendf("%c%s%s.%s", SkPATH_SEPARATOR,
                         name, renderModeDescriptor, suffix);
        return filename;
    }

    /**
     * Test expectations (allowed image checksums, etc.)
     */
    class Expectations {
    public:
        /**
         * No expectations at all.
         */
        Expectations(bool ignoreFailure=kDefaultIgnoreFailure) {
            fIgnoreFailure = ignoreFailure;
        }

        /**
         * Expect exactly one image (appropriate for the case when we
         * are comparing against a single PNG file).
         */
        Expectations(const SkBitmap& bitmap, bool ignoreFailure=kDefaultIgnoreFailure) {
            fBitmap = bitmap;
            fIgnoreFailure = ignoreFailure;
            fAllowedChecksums.push_back() = SkBitmapChecksummer::Compute64(bitmap);
        }

        /**
         * Create Expectations from a JSON element as found within the
         * kJsonKey_ExpectedResults section.
         *
         * It's fine if the jsonElement is null or empty; in that case, we just
         * don't have any expectations.
         */
        Expectations(Json::Value jsonElement) {
            if (jsonElement.empty()) {
                fIgnoreFailure = kDefaultIgnoreFailure;
            } else {
                Json::Value ignoreFailure = jsonElement[kJsonKey_ExpectedResults_IgnoreFailure];
                if (ignoreFailure.isNull()) {
                    fIgnoreFailure = kDefaultIgnoreFailure;
                } else if (!ignoreFailure.isBool()) {
                    gm_fprintf(stderr, "found non-boolean json value"
                               " for key '%s' in element '%s'\n",
                               kJsonKey_ExpectedResults_IgnoreFailure,
                               jsonElement.toStyledString().c_str());
                    DEBUGFAIL_SEE_STDERR;
                    fIgnoreFailure = kDefaultIgnoreFailure;
                } else {
                    fIgnoreFailure = ignoreFailure.asBool();
                }

                Json::Value allowedChecksums = jsonElement[kJsonKey_ExpectedResults_Checksums];
                if (allowedChecksums.isNull()) {
                    // ok, we'll just assume there aren't any expected checksums to compare against
                } else if (!allowedChecksums.isArray()) {
                    gm_fprintf(stderr, "found non-array json value"
                               " for key '%s' in element '%s'\n",
                               kJsonKey_ExpectedResults_Checksums,
                               jsonElement.toStyledString().c_str());
                    DEBUGFAIL_SEE_STDERR;
                } else {
                    for (Json::ArrayIndex i=0; i<allowedChecksums.size(); i++) {
                        Json::Value checksumElement = allowedChecksums[i];
                        if (!checksumElement.isIntegral()) {
                            gm_fprintf(stderr, "found non-integer checksum"
                                       " in json element '%s'\n",
                                       jsonElement.toStyledString().c_str());
                            DEBUGFAIL_SEE_STDERR;
                        } else {
                            fAllowedChecksums.push_back() = asChecksum(checksumElement);
                        }
                    }
                }
            }
        }

        /**
         * Returns true iff we want to ignore failed expectations.
         */
        bool ignoreFailure() const { return this->fIgnoreFailure; }

        /**
         * Returns true iff there are no allowed checksums.
         */
        bool empty() const { return this->fAllowedChecksums.empty(); }

        /**
         * Returns true iff actualChecksum matches any allowedChecksum,
         * regardless of fIgnoreFailure.  (The caller can check
         * that separately.)
         */
        bool match(Checksum actualChecksum) const {
            for (int i=0; i < this->fAllowedChecksums.count(); i++) {
                Checksum allowedChecksum = this->fAllowedChecksums[i];
                if (allowedChecksum == actualChecksum) {
                    return true;
                }
            }
            return false;
        }

        /**
         * If this Expectation is based on a single SkBitmap, return a
         * pointer to that SkBitmap. Otherwise (if the Expectation is
         * empty, or if it was based on a list of checksums rather
         * than a single bitmap), returns NULL.
         */
        const SkBitmap *asBitmap() const {
            return (SkBitmap::kNo_Config == fBitmap.config()) ? NULL : &fBitmap;
        }

        /**
         * Return a JSON representation of the allowed checksums.
         * This does NOT include any information about whether to
         * ignore failures.
         */
        Json::Value allowedChecksumsAsJson() const {
            Json::Value allowedChecksumArray;
            if (!this->fAllowedChecksums.empty()) {
                for (int i=0; i < this->fAllowedChecksums.count(); i++) {
                    Checksum allowedChecksum = this->fAllowedChecksums[i];
                    allowedChecksumArray.append(asJsonValue(allowedChecksum));
                }
            }
            return allowedChecksumArray;
        }

    private:
        const static bool kDefaultIgnoreFailure = false;

        SkTArray<Checksum> fAllowedChecksums;
        bool fIgnoreFailure;
        SkBitmap fBitmap;
    };

    /**
     * Abstract source of Expectations objects for individual tests.
     */
    class ExpectationsSource : public SkRefCnt {
    public:
        virtual Expectations get(const char *testName) = 0;
    };

    /**
     * Return Expectations based on individual image files on disk.
     */
    class IndividualImageExpectationsSource : public ExpectationsSource {
    public:
        /**
         * Create an ExpectationsSource that will return Expectations based on
         * image files found within rootDir.
         *
         * rootDir: directory under which to look for image files
         *          (this string will be copied to storage within this object)
         * notifyOfMissingFiles: whether to log a message to stderr if an image
         *                       file cannot be found
         */
        IndividualImageExpectationsSource(const char *rootDir,
                                          bool notifyOfMissingFiles) :
            fRootDir(rootDir), fNotifyOfMissingFiles(notifyOfMissingFiles) {}

        Expectations get(const char *testName) SK_OVERRIDE {
            SkString path = make_filename(fRootDir.c_str(), "", testName,
                                          "png");
            SkBitmap referenceBitmap;
            bool decodedReferenceBitmap =
                SkImageDecoder::DecodeFile(path.c_str(), &referenceBitmap,
                                           SkBitmap::kARGB_8888_Config,
                                           SkImageDecoder::kDecodePixels_Mode,
                                           NULL);
            if (decodedReferenceBitmap) {
                return Expectations(referenceBitmap);
            } else {
                if (fNotifyOfMissingFiles) {
                    gm_fprintf(stderr, "FAILED to read %s\n", path.c_str());
                }
                return Expectations();
            }
        }

    private:
        const SkString fRootDir;
        const bool fNotifyOfMissingFiles;
    };

    /**
     * Return Expectations based on JSON summary file.
     */
    class JsonExpectationsSource : public ExpectationsSource {
    public:
        /**
         * Create an ExpectationsSource that will return Expectations based on
         * a JSON file.
         *
         * jsonPath: path to JSON file to read
         */
        JsonExpectationsSource(const char *jsonPath) {
            parse(jsonPath, &fJsonRoot);
            fJsonExpectedResults = fJsonRoot[kJsonKey_ExpectedResults];
        }

        Expectations get(const char *testName) SK_OVERRIDE {
            return Expectations(fJsonExpectedResults[testName]);
        }

    private:

        /**
         * Read as many bytes as possible (up to maxBytes) from the stream into
         * an SkData object.
         *
         * If the returned SkData contains fewer than maxBytes, then EOF has been
         * reached and no more data would be available from subsequent calls.
         * (If EOF has already been reached, then this call will return an empty
         * SkData object immediately.)
         *
         * If there are fewer than maxBytes bytes available to read from the
         * stream, but the stream has not been closed yet, this call will block
         * until there are enough bytes to read or the stream has been closed.
         *
         * It is up to the caller to call unref() on the returned SkData object
         * once the data is no longer needed, so that the underlying buffer will
         * be freed.  For example:
         *
         * {
         *   size_t maxBytes = 256;
         *   SkAutoDataUnref dataRef(readIntoSkData(stream, maxBytes));
         *   if (NULL != dataRef.get()) {
         *     size_t bytesActuallyRead = dataRef.get()->size();
         *     // use the data...
         *   }
         * }
         * // underlying buffer has been freed, thanks to auto unref
         *
         */
        // TODO(epoger): Move this, into SkStream.[cpp|h] as attempted in
        // https://codereview.appspot.com/7300071 ?
        // And maybe readFileIntoSkData() also?
        static SkData* readIntoSkData(SkStream &stream, size_t maxBytes) {
            if (0 == maxBytes) {
                return SkData::NewEmpty();
            }
            char* bufStart = reinterpret_cast<char *>(sk_malloc_throw(maxBytes));
            char* bufPtr = bufStart;
            size_t bytesRemaining = maxBytes;
            while (bytesRemaining > 0) {
                size_t bytesReadThisTime = stream.read(bufPtr, bytesRemaining);
                if (0 == bytesReadThisTime) {
                    break;
                }
                bytesRemaining -= bytesReadThisTime;
                bufPtr += bytesReadThisTime;
            }
            return SkData::NewFromMalloc(bufStart, maxBytes - bytesRemaining);
        }

        /**
         * Wrapper around readIntoSkData for files: reads the entire file into
         * an SkData object.
         */
        static SkData* readFileIntoSkData(SkFILEStream &stream) {
            return readIntoSkData(stream, stream.getLength());
        }

        /**
         * Read the file contents from jsonPath and parse them into jsonRoot.
         *
         * Returns true if successful.
         */
        static bool parse(const char *jsonPath, Json::Value *jsonRoot) {
            SkFILEStream inFile(jsonPath);
            if (!inFile.isValid()) {
                gm_fprintf(stderr, "unable to read JSON file %s\n", jsonPath);
                DEBUGFAIL_SEE_STDERR;
                return false;
            }

            SkAutoDataUnref dataRef(readFileIntoSkData(inFile));
            if (NULL == dataRef.get()) {
                gm_fprintf(stderr, "error reading JSON file %s\n", jsonPath);
                DEBUGFAIL_SEE_STDERR;
                return false;
            }

            const char *bytes = reinterpret_cast<const char *>(dataRef.get()->data());
            size_t size = dataRef.get()->size();
            Json::Reader reader;
            if (!reader.parse(bytes, bytes+size, *jsonRoot)) {
                gm_fprintf(stderr, "error parsing JSON file %s\n", jsonPath);
                DEBUGFAIL_SEE_STDERR;
                return false;
            }
            return true;
        }

        Json::Value fJsonRoot;
        Json::Value fJsonExpectedResults;
    };

}
#endif