Adding a basic test for lossless. (#984)

This will be used when YCgCo-R is added next.
diff --git a/apps/avifenc.c b/apps/avifenc.c
index 7529e89..1a1a0ff 100644
--- a/apps/avifenc.c
+++ b/apps/avifenc.c
@@ -278,27 +278,14 @@
         return AVIF_APP_FILE_FORMAT_UNKNOWN;
     }
 
-    avifAppFileFormat nextInputFormat = avifGuessFileFormat(input->files[input->fileIndex].filename);
-    if (nextInputFormat == AVIF_APP_FILE_FORMAT_Y4M) {
-        if (!y4mRead(input->files[input->fileIndex].filename, image, sourceTiming, &input->frameIter)) {
-            return AVIF_APP_FILE_FORMAT_UNKNOWN;
-        }
-        if (outDepth) {
-            *outDepth = image->depth;
-        }
-    } else if (nextInputFormat == AVIF_APP_FILE_FORMAT_JPEG) {
-        if (!avifJPEGRead(input->files[input->fileIndex].filename, image, input->requestedFormat, input->requestedDepth)) {
-            return AVIF_APP_FILE_FORMAT_UNKNOWN;
-        }
-        if (outDepth) {
-            *outDepth = 8;
-        }
-    } else if (nextInputFormat == AVIF_APP_FILE_FORMAT_PNG) {
-        if (!avifPNGRead(input->files[input->fileIndex].filename, image, input->requestedFormat, input->requestedDepth, outDepth)) {
-            return AVIF_APP_FILE_FORMAT_UNKNOWN;
-        }
-    } else {
-        fprintf(stderr, "Unrecognized file format: %s\n", input->files[input->fileIndex].filename);
+    const avifAppFileFormat nextInputFormat = avifReadImage(input->files[input->fileIndex].filename,
+                                                            input->requestedFormat,
+                                                            input->requestedDepth,
+                                                            image,
+                                                            outDepth,
+                                                            sourceTiming,
+                                                            &input->frameIter);
+    if (nextInputFormat == AVIF_APP_FILE_FORMAT_UNKNOWN) {
         return AVIF_APP_FILE_FORMAT_UNKNOWN;
     }
 
diff --git a/apps/shared/avifutil.c b/apps/shared/avifutil.c
index 81281a4..4721b8d 100644
--- a/apps/shared/avifutil.c
+++ b/apps/shared/avifutil.c
@@ -7,6 +7,10 @@
 #include <stdio.h>
 #include <string.h>
 
+#include "avifjpeg.h"
+#include "avifpng.h"
+#include "y4m.h"
+
 static int32_t calcGCD(int32_t a, int32_t b)
 {
     if (a < 0) {
@@ -220,6 +224,40 @@
     return AVIF_APP_FILE_FORMAT_UNKNOWN;
 }
 
+avifAppFileFormat avifReadImage(const char * filename,
+                                avifPixelFormat requestedFormat,
+                                int requestedDepth,
+                                avifImage * image,
+                                uint32_t * outDepth,
+                                avifAppSourceTiming * sourceTiming,
+                                struct y4mFrameIterator ** frameIter)
+{
+    const avifAppFileFormat format = avifGuessFileFormat(filename);
+    if (format == AVIF_APP_FILE_FORMAT_Y4M) {
+        if (!y4mRead(filename, image, sourceTiming, frameIter)) {
+            return AVIF_APP_FILE_FORMAT_UNKNOWN;
+        }
+        if (outDepth) {
+            *outDepth = image->depth;
+        }
+    } else if (format == AVIF_APP_FILE_FORMAT_JPEG) {
+        if (!avifJPEGRead(filename, image, requestedFormat, requestedDepth)) {
+            return AVIF_APP_FILE_FORMAT_UNKNOWN;
+        }
+        if (outDepth) {
+            *outDepth = 8;
+        }
+    } else if (format == AVIF_APP_FILE_FORMAT_PNG) {
+        if (!avifPNGRead(filename, image, requestedFormat, requestedDepth, outDepth)) {
+            return AVIF_APP_FILE_FORMAT_UNKNOWN;
+        }
+    } else {
+        fprintf(stderr, "Unrecognized file format: %s\n", filename);
+        return AVIF_APP_FILE_FORMAT_UNKNOWN;
+    }
+    return format;
+}
+
 void avifDumpDiagnostics(const avifDiagnostics * diag)
 {
     if (!*diag->error) {
diff --git a/apps/shared/avifutil.h b/apps/shared/avifutil.h
index 22dfb03..29a6087 100644
--- a/apps/shared/avifutil.h
+++ b/apps/shared/avifutil.h
@@ -56,6 +56,18 @@
     uint64_t timescale; // timescale of the media (Hz)
 } avifAppSourceTiming;
 
+struct y4mFrameIterator;
+// Reads an image from a file with the requested format and depth.
+// In case of a y4m file, sourceTiming and frameIter can be set.
+// Returns AVIF_APP_FILE_FORMAT_UNKNOWN in case of error.
+avifAppFileFormat avifReadImage(const char * filename,
+                                avifPixelFormat requestedFormat,
+                                int requestedDepth,
+                                avifImage * image,
+                                uint32_t * outDepth,
+                                avifAppSourceTiming * sourceTiming,
+                                struct y4mFrameIterator ** frameIter);
+
 // Used by image decoders when the user doesn't explicitly choose a format with --yuv
 // This must match the cited fallback for "--yuv auto" in avifenc.c's syntax() function.
 #define AVIF_APP_DEFAULT_PIXEL_FORMAT AVIF_PIXEL_FORMAT_YUV444
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index a46342a..eab84e6 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -42,9 +42,14 @@
 ################################################################################
 # GoogleTest
 
-if(AVIF_ENABLE_GTEST)
+if(AVIF_ENABLE_GTEST OR AVIF_BUILD_APPS)
     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()
+
+if(AVIF_ENABLE_GTEST)
     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})
@@ -63,9 +68,6 @@
         find_package(GTest REQUIRED)
     endif()
 
-    add_library(aviftest_helpers OBJECT gtest/aviftest_helpers.cc)
-    target_link_libraries(aviftest_helpers avif ${AVIF_PLATFORM_LIBRARIES})
-
     add_executable(avifgridapitest gtest/avifgridapitest.cc)
     target_link_libraries(avifgridapitest aviftest_helpers ${GTEST_BOTH_LIBRARIES})
     target_include_directories(avifgridapitest PRIVATE ${GTEST_INCLUDE_DIRS})
@@ -96,3 +98,17 @@
 else()
     message(STATUS "Most tests are disabled because AVIF_ENABLE_GTEST is OFF.")
 endif()
+
+################################################################################
+# Bash tests
+
+if(AVIF_BUILD_APPS)
+    # 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)
+    add_test(
+        NAME test_cmd
+        COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/test_cmd.sh ${CMAKE_BINARY_DIR}
+        WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
+endif()
diff --git a/tests/gtest/are_images_equal.cc b/tests/gtest/are_images_equal.cc
new file mode 100644
index 0000000..7e01a79
--- /dev/null
+++ b/tests/gtest/are_images_equal.cc
@@ -0,0 +1,54 @@
+// Copyright 2022 Google LLC. All rights reserved.
+// SPDX-License-Identifier: BSD-2-Clause
+// Compares two files and returns whether they are the same once decoded.
+
+#include <iostream>
+
+#include "aviftest_helpers.h"
+#include "avifutil.h"
+
+using libavif::testutil::AvifImagePtr;
+
+int main(int argc, char** argv) {
+  if (argc != 4) {
+    std::cerr << "Wrong argument: " << argv[0]
+              << " file1 file2 ignore_alpha_flag" << std::endl;
+    return 2;
+  }
+  AvifImagePtr decoded[2] = {
+      AvifImagePtr(avifImageCreateEmpty(), avifImageDestroy),
+      AvifImagePtr(avifImageCreateEmpty(), avifImageDestroy)};
+  if (!decoded[0] || !decoded[1]) {
+    std::cerr << "Cannot create AVIF images." << std::endl;
+    return 2;
+  }
+  uint32_t depth[2];
+  // Request the bit depth closest to the bit depth of the input file.
+  constexpr int kRequestedDepth = 0;
+  constexpr avifPixelFormat requestedFormat = AVIF_PIXEL_FORMAT_NONE;
+  for (int i : {0, 1}) {
+    // Make sure no color conversion happens.
+    decoded[i]->matrixCoefficients = AVIF_MATRIX_COEFFICIENTS_IDENTITY;
+    if (avifReadImage(argv[i + 1], requestedFormat, kRequestedDepth,
+                      decoded[i].get(), &depth[i], nullptr,
+                      nullptr) == AVIF_APP_FILE_FORMAT_UNKNOWN) {
+      std::cerr << "Image " << argv[i + 1] << " cannot be read." << std::endl;
+      return 2;
+    }
+  }
+
+  if (depth[0] != depth[1]) {
+    std::cerr << "Images " << argv[1] << " and " << argv[2]
+              << " have different depths." << std::endl;
+    return 1;
+  }
+  if (!libavif::testutil::AreImagesEqual(*decoded[0], *decoded[1],
+                                         std::stoi(argv[3]))) {
+    std::cerr << "Images " << argv[1] << " and " << argv[2] << " are different."
+              << std::endl;
+    return 1;
+  }
+  std::cout << "Images " << argv[1] << " and " << argv[2] << " are identical."
+            << std::endl;
+  return 0;
+}
diff --git a/tests/gtest/aviftest_helpers.cc b/tests/gtest/aviftest_helpers.cc
index f35775e..6ca14dc 100644
--- a/tests/gtest/aviftest_helpers.cc
+++ b/tests/gtest/aviftest_helpers.cc
@@ -154,7 +154,8 @@
 //------------------------------------------------------------------------------
 
 // Returns true if image1 and image2 are identical.
-bool AreImagesEqual(const avifImage& image1, const avifImage& image2) {
+bool AreImagesEqual(const avifImage& image1, const avifImage& image2,
+                    bool ignore_alpha) {
   if (image1.width != image2.width || image1.height != image2.height ||
       image1.depth != image2.depth || image1.yuvFormat != image2.yuvFormat ||
       image1.yuvRange != image2.yuvRange) {
@@ -166,6 +167,7 @@
   avifGetPixelFormatInfo(image1.yuvFormat, &info);
 
   for (int c = 0; c < 4; c++) {
+    if (ignore_alpha && c == AVIF_CHAN_A) continue;
     uint8_t* row1 =
         (c == AVIF_CHAN_A) ? image1.alphaPlane : image1.yuvPlanes[c];
     uint8_t* row2 =
diff --git a/tests/gtest/aviftest_helpers.h b/tests/gtest/aviftest_helpers.h
index adea4f9..b7c3138 100644
--- a/tests/gtest/aviftest_helpers.h
+++ b/tests/gtest/aviftest_helpers.h
@@ -54,7 +54,10 @@
                       uint32_t value);
 
 // Returns true if both images have the same features and pixel values.
-bool AreImagesEqual(const avifImage& image1, const avifImage& image2);
+// If ignore_alpha is true, the alpha channel is not taken into account in the
+// comparison.
+bool AreImagesEqual(const avifImage& image1, const avifImage& image2,
+                    bool ignore_alpha = false);
 
 //------------------------------------------------------------------------------
 
diff --git a/tests/test_cmd.sh b/tests/test_cmd.sh
new file mode 100755
index 0000000..6bfccbb
--- /dev/null
+++ b/tests/test_cmd.sh
@@ -0,0 +1,67 @@
+#!/bin/bash
+# Copyright 2022 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# ------------------------------------------------------------------------------
+#
+# tests for command lines
+
+set -ex
+
+if [[ "$#" -ge 1 ]]; then
+  # eval so that the passed in directory can contain variables.
+  BINARY_DIR="$(eval echo "$1")"
+else
+  # assume "tests" is the current directory
+  BINARY_DIR=..
+fi
+if [[ "$#" -ge 2 ]]; then
+  TESTDATA_DIR="$(eval echo "$2")"
+else
+  TESTDATA_DIR=./data
+fi
+
+AVIFENC="${BINARY_DIR}/avifenc"
+AVIFDEC="${BINARY_DIR}/avifdec"
+ARE_IMAGES_EQUAL="${BINARY_DIR}/tests/are_images_equal"
+TMP_ENCODED_FILE=/tmp/encoded.avif
+DECODED_FILE=/tmp/decoded.png
+PNG_FILE=/tmp/kodim03.png
+
+# Prepare some extra data.
+set +x
+echo "Generating a color PNG"
+"${AVIFENC}" -s 10 "${TESTDATA_DIR}"/kodim03_yuv420_8bpc.y4m -o "${TMP_ENCODED_FILE}" &> /dev/null
+"${AVIFDEC}" "${TMP_ENCODED_FILE}" "${PNG_FILE}"  &> /dev/null
+set -x
+
+# Basic calls.
+"${AVIFENC}" --version
+"${AVIFDEC}" --version
+
+# Lossless test.
+echo "Testing basic lossless"
+"${AVIFENC}" -s 10 -l "${PNG_FILE}" -o "${TMP_ENCODED_FILE}"
+"${AVIFDEC}" "${TMP_ENCODED_FILE}" "${DECODED_FILE}"
+"${ARE_IMAGES_EQUAL}" "${PNG_FILE}" "${DECODED_FILE}" 0
+
+# Test code that should fail.
+set +e
+"${ARE_IMAGES_EQUAL}" "${TESTDATA_DIR}"/kodim23_yuv420_8bpc.y4m "${DECODED_FILE}" 0
+if [[ $? -ne 1 ]]; then
+  echo "Image should be different"
+  exit 1
+fi
+
+echo "TEST OK"
+exit 0