testutil::ReadImage()

Use AvifImagePtr in avifmetadatatest.cc to fix C API  memory leaks.

The aviftest_helpers target must be linked to avif_apps.
Use a STATIC avif_apps library instead of an OBJECT library to avoid
issues related to the lack of property transitivity in OBJECT libs.
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 9e0c638..37e22ee 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -479,7 +479,7 @@
     find_package(JPEG REQUIRED)
 
     add_library(
-        avif_apps OBJECT apps/shared/avifjpeg.c apps/shared/iccjpeg.c apps/shared/avifpng.c apps/shared/avifutil.c
+        avif_apps STATIC apps/shared/avifjpeg.c apps/shared/iccjpeg.c apps/shared/avifpng.c apps/shared/avifutil.c
                          apps/shared/y4m.c
     )
     target_link_libraries(avif_apps avif ${AVIF_PLATFORM_LIBRARIES} ${PNG_LIBRARY} ${ZLIB_LIBRARY} ${JPEG_LIBRARY})
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 2d3f75d..b328048 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -42,14 +42,12 @@
 ################################################################################
 # GoogleTest
 
-if(AVIF_ENABLE_GTEST OR AVIF_BUILD_APPS)
+if(AVIF_ENABLE_GTEST)
     enable_language(CXX)
     set(CMAKE_CXX_STANDARD 11)
     add_library(aviftest_helpers OBJECT gtest/aviftest_helpers.cc)
-    target_link_libraries(aviftest_helpers avif ${AVIF_PLATFORM_LIBRARIES})
-endif()
+    target_link_libraries(aviftest_helpers avif_apps)
 
-if(AVIF_ENABLE_GTEST)
     if(AVIF_LOCAL_GTEST)
         set(GTEST_INCLUDE_DIRS ${CMAKE_SOURCE_DIR}/ext/googletest/googletest/include)
         set(GTEST_LIBRARIES
@@ -93,12 +91,12 @@
 
     add_executable(avifincrtest gtest/avifincrtest.cc)
     target_link_libraries(avifincrtest aviftest_helpers avifincrtest_helpers)
-    add_test(NAME avifincrtest COMMAND avifincrtest ${CMAKE_CURRENT_SOURCE_DIR}/data)
+    add_test(NAME avifincrtest COMMAND avifincrtest ${CMAKE_CURRENT_SOURCE_DIR}/data/)
 
     add_executable(avifmetadatatest gtest/avifmetadatatest.cc)
-    target_link_libraries(avifmetadatatest aviftest_helpers avif_apps ${GTEST_LIBRARIES})
+    target_link_libraries(avifmetadatatest aviftest_helpers ${GTEST_LIBRARIES})
     target_include_directories(avifmetadatatest PRIVATE ${GTEST_INCLUDE_DIRS})
-    add_test(NAME avifmetadatatest COMMAND avifmetadatatest ${CMAKE_CURRENT_SOURCE_DIR}/data)
+    add_test(NAME avifmetadatatest COMMAND avifmetadatatest ${CMAKE_CURRENT_SOURCE_DIR}/data/)
 
     add_executable(avifrgbtoyuvtest gtest/avifrgbtoyuvtest.cc)
     target_link_libraries(avifrgbtoyuvtest aviftest_helpers ${GTEST_BOTH_LIBRARIES})
@@ -115,7 +113,7 @@
     endif()
 
     add_executable(avify4mtest gtest/avify4mtest.cc)
-    target_link_libraries(avify4mtest aviftest_helpers avif_apps ${GTEST_BOTH_LIBRARIES})
+    target_link_libraries(avify4mtest aviftest_helpers ${GTEST_BOTH_LIBRARIES})
     target_include_directories(avify4mtest PRIVATE ${GTEST_INCLUDE_DIRS})
     add_test(NAME avify4mtest COMMAND avify4mtest)
 else()
@@ -129,7 +127,7 @@
     # When building apps, test the avifenc/avifdec.
     # 'are_images_equal' is used to make sure inputs/outputs are unchanged.
     add_executable(are_images_equal gtest/are_images_equal.cc)
-    target_link_libraries(are_images_equal aviftest_helpers avif_apps)
+    target_link_libraries(are_images_equal aviftest_helpers)
     add_test(NAME test_cmd COMMAND bash ${CMAKE_CURRENT_SOURCE_DIR}/test_cmd.sh ${CMAKE_BINARY_DIR}
                                    ${CMAKE_CURRENT_SOURCE_DIR}/data
     )
diff --git a/tests/gtest/avifmetadatatest.cc b/tests/gtest/avifmetadatatest.cc
index d70c983..4fe8cf7 100644
--- a/tests/gtest/avifmetadatatest.cc
+++ b/tests/gtest/avifmetadatatest.cc
@@ -6,7 +6,6 @@
 
 #include "avif/avif.h"
 #include "aviftest_helpers.h"
-#include "avifutil.h"
 #include "gtest/gtest.h"
 
 using ::testing::Bool;
@@ -113,8 +112,7 @@
 
 // zTXt "Raw profile type exif" at the beginning of a PNG file.
 TEST_P(MetadataTest, Read) {
-  const std::string file_path =
-      std::string(data_path) + "/" + std::get<0>(GetParam());
+  const char* file_name = std::get<0>(GetParam());
   const bool use_icc = std::get<1>(GetParam());
   const bool use_exif = std::get<2>(GetParam());
   const bool use_xmp = std::get<3>(GetParam());
@@ -122,14 +120,12 @@
   const bool expect_exif = std::get<5>(GetParam());
   const bool expect_xmp = std::get<6>(GetParam());
 
-  avifImage* image = avifImageCreateEmpty();
+  const testutil::AvifImagePtr image = testutil::ReadImage(
+      data_path, file_name, AVIF_PIXEL_FORMAT_NONE, 0, AVIF_RGB_TO_YUV_DEFAULT,
+      !use_icc, !use_exif, !use_xmp);
   ASSERT_NE(image, nullptr);
-  image->matrixCoefficients = AVIF_MATRIX_COEFFICIENTS_IDENTITY;  // lossless
-  ASSERT_NE(avifReadImage(file_path.c_str(), AVIF_PIXEL_FORMAT_NONE, 0,
-                          AVIF_RGB_TO_YUV_DEFAULT, !use_icc, !use_exif,
-                          !use_xmp, image, nullptr, nullptr, nullptr),
-            AVIF_APP_FILE_FORMAT_UNKNOWN);
   EXPECT_NE(image->width * image->height, 0u);
+
   if (expect_icc) {
     EXPECT_NE(image->icc.size, 0u);
     EXPECT_NE(image->icc.data, nullptr);
@@ -182,28 +178,21 @@
 
 // Verify all parsers lead exactly to the same metadata bytes.
 TEST(MetadataTest, Compare) {
-  constexpr const char* kFileNames[] = {"paris_icc_exif_xmp.png",
-                                        "paris_exif_xmp_icc.jpg",
-                                        "paris_icc_exif_xmp_at_end.png"};
-  avifImage* images[sizeof(kFileNames) / sizeof(kFileNames[0])];
-  avifImage** image_it = images;
-  for (const char* file_name : kFileNames) {
-    const std::string file_path = std::string(data_path) + "/" + file_name;
+  const testutil::AvifImagePtr ref =
+      testutil::ReadImage(data_path, "paris_icc_exif_xmp.png");
+  ASSERT_NE(ref, nullptr);
+  EXPECT_GT(ref->exif.size, 0u);
+  EXPECT_GT(ref->xmp.size, 0u);
+  EXPECT_GT(ref->icc.size, 0u);
 
-    *image_it = avifImageCreateEmpty();
-    ASSERT_NE(*image_it, nullptr);
-    ASSERT_NE(avifReadImage(file_path.c_str(), AVIF_PIXEL_FORMAT_NONE, 0,
-                            AVIF_RGB_TO_YUV_DEFAULT, /*ignoreICC=*/false,
-                            /*ignoreExif=*/false, /*ignoreXMP=*/false,
-                            *image_it, nullptr, nullptr, nullptr),
-              AVIF_APP_FILE_FORMAT_UNKNOWN);
-    ++image_it;
-  }
-
-  for (avifImage* image : images) {
-    EXPECT_TRUE(testutil::AreByteSequencesEqual(image->exif, images[0]->exif));
-    EXPECT_TRUE(testutil::AreByteSequencesEqual(image->xmp, images[0]->xmp));
-    EXPECT_TRUE(testutil::AreByteSequencesEqual(image->icc, images[0]->icc));
+  for (const char* file_name :
+       {"paris_exif_xmp_icc.jpg", "paris_icc_exif_xmp_at_end.png"}) {
+    const testutil::AvifImagePtr image =
+        testutil::ReadImage(data_path, file_name);
+    ASSERT_NE(image, nullptr);
+    EXPECT_TRUE(testutil::AreByteSequencesEqual(image->exif, ref->exif));
+    EXPECT_TRUE(testutil::AreByteSequencesEqual(image->xmp, ref->xmp));
+    EXPECT_TRUE(testutil::AreByteSequencesEqual(image->icc, ref->icc));
   }
 }
 
diff --git a/tests/gtest/aviftest_helpers.cc b/tests/gtest/aviftest_helpers.cc
index 493512a..25827b6 100644
--- a/tests/gtest/aviftest_helpers.cc
+++ b/tests/gtest/aviftest_helpers.cc
@@ -6,8 +6,10 @@
 #include <algorithm>
 #include <cassert>
 #include <cstdint>
+#include <string>
 
 #include "avif/avif.h"
+#include "avifutil.h"
 
 namespace libavif {
 namespace testutil {
@@ -227,6 +229,26 @@
          AreByteSequencesEqual(image1.xmp, image2.xmp);
 }
 
+//------------------------------------------------------------------------------
+
+AvifImagePtr ReadImage(const char* folder_path, const char* file_name,
+                       avifPixelFormat requested_format, int requested_depth,
+                       avifRGBToYUVFlags flags, avifBool ignore_icc,
+                       avifBool ignore_exif, avifBool ignore_xmp) {
+  testutil::AvifImagePtr image(avifImageCreateEmpty(), avifImageDestroy);
+  if (!image ||
+      avifReadImage((std::string(folder_path) + file_name).c_str(),
+                    requested_format, requested_depth, flags, ignore_icc,
+                    ignore_exif, ignore_xmp, image.get(), /*outDepth=*/nullptr,
+                    /*sourceTiming=*/nullptr,
+                    /*frameIter=*/nullptr) == AVIF_APP_FILE_FORMAT_UNKNOWN) {
+    return {nullptr, avifImageDestroy};
+  }
+  return image;
+}
+
+//------------------------------------------------------------------------------
+
 static avifResult avifIOLimitedReaderRead(avifIO* io, uint32_t readFlags,
                                           uint64_t offset, size_t size,
                                           avifROData* out) {
@@ -264,5 +286,6 @@
 }
 
 //------------------------------------------------------------------------------
+
 }  // namespace testutil
 }  // namespace libavif
diff --git a/tests/gtest/aviftest_helpers.h b/tests/gtest/aviftest_helpers.h
index f7f2a2c..842b60d 100644
--- a/tests/gtest/aviftest_helpers.h
+++ b/tests/gtest/aviftest_helpers.h
@@ -68,6 +68,18 @@
                     bool ignore_alpha = false);
 
 //------------------------------------------------------------------------------
+// Shorter versions of avifutil.h functions
+
+// Reads the image named file_name located in directory at folder_path.
+// Returns nullptr in case of error.
+AvifImagePtr ReadImage(
+    const char* folder_path, const char* file_name,
+    avifPixelFormat requested_format = AVIF_PIXEL_FORMAT_NONE,
+    int requested_depth = 0, avifRGBToYUVFlags flags = AVIF_RGB_TO_YUV_DEFAULT,
+    avifBool ignore_icc = false, avifBool ignore_exif = false,
+    avifBool ignore_xmp = false);
+
+//------------------------------------------------------------------------------
 // avifIO overlay
 
 struct AvifIOLimitedReader {