Convert some tests to C++ and GoogleTest
Keep aviftest in C with no GoogleTest dependency as a minimum test
suite when GoogleTest is unavailable.
Convert feature-oriented, unit tests to C++ and to the GoogleTest
testing framework. Migrating remaining C behaviors will be done in
another change (goto, casts, etc.).
Add extern "C" in apps/shared headers.
Add AVIF_ENABLE_GTEST and AVIF_LOCAL_GTEST CMake options.
Build GoogleTest and run all tests in the GitHub workflow.
Set CMAKE_CXX_STANDARD to C++11.
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 44d532d..717ab9d 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -4,9 +4,6 @@
# With testing enabled, all targets referenced by add_test() can be run
# at once with CMake's ctest command line tool from the build folder.
enable_testing()
-# Using a CMake FIXTURES_SETUP/FIXTURES_CLEANUP to create and delete a
-# subdirectory would be cleaner but this is way simpler.
-set(AVIF_TEST_TMP_DIR ${PROJECT_BINARY_DIR})
add_executable(aviftest aviftest.c)
if(AVIF_LOCAL_LIBGAV1)
@@ -15,13 +12,6 @@
target_link_libraries(aviftest avif ${AVIF_PLATFORM_LIBRARIES})
add_test(NAME aviftest COMMAND aviftest ${CMAKE_CURRENT_SOURCE_DIR}/data)
-add_executable(avifgridapitest avifgridapitest.c)
-if(AVIF_LOCAL_LIBGAV1)
- set_target_properties(avifgridapitest PROPERTIES LINKER_LANGUAGE "CXX")
-endif()
-target_link_libraries(avifgridapitest avif ${AVIF_PLATFORM_LIBRARIES})
-add_test(NAME avifgridapitest COMMAND avifgridapitest)
-
add_executable(avifincrtest avifincrtest.c avifincrtest_helpers.c)
if(AVIF_LOCAL_LIBGAV1)
set_target_properties(avifincrtest PROPERTIES LINKER_LANGUAGE "CXX")
@@ -36,14 +26,6 @@
target_link_libraries(avifmetadatatest avif ${AVIF_PLATFORM_LIBRARIES})
add_test(NAME avifmetadatatest COMMAND avifmetadatatest)
-add_executable(avify4mtest avify4mtest.c)
-if(AVIF_LOCAL_LIBGAV1)
- set_target_properties(avify4mtest PROPERTIES LINKER_LANGUAGE "CXX")
-endif()
-target_link_libraries(avify4mtest avif avif_apps ${AVIF_PLATFORM_LIBRARIES})
-add_test(NAME avify4mtest COMMAND avify4mtest AVIF_TEST_TMP_DIR)
-set_tests_properties(avify4mtest PROPERTIES ENVIRONMENT "AVIF_TEST_TMP_DIR=${AVIF_TEST_TMP_DIR}")
-
add_executable(avifyuv avifyuv.c)
if(AVIF_LOCAL_LIBGAV1)
set_target_properties(avifyuv PROPERTIES LINKER_LANGUAGE "CXX")
@@ -53,6 +35,40 @@
add_test(NAME avifyuv_${AVIFYUV_MODE} COMMAND avifyuv -m ${AVIFYUV_MODE})
endforeach()
+if(AVIF_ENABLE_GTEST)
+ enable_language(CXX)
+ set(CMAKE_CXX_STANDARD 11)
+ if(AVIF_LOCAL_GTEST)
+ set(GTEST_INCLUDE_DIRS ${CMAKE_SOURCE_DIR}/ext/googletest/googletest/include)
+ set(GTEST_LIBRARIES ${CMAKE_SOURCE_DIR}/ext/googletest/build/lib/libgtest${CMAKE_STATIC_LIBRARY_SUFFIX})
+ set(GTEST_MAIN_LIBRARIES ${CMAKE_SOURCE_DIR}/ext/googletest/build/lib/libgtest_main${CMAKE_STATIC_LIBRARY_SUFFIX})
+ set(GTEST_BOTH_LIBRARIES ${GTEST_LIBRARIES} ${GTEST_MAIN_LIBRARIES})
+ if(NOT EXISTS ${GTEST_INCLUDE_DIRS}/gtest/gtest.h)
+ message(FATAL_ERROR "googletest(AVIF_LOCAL_GTEST): ${GTEST_INCLUDE_DIRS}/gtest/gtest.h is missing, bailing out")
+ elseif(NOT EXISTS ${GTEST_LIBRARIES})
+ message(FATAL_ERROR "googletest(AVIF_LOCAL_GTEST): ${GTEST_LIBRARIES} is missing, bailing out")
+ elseif(NOT EXISTS ${GTEST_MAIN_LIBRARIES})
+ message(FATAL_ERROR "googletest(AVIF_LOCAL_GTEST): ${GTEST_MAIN_LIBRARIES} is missing, bailing out")
+ else()
+ message(STATUS "Found local ext/googletest")
+ endif()
+ else()
+ find_package(GTest REQUIRED)
+ endif()
+
+ add_executable(avifgridapitest avifgridapitest.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)
+ 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)
+else()
+ message(STATUS "Most tests are disabled because AVIF_ENABLE_GTEST is OFF.")
+endif()
+
if(AVIF_ENABLE_COVERAGE)
add_custom_target(
avif_coverage
diff --git a/tests/avifgridapitest.c b/tests/avifgridapitest.c
deleted file mode 100644
index dd74500..0000000
--- a/tests/avifgridapitest.c
+++ /dev/null
@@ -1,258 +0,0 @@
-// Copyright 2022 Google LLC. All rights reserved.
-// SPDX-License-Identifier: BSD-2-Clause
-
-#include "avif/avif.h"
-
-#include <assert.h>
-#include <inttypes.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-//------------------------------------------------------------------------------
-
-// Fills a plane with a repeating gradient.
-static 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.
-static avifBool createImage(int width, int height, int depth, avifPixelFormat yuvFormat, avifBool createAlpha, avifImage ** image)
-{
- *image = avifImageCreate(width, height, depth, yuvFormat);
- if (*image == NULL) {
- printf("ERROR: avifImageCreate() failed\n");
- return AVIF_FALSE;
- }
- avifImageAllocatePlanes(*image, createAlpha ? AVIF_PLANES_ALL : AVIF_PLANES_YUV);
- if (width * height == 0) {
- return AVIF_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 AVIF_TRUE;
-}
-
-// Generates then encodes a grid image. Returns false in case of failure.
-static avifBool encodeGrid(int columns, int rows, int cellWidth, int cellHeight, int depth, avifPixelFormat yuvFormat, avifBool createAlpha, avifRWData * output)
-{
- avifBool success = AVIF_FALSE;
- avifEncoder * encoder = NULL;
- avifImage ** cellImages = 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 == NULL) {
- 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 = AVIF_TRUE;
-cleanup:
- if (encoder != NULL) {
- avifEncoderDestroy(encoder);
- }
- if (cellImages != NULL) {
- for (int i = 0; i < columns * rows; ++i) {
- if (cellImages[i] != NULL) {
- avifImageDestroy(cellImages[i]);
- }
- }
- avifFree(cellImages);
- }
- return success;
-}
-
-//------------------------------------------------------------------------------
-
-// Decodes the data. Returns false in case of failure.
-static avifBool decode(const avifRWData * encodedAvif)
-{
- avifBool success = AVIF_FALSE;
- avifImage * const image = avifImageCreateEmpty();
- avifDecoder * const decoder = avifDecoderCreate();
- if (image == NULL || decoder == NULL) {
- 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 = AVIF_TRUE;
-cleanup:
- if (image != NULL) {
- avifImageDestroy(image);
- }
- if (decoder != NULL) {
- avifDecoderDestroy(decoder);
- }
- return success;
-}
-
-//------------------------------------------------------------------------------
-
-// Generates, encodes then decodes a grid image.
-static avifBool encodeDecode(int columns, int rows, int cellWidth, int cellHeight, int depth, avifPixelFormat yuvFormat, avifBool createAlpha, avifBool expectedSuccess)
-{
- avifBool success = AVIF_FALSE;
- avifRWData encodedAvif = { 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 = AVIF_TRUE;
-cleanup:
- avifRWDataFree(&encodedAvif);
- return success;
-}
-
-//------------------------------------------------------------------------------
-
-// For each bit depth, with and without alpha, generates, encodes then decodes a grid image.
-static avifBool encodeDecodeDepthsAlpha(int columns, int rows, int cellWidth, int cellHeight, avifPixelFormat yuvFormat, avifBool expectedSuccess)
-{
- const int depths[] = { 8, 10, 12 }; // See avifEncoderAddImageInternal()
- for (size_t d = 0; d < sizeof(depths) / sizeof(depths[0]); ++d) {
- for (avifBool createAlpha = AVIF_FALSE; createAlpha <= AVIF_TRUE; ++createAlpha) {
- if (!encodeDecode(columns, rows, cellWidth, cellHeight, depths[d], yuvFormat, createAlpha, expectedSuccess)) {
- return AVIF_FALSE;
- }
- }
- }
- return AVIF_TRUE;
-}
-
-// For each dimension, for each combination of cell count and size, generates, encodes then decodes a grid image for several depths and alpha.
-static avifBool encodeDecodeSizes(const int columnsCellWidths[][2],
- int horizontalCombinationCount,
- const int rowsCellHeights[][2],
- int verticalCombinationCount,
- avifPixelFormat yuvFormat,
- avifBool expectedSuccess)
-{
- for (int i = 0; i < horizontalCombinationCount; ++i) {
- for (int j = 0; j < verticalCombinationCount; ++j) {
- if (!encodeDecodeDepthsAlpha(/*columns=*/columnsCellWidths[i][0],
- /*rows=*/rowsCellHeights[j][0],
- /*cellWidth=*/columnsCellWidths[i][1],
- /*cellHeight=*/rowsCellHeights[j][1],
- yuvFormat,
- expectedSuccess)) {
- return AVIF_FALSE;
- }
- }
- }
- return AVIF_TRUE;
-}
-
-int main(void)
-{
- // Pairs of cell count and cell size for a single dimension.
- // A cell cannot be smaller than 64px in any dimension if there are several cells.
- // A cell cannot have an odd size in any dimension if there are several cells and chroma subsampling.
- // Image size must be a multiple of cell size.
- const int validCellCountsSizes[][2] = { { 1, 64 }, { 1, 66 }, { 2, 64 }, { 3, 68 } };
- const int validCellCountsSizeCount = sizeof(validCellCountsSizes) / sizeof(validCellCountsSizes[0]);
- const int invalidCellCountsSizes[][2] = { { 0, 0 }, { 0, 1 }, { 1, 0 }, { 2, 1 }, { 2, 2 }, { 2, 3 }, { 2, 63 } };
- const int invalidCellCountsSizeCount = sizeof(invalidCellCountsSizes) / sizeof(invalidCellCountsSizes[0]);
-
- for (int yuvFormat = AVIF_PIXEL_FORMAT_YUV444; yuvFormat <= AVIF_PIXEL_FORMAT_YUV400; ++yuvFormat) {
- if (!encodeDecodeSizes(validCellCountsSizes,
- validCellCountsSizeCount,
- validCellCountsSizes,
- validCellCountsSizeCount,
- yuvFormat,
- /*expectedSuccess=*/AVIF_TRUE)) {
- return EXIT_FAILURE;
- }
-
- if (!encodeDecodeSizes(validCellCountsSizes,
- validCellCountsSizeCount,
- invalidCellCountsSizes,
- invalidCellCountsSizeCount,
- yuvFormat,
- /*expectedSuccess=*/AVIF_FALSE) ||
- !encodeDecodeSizes(invalidCellCountsSizes,
- invalidCellCountsSizeCount,
- validCellCountsSizes,
- validCellCountsSizeCount,
- yuvFormat,
- /*expectedSuccess=*/AVIF_FALSE) ||
- !encodeDecodeSizes(invalidCellCountsSizes,
- invalidCellCountsSizeCount,
- invalidCellCountsSizes,
- invalidCellCountsSizeCount,
- yuvFormat,
- /*expectedSuccess=*/AVIF_FALSE)) {
- return EXIT_FAILURE;
- }
-
- // Special case depending on the cell count and the chroma subsampling.
- for (int rows = 1; rows <= 2; ++rows) {
- avifBool expectedSuccess = (rows == 1) || (yuvFormat != AVIF_PIXEL_FORMAT_YUV420);
- if (!encodeDecodeDepthsAlpha(/*columns=*/1, rows, /*cellWidth=*/64, /*cellHeight=*/65, yuvFormat, expectedSuccess)) {
- return EXIT_FAILURE;
- }
- }
-
- // Special case depending on the cell count and the cell size.
- for (int columns = 1; columns <= 2; ++columns) {
- for (int rows = 1; rows <= 2; ++rows) {
- avifBool expectedSuccess = (columns * rows == 1);
- if (!encodeDecodeDepthsAlpha(columns, rows, /*cellWidth=*/1, /*cellHeight=*/65, yuvFormat, expectedSuccess)) {
- return EXIT_FAILURE;
- }
- }
- }
- }
- return EXIT_SUCCESS;
-}
diff --git a/tests/avifgridapitest.cc b/tests/avifgridapitest.cc
new file mode 100644
index 0000000..c939e29
--- /dev/null
+++ b/tests/avifgridapitest.cc
@@ -0,0 +1,277 @@
+// Copyright 2022 Google LLC. All rights reserved.
+// SPDX-License-Identifier: BSD-2-Clause
+
+#include "avif/avif.h"
+
+#include <cassert>
+#include <cinttypes>
+#include <cstdio>
+#include <cstring>
+#include <tuple>
+
+#include "gtest/gtest.h"
+
+using testing::Combine;
+using testing::Values;
+using testing::ValuesIn;
+
+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
+{
+ int count, size;
+};
+
+class GridApiTest
+ : public testing::TestWithParam<std::tuple</*horizontal=*/Cell, /*vertical=*/Cell, /*bitDepth=*/int, /*yuvFormat=*/avifPixelFormat, /*createAlpha=*/bool, /*expectedSuccess=*/bool>>
+{
+};
+
+TEST_P(GridApiTest, EncodeDecode)
+{
+ const Cell horizontal = std::get<0>(GetParam());
+ const Cell vertical = std::get<1>(GetParam());
+ const int bitDepth = std::get<2>(GetParam());
+ const avifPixelFormat yuvFormat = std::get<3>(GetParam());
+ 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));
+}
+
+// A cell cannot be smaller than 64px in any dimension if there are several cells.
+// A cell cannot have an odd size in any dimension if there are several cells and chroma subsampling.
+// Image size must be a multiple of cell size.
+constexpr Cell kValidCells[] = { { 1, 64 }, { 1, 66 }, { 2, 64 }, { 3, 68 } };
+constexpr Cell kInvalidCells[] = { { 0, 0 }, { 0, 1 }, { 1, 0 }, { 2, 1 }, { 2, 2 }, { 2, 3 }, { 2, 63 } };
+constexpr int kBitDepths[] = { 8, 10, 12 };
+constexpr avifPixelFormat kPixelFormats[] = { AVIF_PIXEL_FORMAT_YUV444, AVIF_PIXEL_FORMAT_YUV422, AVIF_PIXEL_FORMAT_YUV420, AVIF_PIXEL_FORMAT_YUV400 };
+
+INSTANTIATE_TEST_SUITE_P(Valid,
+ GridApiTest,
+ Combine(/*horizontal=*/ValuesIn(kValidCells),
+ /*vertical=*/ValuesIn(kValidCells),
+ ValuesIn(kBitDepths),
+ ValuesIn(kPixelFormats),
+ /*createAlpha=*/Values(false, true),
+ /*expectedSuccess=*/Values(true)));
+
+INSTANTIATE_TEST_SUITE_P(InvalidVertically,
+ GridApiTest,
+ Combine(/*horizontal=*/ValuesIn(kValidCells),
+ /*vertical=*/ValuesIn(kInvalidCells),
+ ValuesIn(kBitDepths),
+ ValuesIn(kPixelFormats),
+ /*createAlpha=*/Values(false, true),
+ /*expectedSuccess=*/Values(false)));
+INSTANTIATE_TEST_SUITE_P(InvalidHorizontally,
+ GridApiTest,
+ Combine(/*horizontal=*/ValuesIn(kInvalidCells),
+ /*vertical=*/ValuesIn(kValidCells),
+ ValuesIn(kBitDepths),
+ ValuesIn(kPixelFormats),
+ /*createAlpha=*/Values(false, true),
+ /*expectedSuccess=*/Values(false)));
+INSTANTIATE_TEST_SUITE_P(InvalidBoth,
+ GridApiTest,
+ Combine(/*horizontal=*/ValuesIn(kInvalidCells),
+ /*vertical=*/ValuesIn(kInvalidCells),
+ ValuesIn(kBitDepths),
+ ValuesIn(kPixelFormats),
+ /*createAlpha=*/Values(false, true),
+ /*expectedSuccess=*/Values(false)));
+
+// Special case depending on the cell count and the chroma subsampling.
+INSTANTIATE_TEST_SUITE_P(ValidOddHeight,
+ GridApiTest,
+ Combine(/*horizontal=*/Values(Cell { 1, 64 }),
+ /*vertical=*/Values(Cell { 1, 65 }, Cell { 2, 65 }),
+ ValuesIn(kBitDepths),
+ Values(AVIF_PIXEL_FORMAT_YUV444, AVIF_PIXEL_FORMAT_YUV422, AVIF_PIXEL_FORMAT_YUV400),
+ /*createAlpha=*/Values(false, true),
+ /*expectedSuccess=*/Values(true)));
+INSTANTIATE_TEST_SUITE_P(InvalidOddHeight,
+ GridApiTest,
+ Combine(/*horizontal=*/Values(Cell { 1, 64 }),
+ /*vertical=*/Values(Cell { 2, 65 }),
+ ValuesIn(kBitDepths),
+ Values(AVIF_PIXEL_FORMAT_YUV420),
+ /*createAlpha=*/Values(false, true),
+ /*expectedSuccess=*/Values(false)));
+
+// Special case depending on the cell count and the cell size.
+INSTANTIATE_TEST_SUITE_P(ValidOddDimensions,
+ GridApiTest,
+ Combine(/*horizontal=*/Values(Cell { 1, 1 }),
+ /*vertical=*/Values(Cell { 1, 65 }),
+ ValuesIn(kBitDepths),
+ ValuesIn(kPixelFormats),
+ /*createAlpha=*/Values(false, true),
+ /*expectedSuccess=*/Values(true)));
+INSTANTIATE_TEST_SUITE_P(InvalidOddDimensions,
+ GridApiTest,
+ Combine(/*horizontal=*/Values(Cell { 2, 1 }),
+ /*vertical=*/Values(Cell { 1, 65 }, Cell { 2, 65 }),
+ ValuesIn(kBitDepths),
+ ValuesIn(kPixelFormats),
+ /*createAlpha=*/Values(false, true),
+ /*expectedSuccess=*/Values(false)));
+
+} // namespace
diff --git a/tests/avify4mtest.c b/tests/avify4mtest.cc
similarity index 61%
rename from tests/avify4mtest.c
rename to tests/avify4mtest.cc
index cd9f723..cde23a4 100644
--- a/tests/avify4mtest.c
+++ b/tests/avify4mtest.cc
@@ -5,21 +5,30 @@
#include "y4m.h"
-#include <assert.h>
-#include <inttypes.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
+#include <cassert>
+#include <cinttypes>
+#include <cstdio>
+#include <cstring>
+#include <sstream>
+#include <tuple>
+
+#include "gtest/gtest.h"
+
+using testing::Combine;
+using testing::Values;
+
+namespace
+{
//------------------------------------------------------------------------------
// Returns true if image1 and image2 are identical.
-static avifBool compareYUVA(const avifImage * image1, const avifImage * image2)
+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 AVIF_FALSE;
+ return false;
}
assert(image1->width * image1->height > 0);
@@ -38,17 +47,17 @@
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 AVIF_FALSE;
+ return false;
}
row1 += image1->yuvRowBytes[plane];
row2 += image2->yuvRowBytes[plane];
}
}
- if (image1->alphaPlane != NULL || image2->alphaPlane != NULL) {
- if (image1->alphaPlane == NULL || image2->alphaPlane == NULL || image1->alphaPremultiplied != image2->alphaPremultiplied) {
+ if (image1->alphaPlane || image2->alphaPlane) {
+ if (!image1->alphaPlane || !image2->alphaPlane || image1->alphaPremultiplied != image2->alphaPremultiplied) {
printf("ERROR: input mismatch\n");
- return AVIF_FALSE;
+ return false;
}
const uint32_t widthByteCount = image1->width * ((image1->depth > 8) ? sizeof(uint16_t) : sizeof(uint8_t));
const uint8_t * row1 = image1->alphaPlane;
@@ -56,26 +65,26 @@
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 AVIF_FALSE;
+ return false;
}
row1 += image1->alphaRowBytes;
row2 += image2->alphaRowBytes;
}
}
- return AVIF_TRUE;
+ return true;
}
//------------------------------------------------------------------------------
// Fills each plane of the image with the maximum allowed value.
-static void fillPlanes(avifImage * image)
+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] != NULL) {
+ if (image->yuvPlanes[plane]) {
const uint32_t planeWidth =
(plane == AVIF_CHAN_Y) ? image->width : ((image->width + formatInfo.chromaShiftX) >> formatInfo.chromaShiftX);
const uint32_t planeHeight =
@@ -92,7 +101,7 @@
}
}
}
- if (image->alphaPlane != NULL) {
+ 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;
@@ -108,18 +117,18 @@
}
// Creates an image and encodes then decodes it as a y4m file.
-static avifBool encodeDecodeY4m(uint32_t width,
- uint32_t height,
- uint32_t depth,
- avifPixelFormat yuvFormat,
- avifRange yuvRange,
- avifBool createAlpha,
- const char filePath[])
+bool encodeDecodeY4m(uint32_t width,
+ uint32_t height,
+ uint32_t depth,
+ avifPixelFormat yuvFormat,
+ avifRange yuvRange,
+ bool createAlpha,
+ const char filePath[])
{
- avifBool success = AVIF_FALSE;
+ bool success = false;
avifImage * image = avifImageCreateEmpty();
avifImage * decoded = avifImageCreateEmpty();
- if (image == NULL || decoded == NULL) {
+ if (!image || !decoded) {
printf("ERROR: avifImageCreate() failed\n");
goto cleanup;
}
@@ -144,12 +153,12 @@
goto cleanup;
}
- success = AVIF_TRUE;
+ success = true;
cleanup:
- if (image != NULL) {
+ if (image) {
avifImageDestroy(image);
}
- if (decoded != NULL) {
+ if (decoded) {
avifImageDestroy(decoded);
}
return success;
@@ -157,47 +166,42 @@
//------------------------------------------------------------------------------
-int main(int argc, char * argv[])
+class Y4mTest
+ : public testing::TestWithParam<std::tuple</*width=*/int, /*height=*/int, /*bitDepth=*/int, /*yuvFormat=*/avifPixelFormat, /*yuvRange=*/avifRange, /*createAlpha=*/bool>>
{
- if (argc != 2 || !strlen(argv[1])) {
- fprintf(stderr, "Missing temporary directory path environment variable name argument\n");
- return EXIT_FAILURE;
- }
- const char * testTmpdir = getenv(argv[1]);
- if (testTmpdir == NULL || !strlen(testTmpdir)) {
- fprintf(stderr, "The environment variable %s is missing or is an empty string\n", argv[1]);
- return EXIT_FAILURE;
- }
- char filePath[256];
- const int result = snprintf(filePath, sizeof(filePath), "%s/avify4mtest.y4m", testTmpdir);
- if (result < 0 || result >= (int)sizeof(filePath)) {
- fprintf(stderr, "Could not generate a temporary file path\n");
- return EXIT_FAILURE;
- }
+};
- // Try several configurations.
- const uint32_t depths[] = { 8, 10, 12 };
- const uint32_t widths[] = { 1, 2, 3 };
- const uint32_t heights[] = { 1, 2, 3 };
- for (uint32_t d = 0; d < sizeof(depths) / sizeof(depths[0]); ++d) {
- for (int yuvFormat = AVIF_PIXEL_FORMAT_YUV444; yuvFormat <= AVIF_PIXEL_FORMAT_YUV400; ++yuvFormat) {
- for (avifBool createAlpha = AVIF_FALSE; createAlpha <= AVIF_TRUE; ++createAlpha) {
- if (createAlpha && (depths[d] != 8 || yuvFormat != AVIF_PIXEL_FORMAT_YUV444)) {
- continue; // writing alpha is currently only supported in 8bpc YUV444
- }
-
- for (int yuvRange = AVIF_RANGE_LIMITED; yuvRange <= AVIF_RANGE_FULL; ++yuvRange) {
- for (uint32_t w = 0; w < sizeof(widths) / sizeof(widths[0]); ++w) {
- for (uint32_t h = 0; h < sizeof(heights) / sizeof(heights[0]); ++h) {
- if (!encodeDecodeY4m(widths[w], heights[h], depths[d], yuvFormat, yuvRange, createAlpha, filePath)) {
- return EXIT_FAILURE;
- }
- }
- }
- }
- }
- }
- }
-
- return EXIT_SUCCESS;
+TEST_P(Y4mTest, EncodeDecode)
+{
+ const int width = std::get<0>(GetParam());
+ const int height = std::get<1>(GetParam());
+ const int bitDepth = std::get<2>(GetParam());
+ const avifPixelFormat yuvFormat = std::get<3>(GetParam());
+ const avifRange yuvRange = std::get<4>(GetParam());
+ const bool createAlpha = std::get<5>(GetParam());
+ 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()));
}
+
+INSTANTIATE_TEST_SUITE_P(OpaqueCombinations,
+ Y4mTest,
+ Combine(/*width=*/Values(1, 2, 3),
+ /*height=*/Values(1, 2, 3),
+ /*depths=*/Values(8, 10, 12),
+ Values(AVIF_PIXEL_FORMAT_YUV444, AVIF_PIXEL_FORMAT_YUV422, AVIF_PIXEL_FORMAT_YUV420, AVIF_PIXEL_FORMAT_YUV400),
+ Values(AVIF_RANGE_LIMITED, AVIF_RANGE_FULL),
+ /*createAlpha=*/Values(false)));
+
+// Writing alpha is currently only supported in 8bpc YUV444.
+INSTANTIATE_TEST_SUITE_P(AlphaCombinations,
+ Y4mTest,
+ Combine(/*width=*/Values(1, 2, 3),
+ /*height=*/Values(1, 2, 3),
+ /*depths=*/Values(8),
+ Values(AVIF_PIXEL_FORMAT_YUV444),
+ Values(AVIF_RANGE_LIMITED, AVIF_RANGE_FULL),
+ /*createAlpha=*/Values(true)));
+
+} // namespace