]> git.ipfire.org Git - thirdparty/libarchive.git/commitdiff
Add some 7zip zstd reader tests
authorMostyn Bramley-Moore <mostyn@antipode.se>
Mon, 29 May 2023 06:25:37 +0000 (08:25 +0200)
committerMartin Matuška <martin@matuska.de>
Thu, 13 Jul 2023 22:14:52 +0000 (00:14 +0200)
There is a popular 7-Zip fork with zstandard support, with releases as
far back as 2017:
https://github.com/mcmilk/7-Zip-zstd.git

Zstandard support is not yet available in 7-Zip, though it is planned
for a future release:
https://sourceforge.net/p/sevenzip/feature-requests/1580/

This change adds a couple of tests for reading 7-Zip archives which
use zstandard compression. They are expected to fail until support is
added in the following commit.

Relates to #1656.

libarchive/test/test_read_format_7zip.c
libarchive/test/test_read_format_7zip_solid_zstd.7z.uu [new file with mode: 0644]
libarchive/test/test_read_format_7zip_zstd.7z.uu [new file with mode: 0644]
libarchive/test/test_read_format_7zip_zstd_bcj.7z.uu [new file with mode: 0644]
libarchive/test/test_read_format_7zip_zstd_nobcj.7z.uu [new file with mode: 0644]

index 3c72595aeef7cbd6b4749ca38fb0ddb28765cb74..ca9074557fd6585000e378e89fc22a5f582aca73 100644 (file)
@@ -30,6 +30,9 @@ __FBSDID("$FreeBSD");
 #define        open            _open
 #endif
 
+#define __LIBARCHIVE_BUILD
+#include <archive_crc32.h>
+
 /*
  * Extract a non-encoded file.
  * The header of the 7z archive files is not encoded.
@@ -283,6 +286,141 @@ test_extract_all_files(const char *refname)
        assertEqualInt(ARCHIVE_OK, archive_read_free(a));
 }
 
+/*
+ * Extract multi files.
+ * Like test_extract_all_files, but with zstandard compression.
+ */
+static void
+test_extract_all_files_zstd(const char *refname)
+{
+       struct archive_entry *ae;
+       struct archive *a;
+       char buff[128];
+
+       extract_reference_file(refname);
+       assert((a = archive_read_new()) != NULL);
+       assertEqualIntA(a, ARCHIVE_OK, archive_read_support_filter_all(a));
+       assertEqualIntA(a, ARCHIVE_OK, archive_read_support_format_all(a));
+       assertEqualIntA(a, ARCHIVE_OK,
+           archive_read_open_filename(a, refname, 10240));
+
+       /* Verify directory dir1. Note that this comes before the dir1/file1 entry in recent versions of 7-Zip. */
+       assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae));
+       assertEqualInt((AE_IFDIR | 0755), archive_entry_mode(ae));
+       assertEqualString("dir1/", archive_entry_pathname(ae));
+       assertEqualInt(2764801, archive_entry_mtime(ae));
+       assertEqualInt(archive_entry_is_encrypted(ae), 0);
+       assertEqualIntA(a, archive_read_has_encrypted_entries(a), 0);
+
+       /* Verify regular file1. */
+       assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae));
+       assertEqualInt((AE_IFREG | 0644), archive_entry_mode(ae));
+       assertEqualString("dir1/file1", archive_entry_pathname(ae));
+       assertEqualInt(86401, archive_entry_mtime(ae));
+       assertEqualInt(13, archive_entry_size(ae));
+       assertEqualInt(archive_entry_is_encrypted(ae), 0);
+       assertEqualIntA(a, archive_read_has_encrypted_entries(a), 0);
+       assertEqualInt(13, archive_read_data(a, buff, sizeof(buff)));
+       assertEqualMem(buff, "aaaaaaaaaaaa\n", 13);
+
+       /* Verify regular file2. */
+       assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae));
+       assertEqualInt((AE_IFREG | 0644), archive_entry_mode(ae));
+       assertEqualString("file2", archive_entry_pathname(ae));
+       assertEqualInt(86401, archive_entry_mtime(ae));
+       assertEqualInt(26, archive_entry_size(ae));
+       assertEqualInt(archive_entry_is_encrypted(ae), 0);
+       assertEqualIntA(a, archive_read_has_encrypted_entries(a), 0);
+       assertEqualInt(26, archive_read_data(a, buff, sizeof(buff)));
+       assertEqualMem(buff, "aaaaaaaaaaaa\nbbbbbbbbbbbb\n", 26);
+
+       /* Verify regular file3. */
+       assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae));
+       assertEqualInt((AE_IFREG | 0644), archive_entry_mode(ae));
+       assertEqualString("file3", archive_entry_pathname(ae));
+       assertEqualInt(86401, archive_entry_mtime(ae));
+       assertEqualInt(39, archive_entry_size(ae));
+       assertEqualInt(archive_entry_is_encrypted(ae), 0);
+       assertEqualIntA(a, archive_read_has_encrypted_entries(a), 0);
+       assertEqualInt(39, archive_read_data(a, buff, sizeof(buff)));
+       assertEqualMem(buff, "aaaaaaaaaaaa\nbbbbbbbbbbbb\ncccccccccccc\n", 39);
+
+       /* Verify regular file4. */
+       assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae));
+       assertEqualInt((AE_IFREG | 0644), archive_entry_mode(ae));
+       assertEqualString("file4", archive_entry_pathname(ae));
+       assertEqualInt(86401, archive_entry_mtime(ae));
+       assertEqualInt(52, archive_entry_size(ae));
+       assertEqualInt(archive_entry_is_encrypted(ae), 0);
+       assertEqualIntA(a, archive_read_has_encrypted_entries(a), 0);
+       assertEqualInt(52, archive_read_data(a, buff, sizeof(buff)));
+       assertEqualMem(buff,
+           "aaaaaaaaaaaa\nbbbbbbbbbbbb\ncccccccccccc\ndddddddddddd\n", 52);
+
+       assertEqualInt(5, archive_file_count(a));
+
+       /* End of archive. */
+       assertEqualIntA(a, ARCHIVE_EOF, archive_read_next_header(a, &ae));
+
+       /* Verify archive format. */
+       assertEqualIntA(a, ARCHIVE_FILTER_NONE, archive_filter_code(a, 0));
+       assertEqualIntA(a, ARCHIVE_FORMAT_7ZIP, archive_format(a));
+
+       /* Close the archive. */
+       assertEqualInt(ARCHIVE_OK, archive_read_close(a));
+       assertEqualInt(ARCHIVE_OK, archive_read_free(a));
+}
+
+/*
+ * Extract file from an archives using ZSTD compression with and without BCJ.
+ */
+static void
+test_extract_file_zstd_bcj_nobjc(const char *refname)
+{
+       struct archive_entry *ae;
+       struct archive *a;
+       char buff[4096];
+       uint32_t computed_crc = 0;
+       uint32_t expected_crc = 0xbd66eebc;
+
+       extract_reference_file(refname);
+       assert((a = archive_read_new()) != NULL);
+       assertEqualIntA(a, ARCHIVE_OK, archive_read_support_filter_all(a));
+       assertEqualIntA(a, ARCHIVE_OK, archive_read_support_format_all(a));
+       assertEqualIntA(a, ARCHIVE_OK,
+           archive_read_open_filename(a, refname, 10240));
+
+       /* Verify regular file: hw. */
+       assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae));
+       assertEqualInt((AE_IFREG | 0775), archive_entry_mode(ae));
+       assertEqualString("hw", archive_entry_pathname(ae));
+       assertEqualInt(1685913368, archive_entry_mtime(ae));
+       assertEqualInt(15952, archive_entry_size(ae));
+       assertEqualInt(archive_entry_is_encrypted(ae), 0);
+       assertEqualIntA(a, archive_read_has_encrypted_entries(a), 0);
+
+       for (;;) {
+               la_ssize_t bytes_read = archive_read_data(a, buff, sizeof(buff));
+               assert(bytes_read >= 0);
+               if (bytes_read == 0) break;
+               computed_crc = crc32(computed_crc, buff, bytes_read);
+       }
+       assertEqualInt(computed_crc, expected_crc);
+
+       assertEqualInt(1, archive_file_count(a));
+
+       /* End of archive. */
+       assertEqualIntA(a, ARCHIVE_EOF, archive_read_next_header(a, &ae));
+
+       /* Verify archive format. */
+       assertEqualIntA(a, ARCHIVE_FILTER_NONE, archive_filter_code(a, 0));
+       assertEqualIntA(a, ARCHIVE_FORMAT_7ZIP, archive_format(a));
+
+       /* Close the archive. */
+       assertEqualInt(ARCHIVE_OK, archive_read_close(a));
+       assertEqualInt(ARCHIVE_OK, archive_read_free(a));
+}
+
 /*
  * Extract last file.
  * The header of the 7z archive files is encoded with LZMA.
@@ -782,6 +920,74 @@ DEFINE_TEST(test_read_format_7zip_deflate)
        assertEqualInt(ARCHIVE_OK, archive_read_free(a));
 }
 
+DEFINE_TEST(test_read_format_7zip_zstd)
+{
+       struct archive *a;
+
+       assert((a = archive_read_new()) != NULL);
+
+       /* Extracting with libzstd */
+       if (ARCHIVE_OK != archive_read_support_filter_zstd(a)) {
+               skipping(
+                   "7zip:zstd decoding is not supported on this platform");
+       } else {
+               test_extract_all_files_zstd("test_read_format_7zip_zstd.7z");
+       }
+
+       assertEqualInt(ARCHIVE_OK, archive_read_free(a));
+}
+
+DEFINE_TEST(test_read_format_7zip_zstd_solid)
+{
+       struct archive *a;
+
+       assert((a = archive_read_new()) != NULL);
+
+       /* Extracting with libzstd */
+       if (ARCHIVE_OK != archive_read_support_filter_zstd(a)) {
+               skipping(
+                   "7zip:zstd decoding is not supported on this platform");
+       } else {
+               test_extract_all_files_zstd("test_read_format_7zip_solid_zstd.7z");
+       }
+
+       assertEqualInt(ARCHIVE_OK, archive_read_free(a));
+}
+
+DEFINE_TEST(test_read_format_7zip_zstd_bcj)
+{
+       struct archive *a;
+
+       assert((a = archive_read_new()) != NULL);
+
+       /* Extracting with libzstd */
+       if (ARCHIVE_OK != archive_read_support_filter_zstd(a)) {
+               skipping(
+                   "7zip:zstd decoding is not supported on this platform");
+       } else {
+               test_extract_file_zstd_bcj_nobjc("test_read_format_7zip_zstd_bcj.7z");
+       }
+
+       assertEqualInt(ARCHIVE_OK, archive_read_free(a));
+}
+
+DEFINE_TEST(test_read_format_7zip_zstd_nobcj)
+{
+       struct archive *a;
+
+       assert((a = archive_read_new()) != NULL);
+
+       /* Extracting with libzstd */
+       if (ARCHIVE_OK != archive_read_support_filter_zstd(a)) {
+               skipping(
+                   "7zip:zstd decoding is not supported on this platform");
+       } else {
+               test_extract_file_zstd_bcj_nobjc("test_read_format_7zip_zstd_nobcj.7z");
+       }
+
+       assertEqualInt(ARCHIVE_OK, archive_read_free(a));
+}
+
 DEFINE_TEST(test_read_format_7zip_empty)
 {
        test_empty_archive();
diff --git a/libarchive/test/test_read_format_7zip_solid_zstd.7z.uu b/libarchive/test/test_read_format_7zip_solid_zstd.7z.uu
new file mode 100644 (file)
index 0000000..3bffb98
--- /dev/null
@@ -0,0 +1,9 @@
+begin 664 test_read_format_7zip_solid_zstd.7z
+M-WJ\KR<<``1&(FS)O@`````````B`````````$V+D*,HM2_]`$@!``!0*DT8
+M!````"$````HM2_]((+%```P80IB8V0*!X"P/JZA4J4;TS.X,9C'`#4``($S
+M!ZX/T5NC)*"0H'?;G=XO-Y<-`+"F*K87M`OOZ1+1#31#M/`2YN,FY:(1).I)
+M(B(+;9$W4?0*8=3V5N;BSZ)(0UGD/'LOSN"0&#DNX!A2*5\#&8IP$1G=7-]@
+MP\EE$]Z;/%6NU^\_,X:MD?57P#S>.+BINH?CHRX,::[Q5P*!X\DH````%P8V
+?`0F`B``'"P$``2,#`0$%70`0```,@-H*`9/CZP``````
+`
+end
diff --git a/libarchive/test/test_read_format_7zip_zstd.7z.uu b/libarchive/test/test_read_format_7zip_zstd.7z.uu
new file mode 100644 (file)
index 0000000..acd49c4
--- /dev/null
@@ -0,0 +1,12 @@
+begin 664 test_read_format_7zip_zstd.7z
+M-WJ\KR<<``2QPP_C,`$````````C``````````+H!E<HM2_]`$@!``!0*DT8
+M!````!$````HM2_](`U%```080H!``80`BBU+_T`2`$``%`J31@$````%```
+M`"BU+_T@&ET``"!A"F(*`@#`H`'8*+4O_0!(`0``4"I-&`0````6````*+4O
+M_2`G;0``,&$*8@IC"@,4``@8V2BU+_T`2`$``%`J31@$````&0```"BU+_T@
+M-(4``$!A"F(*8PID"@04``@8(QL``($S!ZYMP-,)%[KY0NUS!EQ9<T<AN:R"
+M7Z^`KZ24FV4LJ?"A)\MX&!"$=31!F6U\W/D!@*G'-=Z@#"&#>8R#*L]U;J=*
+M@K`Q6]^1F4MZF33TE2LJK8X@%5MY%P.#1*EM98=_,9"?K'$*_A0YVC)]1ML=
+MK-5-,X82B;/_L`I&FQ!5HP#H````%P:`J`$)@(@`!PL!``$C`P$!!5T`$```
++#(#Z"@%\$`,4````
+`
+end
diff --git a/libarchive/test/test_read_format_7zip_zstd_bcj.7z.uu b/libarchive/test/test_read_format_7zip_zstd_bcj.7z.uu
new file mode 100644 (file)
index 0000000..2a75db8
--- /dev/null
@@ -0,0 +1,56 @@
+begin 664 test_read_format_7zip_zstd_bcj.7z
+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
+@=P```!D$`````!0*`0``'';'*9?9`14&`0`@@/V!````
+`
+end
diff --git a/libarchive/test/test_read_format_7zip_zstd_nobcj.7z.uu b/libarchive/test/test_read_format_7zip_zstd_nobcj.7z.uu
new file mode 100644 (file)
index 0000000..c6fed0b
--- /dev/null
@@ -0,0 +1,56 @@
+begin 664 test_read_format_7zip_zstd_nobcj.7z
+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
+3```<=L<IE]D!%08!`""`_8$`````
+`
+end