Shorten avifgridapitest and avify4mtest

Move code to aviftest_helpers for clarity and to be reused by other tests.
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 717ab9d..49c0d7c 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -56,12 +56,12 @@
         find_package(GTest REQUIRED)
     endif()
 
-    add_executable(avifgridapitest avifgridapitest.cc)
+    add_executable(avifgridapitest avifgridapitest.cc aviftest_helpers.cc)
     target_link_libraries(avifgridapitest avif ${AVIF_PLATFORM_LIBRARIES} ${GTEST_BOTH_LIBRARIES})
     target_include_directories(avifgridapitest PRIVATE ${GTEST_INCLUDE_DIRS})
     add_test(NAME avifgridapitest COMMAND avifgridapitest)
 
-    add_executable(avify4mtest avify4mtest.cc)
+    add_executable(avify4mtest avify4mtest.cc aviftest_helpers.cc)
     target_link_libraries(avify4mtest avif avif_apps ${AVIF_PLATFORM_LIBRARIES} ${GTEST_BOTH_LIBRARIES})
     target_include_directories(avify4mtest PRIVATE ${GTEST_INCLUDE_DIRS})
     add_test(NAME avify4mtest COMMAND avify4mtest)
diff --git a/tests/avifgridapitest.cc b/tests/avifgridapitest.cc
index c939e29..88bda67 100644
--- a/tests/avifgridapitest.cc
+++ b/tests/avifgridapitest.cc
@@ -3,169 +3,21 @@
 
 #include "avif/avif.h"
 
-#include <cassert>
-#include <cinttypes>
-#include <cstdio>
-#include <cstring>
 #include <tuple>
+#include <vector>
 
+#include "aviftest_helpers.h"
 #include "gtest/gtest.h"
 
 using testing::Combine;
 using testing::Values;
 using testing::ValuesIn;
 
+namespace libavif
+{
 namespace
 {
 
-//------------------------------------------------------------------------------
-
-// Fills a plane with a repeating gradient.
-void fillPlane(int width, int height, int depth, uint8_t * row, uint32_t rowBytes)
-{
-    assert(depth == 8 || depth == 10 || depth == 12); // Values allowed by AV1.
-    const int maxValuePlusOne = 1 << depth;
-    for (int y = 0; y < height; ++y) {
-        if (depth == 8) {
-            memset(row, y % maxValuePlusOne, width);
-        } else {
-            for (int x = 0; x < width; ++x) {
-                ((uint16_t *)row)[x] = (uint16_t)(y % maxValuePlusOne);
-            }
-        }
-        row += rowBytes;
-    }
-}
-
-// Creates an image where the pixel values are defined but do not matter.
-// Returns false in case of memory failure.
-bool createImage(int width, int height, int depth, avifPixelFormat yuvFormat, bool createAlpha, avifImage ** image)
-{
-    *image = avifImageCreate(width, height, depth, yuvFormat);
-    if (!*image) {
-        printf("ERROR: avifImageCreate() failed\n");
-        return false;
-    }
-    avifImageAllocatePlanes(*image, createAlpha ? AVIF_PLANES_ALL : AVIF_PLANES_YUV);
-    if (width * height == 0) {
-        return true;
-    }
-
-    avifPixelFormatInfo formatInfo;
-    avifGetPixelFormatInfo((*image)->yuvFormat, &formatInfo);
-    uint32_t uvWidth = ((*image)->width + formatInfo.chromaShiftX) >> formatInfo.chromaShiftX;
-    uint32_t uvHeight = ((*image)->height + formatInfo.chromaShiftY) >> formatInfo.chromaShiftY;
-
-    const int planeCount = formatInfo.monochrome ? 1 : AVIF_PLANE_COUNT_YUV;
-    for (int plane = 0; plane < planeCount; ++plane) {
-        fillPlane((plane == AVIF_CHAN_Y) ? (*image)->width : uvWidth,
-                  (plane == AVIF_CHAN_Y) ? (*image)->height : uvHeight,
-                  (*image)->depth,
-                  (*image)->yuvPlanes[plane],
-                  (*image)->yuvRowBytes[plane]);
-    }
-
-    if (createAlpha) {
-        fillPlane((*image)->width, (*image)->height, (*image)->depth, (*image)->alphaPlane, (*image)->alphaRowBytes);
-    }
-    return true;
-}
-
-// Generates then encodes a grid image. Returns false in case of failure.
-bool encodeGrid(int columns, int rows, int cellWidth, int cellHeight, int depth, avifPixelFormat yuvFormat, bool createAlpha, avifRWData * output)
-{
-    bool success = false;
-    avifEncoder * encoder = NULL;
-    avifImage ** cellImages = (avifImage **)avifAlloc(sizeof(avifImage *) * columns * rows);
-    memset(cellImages, 0, sizeof(avifImage *) * columns * rows);
-    for (int iCell = 0; iCell < columns * rows; ++iCell) {
-        if (!createImage(cellWidth, cellHeight, depth, yuvFormat, createAlpha, &cellImages[iCell])) {
-            goto cleanup;
-        }
-    }
-
-    encoder = avifEncoderCreate();
-    if (!encoder) {
-        printf("ERROR: avifEncoderCreate() failed\n");
-        goto cleanup;
-    }
-    encoder->speed = AVIF_SPEED_FASTEST;
-    if (avifEncoderAddImageGrid(encoder, columns, rows, (const avifImage * const *)cellImages, AVIF_ADD_IMAGE_FLAG_SINGLE) !=
-        AVIF_RESULT_OK) {
-        printf("ERROR: avifEncoderAddImageGrid() failed\n");
-        goto cleanup;
-    }
-    if (avifEncoderFinish(encoder, output) != AVIF_RESULT_OK) {
-        printf("ERROR: avifEncoderFinish() failed\n");
-        goto cleanup;
-    }
-
-    success = true;
-cleanup:
-    if (encoder) {
-        avifEncoderDestroy(encoder);
-    }
-    if (cellImages) {
-        for (int i = 0; i < columns * rows; ++i) {
-            if (cellImages[i]) {
-                avifImageDestroy(cellImages[i]);
-            }
-        }
-        avifFree(cellImages);
-    }
-    return success;
-}
-
-//------------------------------------------------------------------------------
-
-// Decodes the data. Returns false in case of failure.
-bool decode(const avifRWData * encodedAvif)
-{
-    bool success = false;
-    avifImage * const image = avifImageCreateEmpty();
-    avifDecoder * const decoder = avifDecoderCreate();
-    if (!image || !decoder) {
-        printf("ERROR: memory allocation failed\n");
-        goto cleanup;
-    }
-    if (avifDecoderReadMemory(decoder, image, encodedAvif->data, encodedAvif->size) != AVIF_RESULT_OK) {
-        printf("ERROR: avifDecoderReadMemory() failed\n");
-        goto cleanup;
-    }
-    success = true;
-cleanup:
-    if (image) {
-        avifImageDestroy(image);
-    }
-    if (decoder) {
-        avifDecoderDestroy(decoder);
-    }
-    return success;
-}
-
-//------------------------------------------------------------------------------
-
-// Generates, encodes then decodes a grid image.
-bool encodeDecode(int columns, int rows, int cellWidth, int cellHeight, int depth, avifPixelFormat yuvFormat, bool createAlpha, bool expectedSuccess)
-{
-    bool success = false;
-    avifRWData encodedAvif = { nullptr, 0 };
-    if (encodeGrid(columns, rows, cellWidth, cellHeight, depth, yuvFormat, createAlpha, &encodedAvif) != expectedSuccess) {
-        goto cleanup;
-    }
-    // Only decode if the encoding was expected to succeed.
-    // Any successful encoding shall result in a valid decoding.
-    if (expectedSuccess && !decode(&encodedAvif)) {
-        goto cleanup;
-    }
-    success = true;
-cleanup:
-    avifRWDataFree(&encodedAvif);
-    return success;
-}
-
-//------------------------------------------------------------------------------
-
 // Pair of cell count and cell size for a single dimension.
 struct Cell
 {
@@ -186,14 +38,41 @@
     const bool createAlpha = std::get<4>(GetParam());
     const bool expectedSuccess = std::get<5>(GetParam());
 
-    EXPECT_TRUE(encodeDecode(/*columns=*/horizontal.count,
-                             /*rows=*/vertical.count,
-                             /*cellWidth=*/horizontal.size,
-                             /*cellHeight=*/vertical.size,
-                             bitDepth,
-                             yuvFormat,
-                             createAlpha,
-                             expectedSuccess));
+    // Construct a grid.
+    std::vector<testutil::avifImagePtr> cellImages;
+    cellImages.reserve(horizontal.count * vertical.count);
+    for (int i = 0; i < horizontal.count * vertical.count; ++i) {
+        cellImages.emplace_back(
+            testutil::createImage(horizontal.size, vertical.size, bitDepth, yuvFormat, createAlpha ? AVIF_PLANES_ALL : AVIF_PLANES_YUV));
+        ASSERT_NE(cellImages.back(), nullptr);
+        testutil::fillImageGradient(cellImages.back().get());
+    }
+
+    // Encode the grid image.
+    testutil::avifEncoderPtr encoder(avifEncoderCreate(), avifEncoderDestroy);
+    ASSERT_NE(encoder, nullptr);
+    encoder->speed = AVIF_SPEED_FASTEST;
+    std::vector<avifImage *> cellImagePtrs(cellImages.size()); // Just here to match libavif API.
+    for (size_t i = 0; i < cellImages.size(); ++i) {
+        cellImagePtrs[i] = cellImages[i].get();
+    }
+    const avifResult result =
+        avifEncoderAddImageGrid(encoder.get(), horizontal.count, vertical.count, cellImagePtrs.data(), AVIF_ADD_IMAGE_FLAG_SINGLE);
+
+    if (expectedSuccess) {
+        ASSERT_EQ(result, AVIF_RESULT_OK);
+        testutil::avifRWDataCleaner encodedAvif;
+        ASSERT_EQ(avifEncoderFinish(encoder.get(), &encodedAvif), AVIF_RESULT_OK);
+
+        // Decode the grid image.
+        testutil::avifImagePtr image(avifImageCreateEmpty(), avifImageDestroy);
+        ASSERT_NE(image, nullptr);
+        testutil::avifDecoderPtr decoder(avifDecoderCreate(), avifDecoderDestroy);
+        ASSERT_NE(decoder, nullptr);
+        ASSERT_EQ(avifDecoderReadMemory(decoder.get(), image.get(), encodedAvif.data, encodedAvif.size), AVIF_RESULT_OK);
+    } else {
+        ASSERT_TRUE(result == AVIF_RESULT_INVALID_IMAGE_GRID || result == AVIF_RESULT_NO_CONTENT);
+    }
 }
 
 // A cell cannot be smaller than 64px in any dimension if there are several cells.
@@ -275,3 +154,4 @@
                                  /*expectedSuccess=*/Values(false)));
 
 } // namespace
+} // namespace libavif
diff --git a/tests/aviftest_helpers.cc b/tests/aviftest_helpers.cc
new file mode 100644
index 0000000..3a3f65f
--- /dev/null
+++ b/tests/aviftest_helpers.cc
@@ -0,0 +1,143 @@
+// Copyright 2022 Google LLC. All rights reserved.
+// SPDX-License-Identifier: BSD-2-Clause
+
+#include "aviftest_helpers.h"
+#include "avif/avif.h"
+
+#include <algorithm>
+#include <cassert>
+
+namespace libavif
+{
+namespace testutil
+{
+namespace
+{
+
+constexpr int AVIF_CHAN_A = AVIF_CHAN_V + 1;
+constexpr int AVIF_CHANS[] = { AVIF_CHAN_Y, AVIF_CHAN_U, AVIF_CHAN_V, AVIF_CHAN_A };
+
+} // namespace
+
+//------------------------------------------------------------------------------
+
+avifImagePtr createImage(int width, int height, int depth, avifPixelFormat yuvFormat, avifPlanesFlags planes, avifRange yuvRange)
+{
+    avifImagePtr image(avifImageCreate(width, height, depth, yuvFormat), avifImageDestroy);
+    if (!image) {
+        return { nullptr, nullptr };
+    }
+    image->yuvRange = yuvRange;
+    avifImageAllocatePlanes(image.get(), planes);
+    return image;
+}
+
+void fillImagePlain(avifImage * image, const uint32_t yuva[4])
+{
+    avifPixelFormatInfo info;
+    avifGetPixelFormatInfo(image->yuvFormat, &info);
+
+    for (int c : AVIF_CHANS) {
+        uint8_t * row = (c == AVIF_CHAN_A) ? image->alphaPlane : image->yuvPlanes[c];
+        if (!row) {
+            continue;
+        }
+        const uint32_t rowBytes = (c == AVIF_CHAN_A) ? image->alphaRowBytes : image->yuvRowBytes[c];
+        const uint32_t planeWidth =
+            (c == AVIF_CHAN_Y || c == AVIF_CHAN_A) ? image->width : ((image->width + info.chromaShiftX) >> info.chromaShiftX);
+        const uint32_t planeHeight =
+            (c == AVIF_CHAN_Y || c == AVIF_CHAN_A) ? image->height : ((image->height + info.chromaShiftY) >> info.chromaShiftY);
+        for (uint32_t y = 0; y < planeHeight; ++y) {
+            if (avifImageUsesU16(image)) {
+                std::fill(reinterpret_cast<uint16_t *>(row),
+                          reinterpret_cast<uint16_t *>(row) + planeWidth,
+                          static_cast<uint16_t>(yuva[c]));
+            } else {
+                std::fill(row, row + planeWidth, static_cast<uint8_t>(yuva[c]));
+            }
+            row += rowBytes;
+        }
+    }
+}
+
+void fillImageGradient(avifImage * image)
+{
+    avifPixelFormatInfo info;
+    avifGetPixelFormatInfo(image->yuvFormat, &info);
+
+    for (int c : AVIF_CHANS) {
+        uint8_t * row = (c == AVIF_CHAN_A) ? image->alphaPlane : image->yuvPlanes[c];
+        if (!row) {
+            continue;
+        }
+        const uint32_t rowBytes = (c == AVIF_CHAN_A) ? image->alphaRowBytes : image->yuvRowBytes[c];
+        const uint32_t planeWidth =
+            (c == AVIF_CHAN_Y || c == AVIF_CHAN_A) ? image->width : ((image->width + info.chromaShiftX) >> info.chromaShiftX);
+        const uint32_t planeHeight =
+            (c == AVIF_CHAN_Y || c == AVIF_CHAN_A) ? image->height : ((image->height + info.chromaShiftY) >> info.chromaShiftY);
+        for (uint32_t y = 0; y < planeHeight; ++y) {
+            for (uint32_t x = 0; x < planeWidth; ++x) {
+                const uint32_t value = (x + y) * ((1u << image->depth) - 1u) / std::max(1u, planeWidth + planeHeight - 2);
+                if (avifImageUsesU16(image)) {
+                    reinterpret_cast<uint16_t *>(row)[x] = static_cast<uint16_t>(value);
+                } else {
+                    row[x] = static_cast<uint8_t>(value);
+                }
+            }
+            row += rowBytes;
+        }
+    }
+}
+
+//------------------------------------------------------------------------------
+
+// Returns true if image1 and image2 are identical.
+bool areImagesEqual(const avifImage & image1, const avifImage & image2)
+{
+    if (image1.width != image2.width || image1.height != image2.height || image1.depth != image2.depth ||
+        image1.yuvFormat != image2.yuvFormat || image1.yuvRange != image2.yuvRange) {
+        return false;
+    }
+    assert(image1.width * image1.height > 0);
+
+    avifPixelFormatInfo info;
+    avifGetPixelFormatInfo(image1.yuvFormat, &info);
+
+    for (int c : AVIF_CHANS) {
+        uint8_t * row1 = (c == AVIF_CHAN_A) ? image1.alphaPlane : image1.yuvPlanes[c];
+        uint8_t * row2 = (c == AVIF_CHAN_A) ? image2.alphaPlane : image2.yuvPlanes[c];
+        if (!row1 != !row2) {
+            return false;
+        }
+        if (!row1) {
+            continue;
+        }
+        const uint32_t rowBytes1 = (c == AVIF_CHAN_A) ? image1.alphaRowBytes : image1.yuvRowBytes[c];
+        const uint32_t rowBytes2 = (c == AVIF_CHAN_A) ? image2.alphaRowBytes : image2.yuvRowBytes[c];
+        const uint32_t planeWidth =
+            (c == AVIF_CHAN_Y || c == AVIF_CHAN_A) ? image1.width : ((image1.width + info.chromaShiftX) >> info.chromaShiftX);
+        const uint32_t planeHeight =
+            (c == AVIF_CHAN_Y || c == AVIF_CHAN_A) ? image1.height : ((image1.height + info.chromaShiftY) >> info.chromaShiftY);
+        for (uint32_t y = 0; y < planeHeight; ++y) {
+            if (avifImageUsesU16(&image1)) {
+                if (!std::equal(reinterpret_cast<uint16_t *>(row1),
+                                reinterpret_cast<uint16_t *>(row1) + planeWidth,
+                                reinterpret_cast<uint16_t *>(row2))) {
+                    return false;
+                }
+            } else {
+                if (!std::equal(row1, row1 + planeWidth, row2)) {
+                    return false;
+                }
+            }
+            row1 += rowBytes1;
+            row2 += rowBytes2;
+        }
+    }
+    return true;
+}
+
+//------------------------------------------------------------------------------
+
+} // namespace testutil
+} // namespace libavif
diff --git a/tests/aviftest_helpers.h b/tests/aviftest_helpers.h
new file mode 100644
index 0000000..77d4e56
--- /dev/null
+++ b/tests/aviftest_helpers.h
@@ -0,0 +1,40 @@
+// Copyright 2022 Google LLC
+// SPDX-License-Identifier: BSD-2-Clause
+
+#ifndef LIBAVIF_TESTS_AVIFTEST_HELPERS_H_
+#define LIBAVIF_TESTS_AVIFTEST_HELPERS_H_
+
+#include "avif/avif.h"
+
+#include <memory>
+
+namespace libavif
+{
+namespace testutil
+{
+
+using avifImagePtr = std::unique_ptr<avifImage, decltype(&avifImageDestroy)>;
+using avifEncoderPtr = std::unique_ptr<avifEncoder, decltype(&avifEncoderDestroy)>;
+using avifDecoderPtr = std::unique_ptr<avifDecoder, decltype(&avifDecoderDestroy)>;
+
+class avifRWDataCleaner : public avifRWData
+{
+public:
+    avifRWDataCleaner() : avifRWData({}) {}
+    ~avifRWDataCleaner() { avifRWDataFree(this); }
+};
+
+// Creates an image. Returns null in case of memory failure.
+avifImagePtr createImage(int width, int height, int depth, avifPixelFormat yuvFormat, avifPlanesFlags planes, avifRange yuvRange = AVIF_RANGE_FULL);
+
+// Set all pixels of each plane of an image.
+void fillImagePlain(avifImage * image, const uint32_t yuva[4]);
+void fillImageGradient(avifImage * image);
+
+// Returns true if both images have the same features and pixel values.
+bool areImagesEqual(const avifImage & image1, const avifImage & image2);
+
+} // namespace testutil
+} // namespace libavif
+
+#endif // LIBAVIF_TESTS_AVIFTEST_HELPERS_H_
diff --git a/tests/avify4mtest.cc b/tests/avify4mtest.cc
index cde23a4..4cf4853 100644
--- a/tests/avify4mtest.cc
+++ b/tests/avify4mtest.cc
@@ -5,167 +5,20 @@
 
 #include "y4m.h"
 
-#include <cassert>
-#include <cinttypes>
-#include <cstdio>
-#include <cstring>
 #include <sstream>
 #include <tuple>
 
+#include "aviftest_helpers.h"
 #include "gtest/gtest.h"
 
 using testing::Combine;
 using testing::Values;
 
+namespace libavif
+{
 namespace
 {
 
-//------------------------------------------------------------------------------
-
-// Returns true if image1 and image2 are identical.
-bool compareYUVA(const avifImage * image1, const avifImage * image2)
-{
-    if (image1->width != image2->width || image1->height != image2->height || image1->depth != image2->depth ||
-        image1->yuvFormat != image2->yuvFormat || image1->yuvRange != image2->yuvRange) {
-        printf("ERROR: input mismatch\n");
-        return false;
-    }
-    assert(image1->width * image1->height > 0);
-
-    avifPixelFormatInfo formatInfo;
-    avifGetPixelFormatInfo(image1->yuvFormat, &formatInfo);
-    const uint32_t uvWidth = (image1->width + formatInfo.chromaShiftX) >> formatInfo.chromaShiftX;
-    const uint32_t uvHeight = (image1->height + formatInfo.chromaShiftY) >> formatInfo.chromaShiftY;
-
-    const int planeCount = formatInfo.monochrome ? 1 : AVIF_PLANE_COUNT_YUV;
-    for (int plane = 0; plane < planeCount; ++plane) {
-        const uint32_t widthByteCount =
-            ((plane == AVIF_CHAN_Y) ? image1->width : uvWidth) * ((image1->depth > 8) ? sizeof(uint16_t) : sizeof(uint8_t));
-        const uint32_t height = (plane == AVIF_CHAN_Y) ? image1->height : uvHeight;
-        const uint8_t * row1 = image1->yuvPlanes[plane];
-        const uint8_t * row2 = image2->yuvPlanes[plane];
-        for (uint32_t y = 0; y < height; ++y) {
-            if (memcmp(row1, row2, widthByteCount) != 0) {
-                printf("ERROR: different px at row %" PRIu32 ", channel %" PRIu32 "\n", y, plane);
-                return false;
-            }
-            row1 += image1->yuvRowBytes[plane];
-            row2 += image2->yuvRowBytes[plane];
-        }
-    }
-
-    if (image1->alphaPlane || image2->alphaPlane) {
-        if (!image1->alphaPlane || !image2->alphaPlane || image1->alphaPremultiplied != image2->alphaPremultiplied) {
-            printf("ERROR: input mismatch\n");
-            return false;
-        }
-        const uint32_t widthByteCount = image1->width * ((image1->depth > 8) ? sizeof(uint16_t) : sizeof(uint8_t));
-        const uint8_t * row1 = image1->alphaPlane;
-        const uint8_t * row2 = image2->alphaPlane;
-        for (uint32_t y = 0; y < image1->height; ++y) {
-            if (memcmp(row1, row2, widthByteCount) != 0) {
-                printf("ERROR: different px at row %" PRIu32 ", alpha\n", y);
-                return false;
-            }
-            row1 += image1->alphaRowBytes;
-            row2 += image2->alphaRowBytes;
-        }
-    }
-    return true;
-}
-
-//------------------------------------------------------------------------------
-
-// Fills each plane of the image with the maximum allowed value.
-void fillPlanes(avifImage * image)
-{
-    const uint16_t yuvValue = (image->yuvRange == AVIF_RANGE_LIMITED) ? (235 << (image->depth - 8)) : ((1 << image->depth) - 1);
-    avifPixelFormatInfo formatInfo;
-    avifGetPixelFormatInfo(image->yuvFormat, &formatInfo);
-    const int planeCount = formatInfo.monochrome ? 1 : AVIF_PLANE_COUNT_YUV;
-    for (int plane = 0; plane < planeCount; ++plane) {
-        if (image->yuvPlanes[plane]) {
-            const uint32_t planeWidth =
-                (plane == AVIF_CHAN_Y) ? image->width : ((image->width + formatInfo.chromaShiftX) >> formatInfo.chromaShiftX);
-            const uint32_t planeHeight =
-                (plane == AVIF_CHAN_Y) ? image->height : ((image->height + formatInfo.chromaShiftY) >> formatInfo.chromaShiftY);
-            for (uint32_t y = 0; y < planeHeight; ++y) {
-                uint8_t * const row = image->yuvPlanes[plane] + y * image->yuvRowBytes[plane];
-                if (image->depth == 8) {
-                    memset(row, yuvValue, planeWidth);
-                } else {
-                    for (uint32_t x = 0; x < planeWidth; ++x) {
-                        ((uint16_t *)row)[x] = yuvValue;
-                    }
-                }
-            }
-        }
-    }
-    if (image->alphaPlane) {
-        const uint16_t alphaValue = (1 << image->depth) - 1;
-        for (uint32_t y = 0; y < image->height; ++y) {
-            uint8_t * const row = image->alphaPlane + y * image->alphaRowBytes;
-            if (image->depth == 8) {
-                memset(row, alphaValue, image->width);
-            } else {
-                for (uint32_t x = 0; x < image->width; ++x) {
-                    ((uint16_t *)row)[x] = alphaValue;
-                }
-            }
-        }
-    }
-}
-
-// Creates an image and encodes then decodes it as a y4m file.
-bool encodeDecodeY4m(uint32_t width,
-                     uint32_t height,
-                     uint32_t depth,
-                     avifPixelFormat yuvFormat,
-                     avifRange yuvRange,
-                     bool createAlpha,
-                     const char filePath[])
-{
-    bool success = false;
-    avifImage * image = avifImageCreateEmpty();
-    avifImage * decoded = avifImageCreateEmpty();
-    if (!image || !decoded) {
-        printf("ERROR: avifImageCreate() failed\n");
-        goto cleanup;
-    }
-    image->width = width;
-    image->height = height;
-    image->depth = depth;
-    image->yuvFormat = yuvFormat;
-    image->yuvRange = yuvRange;
-    avifImageAllocatePlanes(image, createAlpha ? AVIF_PLANES_ALL : AVIF_PLANES_YUV);
-    fillPlanes(image);
-
-    if (!y4mWrite(filePath, image)) {
-        printf("ERROR: y4mWrite() failed\n");
-        goto cleanup;
-    }
-    if (!y4mRead(filePath, decoded, /*sourceTiming=*/NULL, /*iter=*/NULL)) {
-        printf("ERROR: y4mRead() failed\n");
-        goto cleanup;
-    }
-
-    if (!compareYUVA(image, decoded)) {
-        goto cleanup;
-    }
-
-    success = true;
-cleanup:
-    if (image) {
-        avifImageDestroy(image);
-    }
-    if (decoded) {
-        avifImageDestroy(decoded);
-    }
-    return success;
-}
-
-//------------------------------------------------------------------------------
-
 class Y4mTest
     : public testing::TestWithParam<std::tuple</*width=*/int, /*height=*/int, /*bitDepth=*/int, /*yuvFormat=*/avifPixelFormat, /*yuvRange=*/avifRange, /*createAlpha=*/bool>>
 {
@@ -182,7 +35,22 @@
     std::ostringstream filePath;
     filePath << testing::TempDir() << "avify4mtest_" << width << "_" << height << "_" << bitDepth << "_" << yuvFormat << "_"
              << yuvRange << "_" << createAlpha;
-    EXPECT_TRUE(encodeDecodeY4m(width, height, bitDepth, yuvFormat, yuvRange, createAlpha, filePath.str().c_str()));
+
+    testutil::avifImagePtr image =
+        testutil::createImage(width, height, bitDepth, yuvFormat, createAlpha ? AVIF_PLANES_ALL : AVIF_PLANES_YUV, yuvRange);
+    ASSERT_NE(image, nullptr);
+    const uint32_t yuva[] = { (yuvRange == AVIF_RANGE_LIMITED) ? (235u << (bitDepth - 8)) : ((1u << bitDepth) - 1),
+                              (yuvRange == AVIF_RANGE_LIMITED) ? (240u << (bitDepth - 8)) : ((1u << bitDepth) - 1),
+                              (yuvRange == AVIF_RANGE_LIMITED) ? (240u << (bitDepth - 8)) : ((1u << bitDepth) - 1),
+                              (1u << bitDepth) - 1 };
+    testutil::fillImagePlain(image.get(), yuva);
+    ASSERT_TRUE(y4mWrite(filePath.str().c_str(), image.get()));
+
+    testutil::avifImagePtr decoded(avifImageCreateEmpty(), avifImageDestroy);
+    ASSERT_NE(decoded, nullptr);
+    ASSERT_TRUE(y4mRead(filePath.str().c_str(), decoded.get(), /*sourceTiming=*/nullptr, /*iter=*/nullptr));
+
+    EXPECT_TRUE(testutil::areImagesEqual(*image, *decoded));
 }
 
 INSTANTIATE_TEST_SUITE_P(OpaqueCombinations,
@@ -205,3 +73,4 @@
                                  /*createAlpha=*/Values(true)));
 
 } // namespace
+} // namespace libavif