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