Split test_api

Change-Id: Idacb490de250d2f49b7c15b1a36b8de728638de6
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 29bd74c..3137cfc 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -7,7 +7,7 @@
 # License 1.0 was not distributed with this source code in the PATENTS file, you
 # can obtain it at www.aomedia.org/license/patent.
 
-cmake_minimum_required(VERSION 3.6)
+cmake_minimum_required(VERSION 3.22)
 project(avifinfo C CXX)
 set(CMAKE_C_STANDARD 11)
 
@@ -55,14 +55,11 @@
     libavif
     GIT_REPOSITORY https://github.com/AOMediaCodec/libavif
     GIT_TAG main
-    PATCH_COMMAND cd ${CMAKE_BINARY_DIR}/libavif-prefix/src/libavif/ext &&
-                  ./aom.cmd
     UPDATE_DISCONNECTED 1 # Avoid building aom everytime.
-    # aom.cmd builds aom as static so libavif should be built as static too.
     CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${EXTERNAL_INSTALL_LOCATION}
-               -DAVIF_CODEC_AOM=ON -DAVIF_LOCAL_AOM=ON -DBUILD_SHARED_LIBS=OFF)
+               -DAVIF_CODEC_AOM=LOCAL -DBUILD_SHARED_LIBS=ON)
 
-  add_executable(avifinfo_tool tools/avifinfo_tool.cc tests/avifinfo_fuzz.cc)
+  add_executable(avifinfo_tool tools/avifinfo_tool.cc tests/test_api.cc)
   set_property(TARGET avifinfo_tool PROPERTY CXX_STANDARD 17) # for filesystem
   target_include_directories(avifinfo_tool SYSTEM
                              PRIVATE ${EXTERNAL_INSTALL_LOCATION}/include)
diff --git a/tests/avifinfo_fuzz.cc b/tests/avifinfo_fuzz.cc
index f561552..7e78a30 100644
--- a/tests/avifinfo_fuzz.cc
+++ b/tests/avifinfo_fuzz.cc
@@ -7,195 +7,21 @@
 // Media Patent License 1.0 was not distributed with this source code in the
 // PATENTS file, you can obtain it at www.aomedia.org/license/patent.
 
-#include <algorithm>
 #include <cstddef>
 #include <cstdint>
 #include <cstdlib>
 
-#include "avifinfo.h"
-
-//------------------------------------------------------------------------------
-// Stream definition.
-
-struct StreamData {
-  const uint8_t* data;
-  size_t data_size;
-};
-
-static const uint8_t* StreamRead(void* stream, size_t num_bytes) {
-  if (stream == nullptr) std::abort();
-  if (num_bytes < 1 || num_bytes > AVIFINFO_MAX_NUM_READ_BYTES) std::abort();
-
-  StreamData* stream_data = reinterpret_cast<StreamData*>(stream);
-  if (num_bytes > stream_data->data_size) return nullptr;
-  const uint8_t* data = stream_data->data;
-  stream_data->data += num_bytes;
-  stream_data->data_size -= num_bytes;
-  return data;
-}
-
-static void StreamSkip(void* stream, size_t num_bytes) {
-  if (stream == nullptr) std::abort();
-  if (num_bytes < 1) std::abort();
-
-  StreamData* stream_data = reinterpret_cast<StreamData*>(stream);
-  num_bytes = std::min(num_bytes, stream_data->data_size);
-  stream_data->data += num_bytes;
-  stream_data->data_size -= num_bytes;
-}
-
-//------------------------------------------------------------------------------
-
-static bool Equals(const AvifInfoFeatures& lhs, const AvifInfoFeatures& rhs) {
-  return lhs.width == rhs.width && lhs.height == rhs.height &&
-         lhs.bit_depth == rhs.bit_depth &&
-         lhs.num_channels == rhs.num_channels &&
-         lhs.has_gainmap == rhs.has_gainmap &&
-         lhs.gainmap_item_id == rhs.gainmap_item_id &&
-         lhs.primary_item_id_location == rhs.primary_item_id_location &&
-         lhs.primary_item_id_bytes == rhs.primary_item_id_bytes;
-}
+#include "devtools/build/runtime/get_runfiles_dir.h"
+#include "googlefuzztest/googlefuzztest.h"
+#include "tests/test_api.h"
 
 // Test a random bitstream of random size, whether it is valid or not.
-extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t data_size) {
-  AvifInfoStatus previous_status_identity = kAvifInfoNotEnoughData;
-  AvifInfoStatus previous_status_features = kAvifInfoNotEnoughData;
-  AvifInfoFeatures previous_features = {0};
-
-  // Check the consistency of the returned status and features:
-  // for a given size and a status that is not kAvifInfoNotEnoughData, any
-  // bigger size (of the same data) should return the same status and features.
-  for (size_t size = 0; size < data_size; ++size) {
-    if (size > 1024 || previous_status_features != kAvifInfoNotEnoughData) {
-      // The behavior is unlikely to change: save computing resources.
-      size = std::min(data_size, size * 2);
-    }
-
-    // Simple raw pointer API.
-    AvifInfoFeatures features;
-    const AvifInfoStatus status_identity = AvifInfoIdentify(data, size);
-    const AvifInfoStatus status_features =
-        AvifInfoGetFeatures(data, size, &features);
-
-    // Once a status different than kAvifInfoNotEnoughData is returned, it
-    // should not change even with more input bytes.
-    if ((previous_status_identity != kAvifInfoNotEnoughData &&
-         status_identity != previous_status_identity) ||
-        (previous_status_features != kAvifInfoNotEnoughData &&
-         status_features != previous_status_features)) {
-      std::abort();
-    }
-
-    // Check the features.
-    if (status_features == previous_status_features) {
-      if (!Equals(features, previous_features)) {
-        std::abort();
-      }
-    } else if (status_features == kAvifInfoOk) {
-      if (status_identity != kAvifInfoOk) {
-        std::abort();
-      }
-      if (features.width == 0u || features.height == 0u ||
-          features.bit_depth == 0u || features.num_channels == 0u ||
-          (!features.has_gainmap && features.gainmap_item_id) ||
-          !features.primary_item_id_location !=
-              !features.primary_item_id_bytes) {
-        std::abort();
-      }
-      if (features.primary_item_id_location &&
-          features.primary_item_id_location + features.primary_item_id_bytes >
-              size) {
-        std::abort();
-      }
-    } else {
-      if (features.width != 0u || features.height != 0u ||
-          features.bit_depth != 0u || features.num_channels != 0u ||
-          features.has_gainmap != 0u || features.gainmap_item_id != 0u ||
-          features.primary_item_id_location != 0u ||
-          features.primary_item_id_bytes != 0u) {
-        std::abort();
-      }
-    }
-
-    // Stream API.
-    AvifInfoFeatures features_stream;
-    StreamData stream_identity = {data, size};
-    StreamData stream_features = {data, size};
-    const AvifInfoStatus status_identity_stream =
-        AvifInfoIdentifyStream(&stream_identity, StreamRead, StreamSkip);
-    const AvifInfoStatus status_features_stream = AvifInfoGetFeaturesStream(
-        &stream_features, StreamRead, StreamSkip, &features_stream);
-    // Both API should have exactly the same behavior, errors included.
-    if (status_identity_stream != status_identity) {
-      std::abort();
-    }
-    // AvifInfoGetFeaturesStream() should only be called if
-    // AvifInfoIdentifyStream() was a success. Call it anyway to make sure it
-    // does not crash, but only check the returned status and features when it
-    // makes sense.
-    if (status_identity_stream == kAvifInfoOk &&
-        (status_features_stream != status_features ||
-         !Equals(features_stream, features))) {
-      std::abort();
-    }
-
-    // Another way of calling the stream API: reuse the stream object that was
-    // used for AvifInfoIdentifyStream().
-    AvifInfoFeatures features_stream_reused;
-    StreamData stream_reused = stream_identity;
-    const AvifInfoStatus status_features_reused_stream =
-        AvifInfoGetFeaturesStream(&stream_reused, StreamRead, StreamSkip,
-                                  &features_stream_reused);
-    // AvifInfoGetFeaturesStream() should only be called if
-    // AvifInfoIdentifyStream() was a success. Call it anyway to make sure it
-    // does not crash, but only check the returned status and features when it
-    // makes sense.
-    if (status_identity_stream == kAvifInfoOk) {
-      if (status_features_reused_stream != status_features_stream) {
-        std::abort();
-      }
-      if (status_features_reused_stream == kAvifInfoOk) {
-        // Offset any location-dependent feature as described in avifinfo.h.
-        features_stream_reused.primary_item_id_location +=
-            size - stream_identity.data_size;
-      }
-      if (!Equals(features_stream_reused, features_stream)) {
-        std::abort();
-      }
-    }
-
-    // Another way of calling the stream API: no provided user-defined skip
-    // method.
-    AvifInfoFeatures features_no_skip;
-    StreamData stream_identity_no_skip = {data, size};
-    StreamData stream_features_no_skip = {data, size};
-    const AvifInfoStatus status_identity_no_skip = AvifInfoIdentifyStream(
-        &stream_identity_no_skip, StreamRead, /*skip=*/nullptr);
-    const AvifInfoStatus status_features_no_skip =
-        AvifInfoGetFeaturesStream(&stream_features_no_skip, StreamRead,
-                                  /*skip=*/nullptr, &features_no_skip);
-    // There may be some difference in status. For example, a valid or invalid
-    // status could be returned just after skipping some bytes. If the skip
-    // argument is omitted, these bytes will be read instead. If some of these
-    // bytes are missing, kAvifInfoNotEnoughData will be returned instead of the
-    // expected success or failure status.
-    if (status_identity_no_skip != status_identity_stream &&
-        !(status_identity_no_skip == kAvifInfoNotEnoughData &&
-          status_identity_stream == kAvifInfoOk)) {
-      std::abort();
-    }
-    if (status_identity_stream == kAvifInfoOk) {
-      if ((status_features_no_skip != status_features &&
-           !(status_features_no_skip == kAvifInfoNotEnoughData &&
-             status_features != kAvifInfoOk)) ||
-          !Equals(features_no_skip, features)) {
-        std::abort();
-      }
-    }
-
-    previous_status_identity = status_identity;
-    previous_status_features = status_features;
-    previous_features = features;
+void BaseTest(const std::string& input) {
+  const uint8_t* data = reinterpret_cast<const uint8_t*>(input.data());
+  const size_t data_size = input.size();
+  if (avifinfo::TestApi(data, data_size) != 0) {
+    std::abort();
   }
-  return 0;
 }
+
+FUZZ_TEST(TestSuite, BaseTest);
diff --git a/tests/avifinfo_fuzz_fast.cc b/tests/avifinfo_fuzz_fast.cc
index 1768472..d8b74f0 100644
--- a/tests/avifinfo_fuzz_fast.cc
+++ b/tests/avifinfo_fuzz_fast.cc
@@ -11,6 +11,8 @@
 #include <cstdint>
 #include <cstdlib>
 
+#include "devtools/build/runtime/get_runfiles_dir.h"
+#include "googlefuzztest/googlefuzztest.h"
 #include "avifinfo.h"
 
 //------------------------------------------------------------------------------
@@ -19,7 +21,9 @@
 // Let the fuzzer exercise any input as fast as possible, to expand as much
 // coverage as possible for a given corpus; hence the simple API use and the
 // limited correctness verifications.
-extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t data_size) {
+void FastTest(const std::string& input) {
+  const uint8_t* data = reinterpret_cast<const uint8_t*>(input.data());
+  const size_t data_size = input.size();
   const AvifInfoStatus status_identity = AvifInfoIdentify(data, data_size);
 
   AvifInfoFeatures features;
@@ -42,5 +46,6 @@
       std::abort();
     }
   }
-  return 0;
 }
+
+FUZZ_TEST(TestSuite, FastTest);
diff --git a/tests/test_api.cc b/tests/test_api.cc
new file mode 100644
index 0000000..5565acf
--- /dev/null
+++ b/tests/test_api.cc
@@ -0,0 +1,206 @@
+// Copyright (c) 2021, Alliance for Open Media. All rights reserved
+//
+// This source code is subject to the terms of the BSD 2 Clause License and
+// the Alliance for Open Media Patent License 1.0. If the BSD 2 Clause License
+// was not distributed with this source code in the LICENSE file, you can
+// obtain it at www.aomedia.org/license/software. If the Alliance for Open
+// Media Patent License 1.0 was not distributed with this source code in the
+// PATENTS file, you can obtain it at www.aomedia.org/license/patent.
+
+#include "tests/test_api.h"
+
+#include <algorithm>
+#include <cstddef>
+#include <cstdint>
+#include <cstdlib>
+
+#include "avifinfo.h"
+
+namespace avifinfo {
+
+//------------------------------------------------------------------------------
+// Stream definition.
+
+struct StreamData {
+  const uint8_t* data;
+  size_t data_size;
+};
+
+static const uint8_t* StreamRead(void* stream, size_t num_bytes) {
+  if (stream == nullptr) return nullptr;
+  if (num_bytes < 1 || num_bytes > AVIFINFO_MAX_NUM_READ_BYTES) return nullptr;
+
+  StreamData* stream_data = reinterpret_cast<StreamData*>(stream);
+  if (num_bytes > stream_data->data_size) return nullptr;
+  const uint8_t* data = stream_data->data;
+  stream_data->data += num_bytes;
+  stream_data->data_size -= num_bytes;
+  return data;
+}
+
+static void StreamSkip(void* stream, size_t num_bytes) {
+  if (stream == nullptr) std::abort();
+  if (num_bytes < 1) std::abort();
+
+  StreamData* stream_data = reinterpret_cast<StreamData*>(stream);
+  num_bytes = std::min(num_bytes, stream_data->data_size);
+  stream_data->data += num_bytes;
+  stream_data->data_size -= num_bytes;
+}
+
+//------------------------------------------------------------------------------
+
+static bool Equals(const AvifInfoFeatures& lhs, const AvifInfoFeatures& rhs) {
+  return lhs.width == rhs.width && lhs.height == rhs.height &&
+         lhs.bit_depth == rhs.bit_depth &&
+         lhs.num_channels == rhs.num_channels &&
+         lhs.has_gainmap == rhs.has_gainmap &&
+         lhs.gainmap_item_id == rhs.gainmap_item_id &&
+         lhs.primary_item_id_location == rhs.primary_item_id_location &&
+         lhs.primary_item_id_bytes == rhs.primary_item_id_bytes;
+}
+
+// Test a random bitstream of random size, whether it is valid or not.
+int TestApi(const uint8_t* data, size_t data_size) {
+  AvifInfoStatus previous_status_identity = kAvifInfoNotEnoughData;
+  AvifInfoStatus previous_status_features = kAvifInfoNotEnoughData;
+  AvifInfoFeatures previous_features = {0};
+
+  // Check the consistency of the returned status and features:
+  // for a given size and a status that is not kAvifInfoNotEnoughData, any
+  // bigger size (of the same data) should return the same status and features.
+  for (size_t size = 0; size < data_size; ++size) {
+    if (size > 1024 || previous_status_features != kAvifInfoNotEnoughData) {
+      // The behavior is unlikely to change: save computing resources.
+      size = std::min(data_size, size * 2);
+    }
+
+    // Simple raw pointer API.
+    AvifInfoFeatures features;
+    const AvifInfoStatus status_identity = AvifInfoIdentify(data, size);
+    const AvifInfoStatus status_features =
+        AvifInfoGetFeatures(data, size, &features);
+
+    // Once a status different than kAvifInfoNotEnoughData is returned, it
+    // should not change even with more input bytes.
+    if ((previous_status_identity != kAvifInfoNotEnoughData &&
+         status_identity != previous_status_identity) ||
+        (previous_status_features != kAvifInfoNotEnoughData &&
+         status_features != previous_status_features)) {
+      return 1;
+    }
+
+    // Check the features.
+    if (status_features == previous_status_features) {
+      if (!Equals(features, previous_features)) {
+        return 1;
+      }
+    } else if (status_features == kAvifInfoOk) {
+      if (status_identity != kAvifInfoOk) {
+        return 1;
+      }
+      if (features.width == 0u || features.height == 0u ||
+          features.bit_depth == 0u || features.num_channels == 0u ||
+          (!features.has_gainmap && features.gainmap_item_id) ||
+          !features.primary_item_id_location !=
+              !features.primary_item_id_bytes) {
+        return 1;
+      }
+      if (features.primary_item_id_location &&
+          features.primary_item_id_location + features.primary_item_id_bytes >
+              size) {
+        return 1;
+      }
+    } else {
+      if (features.width != 0u || features.height != 0u ||
+          features.bit_depth != 0u || features.num_channels != 0u ||
+          features.has_gainmap != 0u || features.gainmap_item_id != 0u ||
+          features.primary_item_id_location != 0u ||
+          features.primary_item_id_bytes != 0u) {
+        return 1;
+      }
+    }
+
+    // Stream API.
+    AvifInfoFeatures features_stream;
+    StreamData stream_identity = {data, size};
+    StreamData stream_features = {data, size};
+    const AvifInfoStatus status_identity_stream =
+        AvifInfoIdentifyStream(&stream_identity, StreamRead, StreamSkip);
+    const AvifInfoStatus status_features_stream = AvifInfoGetFeaturesStream(
+        &stream_features, StreamRead, StreamSkip, &features_stream);
+    // Both API should have exactly the same behavior, errors included.
+    if (status_identity_stream != status_identity) {
+      return 1;
+    }
+    // AvifInfoGetFeaturesStream() should only be called if
+    // AvifInfoIdentifyStream() was a success. Call it anyway to make sure it
+    // does not crash, but only check the returned status and features when it
+    // makes sense.
+    if (status_identity_stream == kAvifInfoOk &&
+        (status_features_stream != status_features ||
+         !Equals(features_stream, features))) {
+      return 1;
+    }
+
+    // Another way of calling the stream API: reuse the stream object that was
+    // used for AvifInfoIdentifyStream().
+    AvifInfoFeatures features_stream_reused;
+    StreamData stream_reused = stream_identity;
+    const AvifInfoStatus status_features_reused_stream =
+        AvifInfoGetFeaturesStream(&stream_reused, StreamRead, StreamSkip,
+                                  &features_stream_reused);
+    // AvifInfoGetFeaturesStream() should only be called if
+    // AvifInfoIdentifyStream() was a success. Call it anyway to make sure it
+    // does not crash, but only check the returned status and features when it
+    // makes sense.
+    if (status_identity_stream == kAvifInfoOk) {
+      if (status_features_reused_stream != status_features_stream) {
+        return 1;
+      }
+      if (status_features_reused_stream == kAvifInfoOk) {
+        // Offset any location-dependent feature as described in avifinfo.h.
+        features_stream_reused.primary_item_id_location +=
+            size - stream_identity.data_size;
+      }
+      if (!Equals(features_stream_reused, features_stream)) {
+        return 1;
+      }
+    }
+
+    // Another way of calling the stream API: no provided user-defined skip
+    // method.
+    AvifInfoFeatures features_no_skip;
+    StreamData stream_identity_no_skip = {data, size};
+    StreamData stream_features_no_skip = {data, size};
+    const AvifInfoStatus status_identity_no_skip = AvifInfoIdentifyStream(
+        &stream_identity_no_skip, StreamRead, /*skip=*/nullptr);
+    const AvifInfoStatus status_features_no_skip =
+        AvifInfoGetFeaturesStream(&stream_features_no_skip, StreamRead,
+                                  /*skip=*/nullptr, &features_no_skip);
+    // There may be some difference in status. For example, a valid or invalid
+    // status could be returned just after skipping some bytes. If the skip
+    // argument is omitted, these bytes will be read instead. If some of these
+    // bytes are missing, kAvifInfoNotEnoughData will be returned instead of the
+    // expected success or failure status.
+    if (status_identity_no_skip != status_identity_stream &&
+        !(status_identity_no_skip == kAvifInfoNotEnoughData &&
+          status_identity_stream == kAvifInfoOk)) {
+      return 1;
+    }
+    if (status_identity_stream == kAvifInfoOk) {
+      if ((status_features_no_skip != status_features &&
+           !(status_features_no_skip == kAvifInfoNotEnoughData &&
+             status_features != kAvifInfoOk)) ||
+          !Equals(features_no_skip, features)) {
+        return 1;
+      }
+    }
+
+    previous_status_identity = status_identity;
+    previous_status_features = status_features;
+    previous_features = features;
+  }
+  return 0;
+}
+}  // namespace avifinfo
diff --git a/tests/test_api.h b/tests/test_api.h
new file mode 100644
index 0000000..383748f
--- /dev/null
+++ b/tests/test_api.h
@@ -0,0 +1,23 @@
+// Copyright (c) 2025, Alliance for Open Media. All rights reserved
+//
+// This source code is subject to the terms of the BSD 2 Clause License and
+// the Alliance for Open Media Patent License 1.0. If the BSD 2 Clause License
+// was not distributed with this source code in the LICENSE file, you can
+// obtain it at www.aomedia.org/license/software. If the Alliance for Open
+// Media Patent License 1.0 was not distributed with this source code in the
+// PATENTS file, you can obtain it at www.aomedia.org/license/patent.
+
+#ifndef TESTS_TEST_API_H_
+#define TESTS_TEST_API_H_
+
+#include <cstddef>
+#include <cstdint>
+
+namespace avifinfo {
+
+// Returns 0 if the API is correct, or aborts the program otherwise.
+int TestApi(const uint8_t* data, size_t data_size);
+
+}  // namespace avifinfo
+
+#endif  // TESTS_TEST_API_H_
diff --git a/tools/avifinfo_tool.cc b/tools/avifinfo_tool.cc
index 4a086fe..4bf4b6f 100644
--- a/tools/avifinfo_tool.cc
+++ b/tools/avifinfo_tool.cc
@@ -20,6 +20,7 @@
 
 #include "avif/avif.h"
 #include "avifinfo.h"
+#include "tests/test_api.h"
 
 namespace {
 
@@ -100,9 +101,6 @@
   return result;
 }
 
-// Reuses the fuzz target for easy library validation.
-extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t data_size);
-
 // Aggregated stats about the decoded/parsed AVIF files.
 struct Stats {
   uint32_t num_files_invalid_at_decode = 0;
@@ -200,7 +198,7 @@
 // Returns false in case of error.
 bool ValidateFile(const std::string& path, const uint8_t* data,
                   size_t data_size) {
-  if (LLVMFuzzerTestOneInput(data, data_size) != 0) {
+  if (avifinfo::TestApi(data, data_size) != 0) {
     std::cout << "validation failed for " << path << std::endl;
     return false;
   }