Added test-extract.c and a new regression test extract-parse.sh.
authorDan Fandrich <dan@coneharvesters.com>
Fri, 22 Feb 2019 15:17:01 +0000 (16:17 +0100)
committerDan Fandrich <dan@coneharvesters.com>
Fri, 22 Feb 2019 15:51:48 +0000 (16:51 +0100)
This extracts the EXIF tags from an image then compares the parsed value
of the extracted tags with those of the original file. This ensures that
the tags are written properly, without change in tag data. The MakerNote
tag sometimes has a harmless, slight difference in size because of
padding being removed.

However, in developing this test, I found that the Olympus variant 4
MakerNote has a huge size difference. This might be harmless (there
might just be a lot of padding removed) but it's also possible that
these MakerNotes aren't being properly parsed.  This discrepancy should
be investigated.

The exif_data_save_data() function is also returning some JPEG markers
at the end of the buffer which I wasn't expecting.  This also should be
investigated.

The test is enabled anyway in the meantime to reduce the chance of
regressions in the remaining tags.

test/Makefile.am
test/extract-parse.sh [new file with mode: 0755]
test/test-extract.c [new file with mode: 0644]

index 4083e50..04ad079 100644 (file)
@@ -12,19 +12,19 @@ SUBDIRS = nls
 #      here yet.
 
 TESTS = test-mem test-value test-integers test-parse test-tagtable test-sorted \
-       test-fuzzer parse-regression.sh swap-byte-order.sh
+       test-fuzzer parse-regression.sh swap-byte-order.sh extract-parse.sh
 
 if USE_FAILMALLOC
 TESTS += check-failmalloc.sh
 endif
 
 check_PROGRAMS = test-mem test-mnote test-value test-integers test-parse \
-       test-tagtable test-sorted test-fuzzer
+       test-tagtable test-sorted test-fuzzer test-extract
 
 LDADD = $(top_builddir)/libexif/libexif.la $(LTLIBINTL)
 
 EXTRA_DIST = check-vars.sh.in parse-regression.sh swap-byte-order.sh \
-       check-failmalloc.sh \
+       extract-parse.sh check-failmalloc.sh \
        testdata/canon_makernote_variant_1.jpg \
        testdata/canon_makernote_variant_1.jpg.parsed \
        testdata/fuji_makernote_variant_1.jpg \
diff --git a/test/extract-parse.sh b/test/extract-parse.sh
new file mode 100755 (executable)
index 0000000..3bcfe33
--- /dev/null
@@ -0,0 +1,39 @@
+#!/bin/sh
+# Compares the parsed EXIF data extracted from test images with the parsed EXIF
+# data in the original images. This tests that the tag parsing and writing
+# round-trip produces an EXIF structure with the same meaning as the original.
+srcdir="${srcdir:-.}"
+TMPORIGINAL="$(mktemp)"
+TMPEXTRACTED="$(mktemp)"
+TMPDATA="$(mktemp)"
+trap 'rm -f "${TMPORIGINAL}" "${TMPEXTRACTED}" "${TMPDATA}"' 0
+
+# Remove the file name, which is a harmless difference between the two outputs.
+# Also delete the size of the MakerNote. Since the MakerNote is parsed
+# internally and rewritten, it can sometimes have slightly different padding
+# and therefore slightly different size, which is a semantically meaningless
+# difference.
+# FIXME: Not all MakerNote differences are harmless. For example,
+# olympus_makernote_variant_4.jpg has a huge size difference, probably because
+# of a parsing bug in libexif. This should be investigated. Ideally, this would
+# ignore small differences in size but trigger on larger differences.
+parse_canonicalize () {
+    sed \
+        -e '/^File /d' \
+        -e '/MakerNote (Undefined)$/{N;N;d}'
+}
+
+# Ensure that names are untranslated
+LANG=
+LANGUAGE=
+LC_ALL=C
+export LANG LANGUAGE LC_ALL
+for fn in "${srcdir}"/testdata/*.jpg ; do
+    ./test-parse "${fn}" | parse_canonicalize > "${TMPORIGINAL}"
+    ./test-extract -o "${TMPDATA}" "${fn}"
+    ./test-parse "${TMPDATA}" | parse_canonicalize > "${TMPEXTRACTED}"
+    if ! diff "${TMPORIGINAL}" "${TMPEXTRACTED}"; then
+        echo Error parsing "$fn"
+        exit 1
+    fi
+done
diff --git a/test/test-extract.c b/test/test-extract.c
new file mode 100644 (file)
index 0000000..0c82d73
--- /dev/null
@@ -0,0 +1,102 @@
+/** \file test-extract.c
+ * \brief Extract EXIF data from a file and write it to another file.
+ *
+ * Copyright (C) 2019 Dan Fandrich <dan@coneharvesters.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA  02110-1301  USA.
+ *
+ */
+
+#include "libexif/exif-data.h"
+
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+
+static const unsigned char header[4] = {'\xff', '\xd8', '\xff', '\xe1'};
+
+int main(const int argc, const char *argv[])
+{
+  int first = 1;
+  const char *fn = "input.jpg";
+  const char *outfn = "output.exif";
+  ExifData *d;
+  unsigned char *buf;
+  unsigned int len;
+  FILE *f;
+  unsigned char lenbuf[2];
+
+  if (argc > 1 && !strcmp(argv[1], "-o")) {
+      outfn = argv[2];
+      first += 2;
+  }
+  if (argc > first) {
+      fn = argv[first];
+      ++first;
+  }
+  if (argc > first) {
+      fprintf (stderr, "Too many arguments\n");
+      return 1;
+  }
+
+  d = exif_data_new_from_file(fn);
+  if (!d) {
+      fprintf (stderr, "Could not load data from '%s'!\n", fn);
+      return 1;
+  }
+
+  exif_data_save_data(d, &buf, &len);
+  exif_data_unref(d);
+
+  if (!buf) {
+      fprintf (stderr, "Could not extract EXIF data!\n");
+      return 2;
+  }
+
+  f = fopen(outfn, "wb");
+  if (!f) {
+      fprintf (stderr, "Could not open '%s' for writing!\n", outfn);
+      return 1;
+  }
+  /* Write EXIF with headers and length. */
+  if (fwrite(header, 1, sizeof(header), f) != sizeof(header)) {
+      fprintf (stderr, "Could not write to '%s'!\n", outfn);
+      return 3;
+  }
+  /*
+   * FIXME: The buffer from exif_data_save_data() seems to contain extra 0xffd8
+   * 0xffd9 JPEG markers at the end that I wasn't expecting, making the length
+   * seem too long. Should those markers really be included?
+   */
+  exif_set_short(lenbuf, EXIF_BYTE_ORDER_MOTOROLA, len);
+  if (fwrite(lenbuf, 1, 2, f) != 2) {
+      fprintf (stderr, "Could not write to '%s'!\n", outfn);
+      return 3;
+  }
+  if (fwrite(buf, 1, len, f) != len) {
+      fprintf (stderr, "Could not write to '%s'!\n", outfn);
+      return 3;
+  }
+  if (fclose(f) != 0) {
+      fprintf (stderr, "Could not close '%s'!\n", outfn);
+      return 3;
+  }
+  free(buf);
+  fprintf (stderr, "Wrote EXIF data to '%s'\n", outfn);
+
+  return 0;
+}