Repository: google/filament
File: filament/backend/test/ImageExpectations.cpp (C++)

Code before:
}

ImageExpectation::ImageExpectation(const char* fileName, int lineNumber,
        filament::backend::DriverApi& api, ScreenshotParams params,
        filament::backend::RenderTargetHandle renderTarget)
        : mFileName(fileName), mLineNumber(lineNumber), mParams(std::move(params)),
          mResult(api, renderTarget, mParams) {}

void ImageExpectation::evaluate() {
    // Ensure this is only evaluated once.
    if (mEvaluated) {
        return;
    }
    mEvaluated = true;

    // Do the actual image comparison inside a scoped trace with the stored file and line.
    {
        testing::ScopedTrace trace(mFileName, mLineNumber, "");
        compareImage();
    }
}

void ImageExpectation::compareImage() const {
    std::cout << std::this_thread::get_id() << std::endl;
    int attempts = 10;
    while (attempts > 0 && !mResult.bytesFilled()) {
        usleep(10000);
        attempts -= 1;
    }
    bool bytesFilled = mResult.bytesFilled();
    // If this fails, it likely means that BackendTest::flushAndWait needs to be called before
    // ImageExpectations is evaluated.
    EXPECT_THAT(bytesFilled, testing::IsTrue())
                        << "Render target wasn't copied to the buffer for " << mFileName;
    if (bytesFilled) {
        uint32_t actualHash = mResult.hash();
        EXPECT_THAT(actualHash, testing::Eq(mParams.expectedHash()));
    }
}

ImageExpectations::ImageExpectations(filament::backend::DriverApi& api) : mApi(api) {}

ImageExpectations::~ImageExpectations() {
    // Guarantee that all expectations are evaluated when this leaves scope even if the caller
    // forgot to manually evaluate them.
    evaluate();
}

void ImageExpectations::addExpectation(const char* fileName, int lineNumber,
        filament::backend::RenderTargetHandle renderTarget, ScreenshotParams params) {
    mExpectations.emplace_back(std::make_unique<ImageExpectation>(fileName, lineNumber, mApi,

Review comment (suggestion):
It seems like backend testing should be deterministic. Why do we need to try this multiple times? Once we call `driver.finish()`, we should be guaranteed that all callbacks have completed.

Code after:
}

std::string ScreenshotParams::expectedFilePath() const {
    return absl::StrFormat("%s/%s", expectedDirectoryPath(), expectedFileName());
}

ImageExpectation::ImageExpectation(const char* fileName, int lineNumber,
        filament::backend::DriverApi& api, ScreenshotParams params,
        filament::backend::RenderTargetHandle renderTarget)
        : mFileName(fileName), mLineNumber(lineNumber), mParams(std::move(params)),
          mResult(api, renderTarget, mParams) {}

void ImageExpectation::evaluate() {
    // Ensure this is only evaluated once.
    if (mEvaluated) {
        return;
    }
    mEvaluated = true;

    // Do the actual image comparison inside a scoped trace with the stored file and line.
    {
        testing::ScopedTrace trace(mFileName, mLineNumber, "");
        compareImage();
    }
}

void ImageExpectation::compareImage() const {
    bool bytesFilled = mResult.bytesFilled();
    // If this fails, it likely means that BackendTest::flushAndWait needs to be called before
    // ImageExpectations is evaluated or destroyed.
    EXPECT_THAT(bytesFilled, testing::IsTrue())
                        << "Render target wasn't copied to the buffer for " << mFileName;
    if (bytesFilled) {
        // Rather than directly compare the two images compare their hashes because comparing very
        // large arrays generates way too much debug output to be useful.
        uint32_t actualHash = mResult.hash();
#ifndef FILAMENT_IOS
        LoadedPng loadedImage(mParams.expectedFilePath());
        uint32_t loadedImageHash = loadedImage.hash();
        EXPECT_THAT(actualHash, testing::Eq(loadedImageHash)) << mParams.expectedFileName();
#endif
        // For builds that can't load PNGs (currently iOS only) use the expected hash.
        EXPECT_THAT(actualHash, testing::Eq(mParams.expectedHash())) << mParams.expectedFileName();
        // TODO: Add better debug output, such as generating a diff image.
    }
}

ImageExpectations::ImageExpectations(filament::backend::DriverApi& api) : mApi(api) {}

ImageExpectations::~ImageExpectations() {
    // Guarantee that all expectations are evaluated when this leaves scope even if the caller