Add test for all enc/dec codec pairs

diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index b3b1eb6..42f1c54 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -95,6 +95,11 @@
     target_include_directories(avifcllitest PRIVATE ${GTEST_INCLUDE_DIRS})
     add_test(NAME avifcllitest COMMAND avifcllitest)
 
+    add_executable(avifcodectest gtest/avifcodectest.cc)
+    target_link_libraries(avifcodectest aviftest_helpers ${GTEST_BOTH_LIBRARIES})
+    target_include_directories(avifcodectest PRIVATE ${GTEST_INCLUDE_DIRS})
+    add_test(NAME avifcodectest COMMAND avifcodectest)
+
     add_executable(avifgridapitest gtest/avifgridapitest.cc)
     target_link_libraries(avifgridapitest avif_internal aviftest_helpers ${GTEST_BOTH_LIBRARIES})
     target_include_directories(avifgridapitest PRIVATE ${GTEST_INCLUDE_DIRS})
diff --git a/tests/gtest/avifcodectest.cc b/tests/gtest/avifcodectest.cc
new file mode 100644
index 0000000..6e0a41c
--- /dev/null
+++ b/tests/gtest/avifcodectest.cc
@@ -0,0 +1,63 @@
+// Copyright 2023 Google LLC
+// SPDX-License-Identifier: BSD-2-Clause
+
+#include "avif/avif.h"
+#include "aviftest_helpers.h"
+#include "gtest/gtest.h"
+
+namespace libavif {
+namespace {
+
+class CodecTest : public testing::TestWithParam<
+                      std::tuple</*encoding_codec=*/avifCodecChoice,
+                                 /*decoding_codec=*/avifCodecChoice>> {};
+
+TEST_P(CodecTest, EncodeDecode) {
+  const avifCodecChoice encoding_codec = std::get<0>(GetParam());
+  const avifCodecChoice decoding_codec = std::get<1>(GetParam());
+
+  if (avifCodecName(encoding_codec, AVIF_CODEC_FLAG_CAN_ENCODE) == nullptr ||
+      avifCodecName(decoding_codec, AVIF_CODEC_FLAG_CAN_DECODE) == nullptr) {
+    GTEST_SKIP() << "Codec unavailable, skip test.";
+  }
+
+  // AVIF_CODEC_CHOICE_SVT requires dimensions to be at least 64 pixels.
+  testutil::AvifImagePtr image =
+      testutil::CreateImage(/*width=*/64, /*height=*/64, /*depth=*/8,
+                            AVIF_PIXEL_FORMAT_YUV420, AVIF_PLANES_ALL);
+  ASSERT_NE(image, nullptr);
+  testutil::FillImageGradient(image.get());
+
+  testutil::AvifEncoderPtr encoder(avifEncoderCreate(), avifEncoderDestroy);
+  ASSERT_NE(encoder, nullptr);
+  encoder->codecChoice = encoding_codec;
+  encoder->quality = encoder->qualityAlpha = 90;  // Small loss.
+  testutil::AvifRwData encoded;
+  ASSERT_EQ(avifEncoderWrite(encoder.get(), image.get(), &encoded),
+            AVIF_RESULT_OK);
+
+  testutil::AvifDecoderPtr decoder(avifDecoderCreate(), avifDecoderDestroy);
+  ASSERT_NE(decoder, nullptr);
+  decoder->codecChoice = decoding_codec;
+  testutil::AvifImagePtr decoded(avifImageCreateEmpty(), avifImageDestroy);
+  ASSERT_NE(decoded, nullptr);
+  ASSERT_EQ(avifDecoderReadMemory(decoder.get(), decoded.get(), encoded.data,
+                                  encoded.size),
+            AVIF_RESULT_OK);
+
+  // AVIF_CODEC_CHOICE_SVT leads to more distortion than other codecs with
+  // default settings.
+  ASSERT_GT(testutil::GetPsnr(*image, *decoded), 30.0);
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    All, CodecTest,
+    testing::Combine(/*encoding_codec=*/testing::Values(AVIF_CODEC_CHOICE_AOM,
+                                                        AVIF_CODEC_CHOICE_RAV1E,
+                                                        AVIF_CODEC_CHOICE_SVT),
+                     /*decoding_codec=*/testing::Values(
+                         AVIF_CODEC_CHOICE_AOM, AVIF_CODEC_CHOICE_DAV1D,
+                         AVIF_CODEC_CHOICE_LIBGAV1)));
+
+}  // namespace
+}  // namespace libavif