View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   https://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.commons.compress.compressors.snappy;
20  
21  import static org.junit.jupiter.api.Assertions.assertArrayEquals;
22  import static org.junit.jupiter.api.Assertions.assertTrue;
23  
24  import java.io.ByteArrayInputStream;
25  import java.io.ByteArrayOutputStream;
26  import java.io.File;
27  import java.io.IOException;
28  import java.io.OutputStream;
29  import java.nio.file.Files;
30  import java.nio.file.Path;
31  import java.util.Random;
32  
33  import org.apache.commons.compress.AbstractTest;
34  import org.apache.commons.compress.compressors.lz77support.Parameters;
35  import org.apache.commons.io.IOUtils;
36  import org.junit.jupiter.api.Test;
37  
38  public final class SnappyRoundtripTest extends AbstractTest {
39  
40      private static Parameters newParameters(final int windowSize, final int minBackReferenceLength, final int maxBackReferenceLength, final int maxOffset,
41              final int maxLiteralLength) {
42          return Parameters.builder(windowSize).withMinBackReferenceLength(minBackReferenceLength).withMaxBackReferenceLength(maxBackReferenceLength)
43                  .withMaxOffset(maxOffset).withMaxLiteralLength(maxLiteralLength).build();
44      }
45  
46      private void roundTripTest(final byte[] input, final Parameters params) throws IOException {
47          final ByteArrayOutputStream os = new ByteArrayOutputStream();
48          try (SnappyCompressorOutputStream sos = new SnappyCompressorOutputStream(os, input.length, params)) {
49              sos.write(input);
50          }
51          try (SnappyCompressorInputStream sis = new SnappyCompressorInputStream(new ByteArrayInputStream(os.toByteArray()), params.getWindowSize())) {
52              final byte[] actual = IOUtils.toByteArray(sis);
53              assertArrayEquals(input, actual);
54          }
55      }
56  
57      private void roundTripTest(final Path input, final Parameters params) throws IOException {
58          final File outputSz = newTempFile(input.getFileName() + ".raw.sz");
59          try (OutputStream os = Files.newOutputStream(outputSz.toPath());
60                  SnappyCompressorOutputStream sos = new SnappyCompressorOutputStream(os, Files.size(input), params)) {
61              sos.write(input);
62              sos.close();
63              assertTrue(sos.isClosed());
64          }
65          try (SnappyCompressorInputStream sis = new SnappyCompressorInputStream(Files.newInputStream(outputSz.toPath()), params.getWindowSize())) {
66              final byte[] expected = Files.readAllBytes(input);
67              final byte[] actual = IOUtils.toByteArray(sis);
68              assertArrayEquals(expected, actual);
69          }
70      }
71  
72      private void roundTripTest(final String testFile) throws IOException {
73          roundTripTest(getPath(testFile), SnappyCompressorOutputStream.createParameterBuilder(SnappyCompressorInputStream.DEFAULT_BLOCK_SIZE).build());
74      }
75  
76      // yields no compression at all
77      @Test
78      void testBiggerFileRoundtrip() throws IOException {
79          roundTripTest("COMPRESS-256.7z");
80      }
81  
82      // should yield decent compression
83      @Test
84      void testBlaTarRoundtrip() throws IOException {
85          // System.err.println("Configuration: default");
86          roundTripTest("bla.tar");
87      }
88  
89      @Test
90      void testBlaTarRoundtripTunedForCompressionRatio() throws IOException {
91          // System.err.println("Configuration: tuned for compression ratio");
92          roundTripTest(getPath("bla.tar"),
93                  SnappyCompressorOutputStream.createParameterBuilder(SnappyCompressorInputStream.DEFAULT_BLOCK_SIZE).tunedForCompressionRatio().build());
94      }
95  
96      @Test
97      void testBlaTarRoundtripTunedForSpeed() throws IOException {
98          // System.err.println("Configuration: tuned for speed");
99          roundTripTest(getPath("bla.tar"),
100                 SnappyCompressorOutputStream.createParameterBuilder(SnappyCompressorInputStream.DEFAULT_BLOCK_SIZE).tunedForSpeed().build());
101     }
102 
103     // yields no compression at all
104     @Test
105     void testGzippedLoremIpsumRoundtrip() throws IOException {
106         roundTripTest("lorem-ipsum.txt.gz");
107     }
108 
109     @Test
110     void testTryReallyBigOffset() throws IOException {
111         // "normal" Snappy files will never reach offsets beyond
112         // 16bits (i.e. those using four bytes to encode the length)
113         // as the block size is only 32k. This means we never execute
114         // the code for four-byte length copies in either stream class
115         // using real-world Snappy files.
116         // This is an artificial stream using a bigger block size that
117         // may not even be expandable by other Snappy implementations.
118         // Start with the four byte sequence 0000 after that add > 64k
119         // of random noise that doesn't contain any 0000 at all, then
120         // add 0000.
121         final ByteArrayOutputStream fs = new ByteArrayOutputStream((1 << 16) + 1024);
122         fs.write(0);
123         fs.write(0);
124         fs.write(0);
125         fs.write(0);
126         final int cnt = 1 << 16 + 5;
127         final Random r = new Random();
128         for (int i = 0; i < cnt; i++) {
129             fs.write(r.nextInt(255) + 1);
130         }
131         fs.write(0);
132         fs.write(0);
133         fs.write(0);
134         fs.write(0);
135 
136         roundTripTest(fs.toByteArray(), newParameters(1 << 17, 4, 64, 1 << 17 - 1, 1 << 17 - 1));
137     }
138 
139     @Test
140     void testTryReallyLongLiterals() throws IOException {
141         // "normal" Snappy files will never reach literal blocks with
142         // length beyond 16bits (i.e. those using three or four bytes
143         // to encode the length) as the block size is only 32k. This
144         // means we never execute the code for the three/four byte
145         // length literals in either stream class using real-world
146         // Snappy files.
147         // What we'd need would be a sequence of bytes with no four
148         // byte subsequence repeated that is longer than 64k, we try
149         // our best with random, but will probably only hit the three byte
150         // methods in a few lucky cases.
151         // The four byte methods would require even more luck and a
152         // buffer (and a file written to disk) that was 2^5 bigger
153         // than the buffer used here.
154         final Path path = newTempPath("reallyBigLiteralTest");
155         try (OutputStream outputStream = Files.newOutputStream(path)) {
156             final int cnt = 1 << 19;
157             final Random r = new Random();
158             for (int i = 0; i < cnt; i++) {
159                 outputStream.write(r.nextInt(256));
160             }
161         }
162         roundTripTest(path, newParameters(1 << 18, 4, 64, 1 << 16 - 1, 1 << 18 - 1));
163     }
164 }