This is an automated email from the ASF dual-hosted git repository.

garydgregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-compress.git


The following commit(s) were added to refs/heads/master by this push:
     new 1ef1b8672 Add more tests for HuffmanDecoder and performance benchmark 
tests (#795)
1ef1b8672 is described below

commit 1ef1b867290e25617bf0bf75e3afca7785f4adf6
Author: Fredrik Kjellberg <[email protected]>
AuthorDate: Sun Aug 9 13:47:37 2026 +0200

    Add more tests for HuffmanDecoder and performance benchmark tests (#795)
    
    * Add more tests for HuffmanDecoder and performance benchmark tests
    
    * Use assertThrows
---
 .../lha/Lh5CompressorInputStreamBenchmark.java     |  81 +++++++++++++++++
 .../zip/ExplodingInputStreamBenchmark.java         |  79 +++++++++++++++++
 .../archivers/zip/ExplodingInputStreamTest.java    |  43 +++++++++
 .../bzip2/BZip2CompressorInputStreamBenchmark.java |  76 ++++++++++++++++
 .../bzip2/BZip2CompressorInputStreamTest.java      |  18 ++--
 .../Deflate64CompressorInputStreamBenchmark.java   |  79 +++++++++++++++++
 .../Deflate64CompressorInputStreamTest.java        |  18 ++++
 .../compress/huffman/HuffmanDecoderTest.java       |  98 ++++++++++++++++++++-
 src/test/resources/lorem-ipsum-deflate64.zip       | Bin 0 -> 34821 bytes
 src/test/resources/lorem-ipsum-implode.zip         | Bin 0 -> 42925 bytes
 src/test/resources/lorem-ipsum.txt.bz2             | Bin 0 -> 23817 bytes
 11 files changed, 485 insertions(+), 7 deletions(-)

diff --git 
a/src/test/java/org/apache/commons/compress/archivers/lha/Lh5CompressorInputStreamBenchmark.java
 
b/src/test/java/org/apache/commons/compress/archivers/lha/Lh5CompressorInputStreamBenchmark.java
new file mode 100644
index 000000000..e4fa3a14a
--- /dev/null
+++ 
b/src/test/java/org/apache/commons/compress/archivers/lha/Lh5CompressorInputStreamBenchmark.java
@@ -0,0 +1,81 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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
+ *
+ *   https://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.
+ */
+
+package org.apache.commons.compress.archivers.lha;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.commons.codec.digest.DigestUtils;
+import org.apache.commons.compress.AbstractTest;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.io.input.UnsynchronizedByteArrayInputStream;
+import org.apache.commons.io.output.NullOutputStream;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.Level;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.OutputTimeUnit;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.Setup;
+import org.openjdk.jmh.annotations.State;
+
+/**
+ * Run this test: mvn clean test -Pbenchmark 
-Dbenchmark=Lh5CompressorInputStreamBenchmark
+ */
+@BenchmarkMode(Mode.AverageTime)
+@OutputTimeUnit(TimeUnit.MICROSECONDS)
+public class Lh5CompressorInputStreamBenchmark {
+
+    @State(Scope.Thread)
+    public static class DecompressionState {
+        private byte[] compressedData;
+
+        /**
+         * Load the archive into memory and extract the compressed data that 
will be used for the
+         * benchmark test. Verify that the compressed data decompresses to the 
expected output.
+         *
+         * @throws IOException
+         */
+        @Setup(Level.Trial)
+        public void setup() throws IOException {
+            final byte[] archiveData = 
AbstractTest.readAllBytes("test-macos-l0-lh5.lha");
+            compressedData = Arrays.copyOfRange(archiveData, 0x33, 0x33 + 
0x9c3f);
+
+            try (InputStream is = 
LhStaticHuffmanCompressorInputStream.lh5CompressorInputStream(
+                    
UnsynchronizedByteArrayInputStream.builder().setByteArray(compressedData).get()))
 {
+                final byte[] data = IOUtils.toByteArray(is);
+                assertEquals(144060, data.length);
+                
assertEquals("a00c4f3f36515c96b2faef71c054e7f3e86a4f0f4ed4824cb7c5293bb455d28a",
 DigestUtils.sha256Hex(data));
+            }
+        }
+    }
+
+    @Benchmark
+    public void testDecompress(final DecompressionState state) throws 
IOException {
+        try (InputStream is = 
LhStaticHuffmanCompressorInputStream.lh5CompressorInputStream(
+                
UnsynchronizedByteArrayInputStream.builder().setByteArray(state.compressedData).get()))
 {
+            IOUtils.copy(is, NullOutputStream.INSTANCE);
+        }
+    }
+}
diff --git 
a/src/test/java/org/apache/commons/compress/archivers/zip/ExplodingInputStreamBenchmark.java
 
b/src/test/java/org/apache/commons/compress/archivers/zip/ExplodingInputStreamBenchmark.java
new file mode 100644
index 000000000..8ddeb178f
--- /dev/null
+++ 
b/src/test/java/org/apache/commons/compress/archivers/zip/ExplodingInputStreamBenchmark.java
@@ -0,0 +1,79 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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
+ *
+ *   https://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.
+ */
+
+package org.apache.commons.compress.archivers.zip;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.commons.codec.digest.DigestUtils;
+import org.apache.commons.compress.AbstractTest;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.io.input.UnsynchronizedByteArrayInputStream;
+import org.apache.commons.io.output.NullOutputStream;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.Level;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.OutputTimeUnit;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.Setup;
+import org.openjdk.jmh.annotations.State;
+
+/**
+ * Run this test: mvn clean test -Pbenchmark 
-Dbenchmark=ExplodingInputStreamBenchmark
+ */
+@BenchmarkMode(Mode.AverageTime)
+@OutputTimeUnit(TimeUnit.MICROSECONDS)
+public class ExplodingInputStreamBenchmark {
+
+    @State(Scope.Thread)
+    public static class DecompressionState {
+        private byte[] compressedData;
+
+        /**
+         * Load the archive into memory and extract the compressed data that 
will be used for the
+         * benchmark test. Verify that the compressed data decompresses to the 
expected output.
+         *
+         * @throws IOException
+         */
+        @Setup(Level.Trial)
+        public void setup() throws IOException {
+            final byte[] archiveData = 
AbstractTest.readAllBytes("lorem-ipsum-implode.zip");
+            compressedData = Arrays.copyOfRange(archiveData, 0x27, 0x27 + 
0xa739);
+
+            try (InputStream is = new ExplodingInputStream(8192, 3, 
UnsynchronizedByteArrayInputStream.builder().setByteArray(compressedData).get()))
 {
+                final byte[] data = IOUtils.toByteArray(is);
+                assertEquals(144060, data.length);
+                
assertEquals("a00c4f3f36515c96b2faef71c054e7f3e86a4f0f4ed4824cb7c5293bb455d28a",
 DigestUtils.sha256Hex(data));
+            }
+        }
+    }
+
+    @Benchmark
+    public void testDecompress(final DecompressionState state) throws 
IOException {
+        try (InputStream is = new ExplodingInputStream(8192, 3, 
UnsynchronizedByteArrayInputStream.builder().setByteArray(state.compressedData).get()))
 {
+            IOUtils.copy(is, NullOutputStream.INSTANCE);
+        }
+    }
+}
diff --git 
a/src/test/java/org/apache/commons/compress/archivers/zip/ExplodingInputStreamTest.java
 
b/src/test/java/org/apache/commons/compress/archivers/zip/ExplodingInputStreamTest.java
new file mode 100644
index 000000000..ea6283a56
--- /dev/null
+++ 
b/src/test/java/org/apache/commons/compress/archivers/zip/ExplodingInputStreamTest.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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
+ *
+ *   https://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.
+ */
+
+package org.apache.commons.compress.archivers.zip;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.apache.commons.codec.digest.DigestUtils;
+import org.apache.commons.compress.AbstractTest;
+import org.apache.commons.io.IOUtils;
+import org.junit.jupiter.api.Test;
+
+public class ExplodingInputStreamTest extends AbstractTest {
+
+    @Test
+    void testDecompress() throws Exception {
+        try (ZipArchiveInputStream archive = new 
ZipArchiveInputStream(newInputStream("lorem-ipsum-implode.zip"))) {
+            final ZipArchiveEntry entry = archive.getNextEntry();
+            assertEquals("LOREM.TXT", entry.getName());
+            assertEquals(ZipMethod.IMPLODING, 
ZipMethod.getMethodByCode(entry.getMethod()));
+
+            final byte[] data = IOUtils.toByteArray(archive);
+            assertEquals(144060, data.length);
+            
assertEquals("a00c4f3f36515c96b2faef71c054e7f3e86a4f0f4ed4824cb7c5293bb455d28a",
 DigestUtils.sha256Hex(data));
+        }
+    }
+}
diff --git 
a/src/test/java/org/apache/commons/compress/compressors/bzip2/BZip2CompressorInputStreamBenchmark.java
 
b/src/test/java/org/apache/commons/compress/compressors/bzip2/BZip2CompressorInputStreamBenchmark.java
new file mode 100644
index 000000000..78945ab78
--- /dev/null
+++ 
b/src/test/java/org/apache/commons/compress/compressors/bzip2/BZip2CompressorInputStreamBenchmark.java
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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
+ *
+ *   https://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.
+ */
+
+package org.apache.commons.compress.compressors.bzip2;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.commons.codec.digest.DigestUtils;
+import org.apache.commons.compress.AbstractTest;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.io.input.UnsynchronizedByteArrayInputStream;
+import org.apache.commons.io.output.NullOutputStream;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.Level;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.OutputTimeUnit;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.Setup;
+import org.openjdk.jmh.annotations.State;
+
+/**
+ * Run this test: mvn clean test -Pbenchmark 
-Dbenchmark=BZip2CompressorInputStreamBenchmark
+ */
+@BenchmarkMode(Mode.AverageTime)
+@OutputTimeUnit(TimeUnit.MICROSECONDS)
+public class BZip2CompressorInputStreamBenchmark {
+
+    @State(Scope.Thread)
+    public static class DecompressionState {
+        private byte[] compressedData;
+
+        /**
+         * Load the compressed data into memory and verify that it 
decompresses to the expected output.
+         *
+         * @throws IOException
+         */
+        @Setup(Level.Trial)
+        public void setup() throws IOException {
+            compressedData = AbstractTest.readAllBytes("lorem-ipsum.txt.bz2");
+
+            try (InputStream is = new 
BZip2CompressorInputStream(UnsynchronizedByteArrayInputStream.builder().setByteArray(compressedData).get()))
 {
+                final byte[] data = IOUtils.toByteArray(is);
+                assertEquals(144060, data.length);
+                
assertEquals("a00c4f3f36515c96b2faef71c054e7f3e86a4f0f4ed4824cb7c5293bb455d28a",
 DigestUtils.sha256Hex(data));
+            }
+        }
+    }
+
+    @Benchmark
+    public void testDecompress(final DecompressionState state) throws 
IOException {
+        try (InputStream is = new 
BZip2CompressorInputStream(UnsynchronizedByteArrayInputStream.builder().setByteArray(state.compressedData).get()))
 {
+            IOUtils.copy(is, NullOutputStream.INSTANCE);
+        }
+    }
+}
diff --git 
a/src/test/java/org/apache/commons/compress/compressors/bzip2/BZip2CompressorInputStreamTest.java
 
b/src/test/java/org/apache/commons/compress/compressors/bzip2/BZip2CompressorInputStreamTest.java
index edbc21d36..71417f1c2 100644
--- 
a/src/test/java/org/apache/commons/compress/compressors/bzip2/BZip2CompressorInputStreamTest.java
+++ 
b/src/test/java/org/apache/commons/compress/compressors/bzip2/BZip2CompressorInputStreamTest.java
@@ -26,13 +26,13 @@
 
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
-import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.nio.ByteOrder;
 import java.nio.file.Files;
 import java.nio.file.Paths;
 
+import org.apache.commons.codec.digest.DigestUtils;
 import org.apache.commons.compress.AbstractTest;
 import org.apache.commons.compress.archivers.ArchiveException;
 import org.apache.commons.compress.archivers.ArchiveInputStream;
@@ -137,9 +137,8 @@ void testHbCreateDecodeTables() throws IOException {
 
     @Test
     void testMultiByteReadConsistentlyReturnsMinusOneAtEof() throws 
IOException {
-        final File input = getFile("bla.txt.bz2");
         final byte[] buf = new byte[2];
-        try (InputStream is = Files.newInputStream(input.toPath());
+        try (InputStream is = newInputStream("bla.txt.bz2");
                 BZip2CompressorInputStream in = new 
BZip2CompressorInputStream(is)) {
             IOUtils.toByteArray(in);
             assertEquals(-1, in.read(buf));
@@ -255,8 +254,7 @@ void 
testShouldThrowIOExceptionInsteadOfRuntimeExceptionCOMPRESS519() {
 
     @Test
     void testSingleByteReadConsistentlyReturnsMinusOneAtEof() throws 
IOException {
-        final File input = getFile("bla.txt.bz2");
-        try (InputStream is = Files.newInputStream(input.toPath());
+        try (InputStream is = newInputStream("bla.txt.bz2");
                 BZip2CompressorInputStream in = new 
BZip2CompressorInputStream(is)) {
             IOUtils.toByteArray(in);
             assertEquals(-1, in.read());
@@ -264,4 +262,14 @@ void testSingleByteReadConsistentlyReturnsMinusOneAtEof() 
throws IOException {
         }
     }
 
+    @Test
+    void testDecompress() throws Exception {
+        try (InputStream is = newInputStream("lorem-ipsum.txt.bz2");
+                BZip2CompressorInputStream in = new 
BZip2CompressorInputStream(is)) {
+            final byte[] data = IOUtils.toByteArray(in);
+            assertEquals(144060, data.length);
+            
assertEquals("a00c4f3f36515c96b2faef71c054e7f3e86a4f0f4ed4824cb7c5293bb455d28a",
 DigestUtils.sha256Hex(data));
+        }
+    }
+
 }
diff --git 
a/src/test/java/org/apache/commons/compress/compressors/deflate64/Deflate64CompressorInputStreamBenchmark.java
 
b/src/test/java/org/apache/commons/compress/compressors/deflate64/Deflate64CompressorInputStreamBenchmark.java
new file mode 100644
index 000000000..03a44cc0f
--- /dev/null
+++ 
b/src/test/java/org/apache/commons/compress/compressors/deflate64/Deflate64CompressorInputStreamBenchmark.java
@@ -0,0 +1,79 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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
+ *
+ *   https://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.
+ */
+
+package org.apache.commons.compress.compressors.deflate64;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.commons.codec.digest.DigestUtils;
+import org.apache.commons.compress.AbstractTest;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.io.input.UnsynchronizedByteArrayInputStream;
+import org.apache.commons.io.output.NullOutputStream;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.Level;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.OutputTimeUnit;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.Setup;
+import org.openjdk.jmh.annotations.State;
+
+/**
+ * Run this test: mvn clean test -Pbenchmark 
-Dbenchmark=Deflate64CompressorInputStreamBenchmark
+ */
+@BenchmarkMode(Mode.AverageTime)
+@OutputTimeUnit(TimeUnit.MICROSECONDS)
+public class Deflate64CompressorInputStreamBenchmark {
+
+    @State(Scope.Thread)
+    public static class DecompressionState {
+        private byte[] compressedData;
+
+        /**
+         * Load the archive into memory and extract the compressed data that 
will be used for the
+         * benchmark test. Verify that the compressed data decompresses to the 
expected output.
+         *
+         * @throws IOException
+         */
+        @Setup(Level.Trial)
+        public void setup() throws IOException {
+            final byte[] archiveData = 
AbstractTest.readAllBytes("lorem-ipsum-deflate64.zip");
+            compressedData = Arrays.copyOfRange(archiveData, 0x2d, 0x2d + 
0x8761);
+
+            try (InputStream is = new 
Deflate64CompressorInputStream(UnsynchronizedByteArrayInputStream.builder().setByteArray(compressedData).get()))
 {
+                final byte[] data = IOUtils.toByteArray(is);
+                assertEquals(144060, data.length);
+                
assertEquals("a00c4f3f36515c96b2faef71c054e7f3e86a4f0f4ed4824cb7c5293bb455d28a",
 DigestUtils.sha256Hex(data));
+            }
+        }
+    }
+
+    @Benchmark
+    public void testDecompress(final DecompressionState state) throws 
IOException {
+        try (InputStream is = new 
Deflate64CompressorInputStream(UnsynchronizedByteArrayInputStream.builder().setByteArray(state.compressedData).get()))
 {
+            IOUtils.copy(is, NullOutputStream.INSTANCE);
+        }
+    }
+}
diff --git 
a/src/test/java/org/apache/commons/compress/compressors/deflate64/Deflate64CompressorInputStreamTest.java
 
b/src/test/java/org/apache/commons/compress/compressors/deflate64/Deflate64CompressorInputStreamTest.java
index 809dcdd63..806689f71 100644
--- 
a/src/test/java/org/apache/commons/compress/compressors/deflate64/Deflate64CompressorInputStreamTest.java
+++ 
b/src/test/java/org/apache/commons/compress/compressors/deflate64/Deflate64CompressorInputStreamTest.java
@@ -30,9 +30,14 @@
 import java.io.InputStream;
 import java.io.InputStreamReader;
 
+import org.apache.commons.codec.digest.DigestUtils;
+import org.apache.commons.compress.AbstractTest;
 import org.apache.commons.compress.archivers.ArchiveException;
 import org.apache.commons.compress.archivers.ArchiveInputStream;
 import org.apache.commons.compress.archivers.ArchiveStreamFactory;
+import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
+import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream;
+import org.apache.commons.compress.archivers.zip.ZipMethod;
 import org.apache.commons.compress.compressors.CompressorException;
 import org.apache.commons.compress.compressors.CompressorStreamFactory;
 import org.apache.commons.io.IOUtils;
@@ -242,4 +247,17 @@ void testUncompressedBlockViaFactory() throws Exception {
             assertNull(br.readLine());
         }
     }
+
+    @Test
+    void testDecompress() throws Exception {
+        try (ZipArchiveInputStream archive = new 
ZipArchiveInputStream(AbstractTest.newInputStream("lorem-ipsum-deflate64.zip")))
 {
+            final ZipArchiveEntry entry = archive.getNextEntry();
+            assertEquals("lorem-ipsum.txt", entry.getName());
+            assertEquals(ZipMethod.ENHANCED_DEFLATED, 
ZipMethod.getMethodByCode(entry.getMethod()));
+
+            final byte[] data = IOUtils.toByteArray(archive);
+            assertEquals(144060, data.length);
+            
assertEquals("a00c4f3f36515c96b2faef71c054e7f3e86a4f0f4ed4824cb7c5293bb455d28a",
 DigestUtils.sha256Hex(data));
+        }
+    }
 }
diff --git 
a/src/test/java/org/apache/commons/compress/huffman/HuffmanDecoderTest.java 
b/src/test/java/org/apache/commons/compress/huffman/HuffmanDecoderTest.java
index 0be3bb578..40a5b561d 100644
--- a/src/test/java/org/apache/commons/compress/huffman/HuffmanDecoderTest.java
+++ b/src/test/java/org/apache/commons/compress/huffman/HuffmanDecoderTest.java
@@ -21,8 +21,10 @@
 
 import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
 
 import java.io.ByteArrayInputStream;
+import java.io.EOFException;
 import java.io.IOException;
 import java.nio.ByteOrder;
 import java.util.ArrayList;
@@ -31,6 +33,7 @@
 import java.util.List;
 import java.util.stream.Stream;
 
+import org.apache.commons.compress.compressors.CompressorException;
 import org.apache.commons.compress.utils.BitInputStream;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.params.ParameterizedTest;
@@ -56,7 +59,20 @@ void testCreateHuffmanDecodingTablesWithLargeAlphaSize() {
     static Stream<Arguments> testDecodeSymbols() {
         // @formatter:off
         return Stream.of(
-                // Simple case
+                // One level, two symbols
+                //   Symbol 0: 0
+                //   Symbol 1: 1
+                Arguments.of(
+                        new int[] {1, 1},
+                        new byte[] {(byte) 0b1_0_1_0_0_1_1_1},
+                        Arrays.asList(1, 0, 1, 0, 0, 1, 1, 1),
+                        ByteOrder.BIG_ENDIAN),
+                Arguments.of(
+                        new int[] {1, 1},
+                        new byte[] {(byte) 0b1_1_1_0_0_1_0_1},
+                        Arrays.asList(1, 0, 1, 0, 0, 1, 1, 1),
+                        ByteOrder.LITTLE_ENDIAN),
+                // Two levels, three symbols
                 //   Symbol 0: 10
                 //   Symbol 1: 11
                 //   Symbol 3: 0
@@ -70,7 +86,7 @@ static Stream<Arguments> testDecodeSymbols() {
                         new byte[] {(byte) 0b01_11_0_0_0_0},
                         Arrays.asList(3, 3, 3, 3, 1, 0),
                         ByteOrder.LITTLE_ENDIAN),
-                // Across byte boundary
+                // Two levels, three symbols, decode across byte boundary
                 // Symbol 0: 10
                 // Symbol 1: 11
                 // Symbol 2: 0
@@ -83,6 +99,23 @@ static Stream<Arguments> testDecodeSymbols() {
                         new int[] {2, 2, 1, 0},
                         new byte[] {(byte) 0b1_11_01_11_0, (byte) 
0b0_11_01_11_0},
                         Arrays.asList(2, 1, 0, 1, 0, 1, 0, 1, 2),
+                        ByteOrder.LITTLE_ENDIAN),
+                // Five levels, six symbols, decode across byte boundary
+                // Symbol 0: 1110
+                // Symbol 1: 10
+                // Symbol 2: 110
+                // Symbol 4: 11110
+                // Symbol 5: 11111
+                // Symbol 6: 0
+                Arguments.of(
+                        new int[] {4, 2, 3, 0, 5, 5, 1},
+                        new byte[] {(byte) 0b0_10_110_11, (byte) 0b10_11110_1, 
(byte) 0b1111_0000},
+                        Arrays.asList(6, 1, 2, 0, 4, 5),
+                        ByteOrder.BIG_ENDIAN),
+                Arguments.of(
+                        new int[] {4, 2, 3, 0, 5, 5, 1},
+                        new byte[] {(byte) 0b11_011_01_0, (byte) 0b1_01111_01, 
(byte) 0b0000_1111},
+                        Arrays.asList(6, 1, 2, 0, 4, 5),
                         ByteOrder.LITTLE_ENDIAN));
         // @formatter:on
     }
@@ -99,4 +132,65 @@ void testDecodeSymbols(final int[] codeLengths, final 
byte[] inputData, final Li
         }
         assertEquals(expectedSymbols, actualSymbols, "Decoded symbols do not 
match expected symbols");
     }
+
+    @Test
+    void testNoCodeLengths() throws Exception {
+        final IllegalArgumentException e = 
assertThrows(IllegalArgumentException.class, () -> new HuffmanDecoder(new 
int[0]),
+                "Expected IllegalArgumentException for empty code length 
list");
+        assertEquals("codeLengthSize must be > 0; was 0", e.getMessage());
+    }
+
+    @Test
+    void testSingleCodeLength() throws Exception {
+        final int[] length = { 1 };
+        // Value: 0
+        final HuffmanDecoder decoder = new HuffmanDecoder(length);
+        assertEquals(0, decodeSymbol(decoder, (byte) 0x00)); // 0xxx xxxx
+        final CompressorException e = assertThrows(CompressorException.class, 
() -> decodeSymbol(decoder, (byte) 0x80),
+                "Expected CompressorException for invalid bitstream");
+        assertEquals("Invalid Huffman code: 2", e.getMessage());
+    }
+
+    @Test
+    void testNoLeafNodes() throws Exception {
+        final HuffmanDecoder decoder = new HuffmanDecoder(new int[] { 0, 0, 0, 
0, 0 });
+        final CompressorException e = assertThrows(CompressorException.class, 
() -> decodeSymbol(decoder, (byte) 0, (byte) 0, (byte) 0, (byte) 0),
+                "Expected CompressorException when decoding symbols for tree 
with no leaf nodes");
+        assertEquals("Invalid Huffman code: 0", e.getMessage());
+    }
+
+    @Test
+    void testInvalidBitstream() throws Exception {
+        final int[] length = { 4, 2, 3, 0, 5, 0, 1 };
+        // Value: 0 1 2 3 4 5 6
+        final HuffmanDecoder decoder = new HuffmanDecoder(length);
+        assertEquals(6, decodeSymbol(decoder, (byte) 0x00)); // 0xxx xxxx
+        assertEquals(1, decodeSymbol(decoder, (byte) 0x80)); // 10xx xxxx
+        assertEquals(2, decodeSymbol(decoder, (byte) 0xc0)); // 110x xxxx
+        assertEquals(0, decodeSymbol(decoder, (byte) 0xe0)); // 1110 xxxx
+        assertEquals(4, decodeSymbol(decoder, (byte) 0xf0)); // 1111 0xxx
+        final CompressorException e = assertThrows(CompressorException.class, 
() -> decodeSymbol(decoder, (byte) 0xf8),
+                "Expected CompressorException for invalid bitstream");
+        assertEquals("Invalid Huffman code: 62", e.getMessage());
+    }
+
+    @Test
+    void testReadEof() throws Exception {
+        final int[] length = { 4, 2, 3, 0, 5, 5, 1 };
+        // Value: 0 1 2 3 4 5 6
+        final HuffmanDecoder decoder = new HuffmanDecoder(length);
+        try (BitInputStream in = new BitInputStream(new 
ByteArrayInputStream(new byte[] { (byte) 0b11111_110 }), ByteOrder.BIG_ENDIAN)) 
{
+            assertEquals(5, decoder.decodeSymbol(in)); // 1111 1xxx
+            assertEquals(2, decoder.decodeSymbol(in)); // 110x xxxx
+            final EOFException e = assertThrows(EOFException.class, () -> 
decoder.decodeSymbol(in),
+                    "Expected EOFException for end of stream");
+            assertEquals("Truncated Huffman bit stream", e.getMessage());
+        }
+    }
+
+    private int decodeSymbol(HuffmanDecoder decoder, final byte... data) 
throws IOException {
+        try (BitInputStream in = new BitInputStream(new 
ByteArrayInputStream(data), ByteOrder.BIG_ENDIAN)) {
+            return decoder.decodeSymbol(in);
+        }
+    }
 }
diff --git a/src/test/resources/lorem-ipsum-deflate64.zip 
b/src/test/resources/lorem-ipsum-deflate64.zip
new file mode 100644
index 000000000..8f5d5b4ee
Binary files /dev/null and b/src/test/resources/lorem-ipsum-deflate64.zip differ
diff --git a/src/test/resources/lorem-ipsum-implode.zip 
b/src/test/resources/lorem-ipsum-implode.zip
new file mode 100644
index 000000000..9c8d621ab
Binary files /dev/null and b/src/test/resources/lorem-ipsum-implode.zip differ
diff --git a/src/test/resources/lorem-ipsum.txt.bz2 
b/src/test/resources/lorem-ipsum.txt.bz2
new file mode 100644
index 000000000..cfa411104
Binary files /dev/null and b/src/test/resources/lorem-ipsum.txt.bz2 differ

Reply via email to