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;
}