View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.io.channels;
19  
20  import static java.nio.charset.StandardCharsets.US_ASCII;
21  import static org.junit.jupiter.api.Assertions.assertFalse;
22  import static org.junit.jupiter.api.Assertions.assertNotEquals;
23  import static org.junit.jupiter.api.Assertions.assertTrue;
24  
25  import java.io.ByteArrayInputStream;
26  import java.io.File;
27  import java.io.FileInputStream;
28  import java.io.IOException;
29  import java.io.InputStream;
30  import java.io.SequenceInputStream;
31  import java.nio.channels.Channels;
32  import java.nio.channels.FileChannel;
33  import java.nio.channels.SeekableByteChannel;
34  import java.nio.file.Files;
35  import java.nio.file.Path;
36  
37  import org.apache.commons.io.FileUtils;
38  import org.apache.commons.io.IOUtils;
39  import org.apache.commons.io.file.AbstractTempDirTest;
40  import org.apache.commons.lang3.ArrayUtils;
41  import org.apache.commons.lang3.StringUtils;
42  import org.junit.jupiter.params.ParameterizedTest;
43  import org.junit.jupiter.params.provider.ValueSource;
44  import org.junitpioneer.jupiter.cartesian.CartesianTest;
45  import org.junitpioneer.jupiter.cartesian.CartesianTest.Values;
46  
47  /**
48   * Tests {@link FileChannels}.
49   */
50  class FileChannelsTest extends AbstractTempDirTest {
51  
52      enum FileChannelType {
53          STOCK, PROXY, NON_BLOCKING, FIXED_READ_SIZE
54      }
55  
56      private static final int LARGE_FILE_SIZE = Integer.getInteger(FileChannelsTest.class.getSimpleName(), 100_000);
57  
58      private static final int SMALL_BUFFER_SIZE = 1024;
59      private static final String CONTENT = StringUtils.repeat("x", SMALL_BUFFER_SIZE);
60  
61      @SuppressWarnings("resource") // Caller closes
62      private static FileChannel getChannel(final FileInputStream inNotEmpty, final FileChannelType fileChannelType, final int readSize) throws IOException {
63          return wrap(inNotEmpty.getChannel(), fileChannelType, readSize);
64      }
65  
66      private static int half(final int bufferSize) {
67          return Math.max(1, bufferSize / 2);
68      }
69  
70      private static boolean isEmpty(final File empty) {
71          return empty.length() == 0;
72      }
73  
74      @SuppressWarnings("resource") // Caller closes
75      private static FileChannel open(final Path path, final FileChannelType fileChannelType, final int readSize) throws IOException {
76          return wrap(FileChannel.open(path), fileChannelType, readSize);
77      }
78  
79      private static FileChannel reset(final FileChannel fc) throws IOException {
80          return fc.position(0);
81      }
82  
83      private static byte reverse(final byte b) {
84          return (byte) (~b & 0xff);
85      }
86  
87      private static FileChannel wrap(final FileChannel fc, final FileChannelType fileChannelType, final int readSize) throws IOException {
88          switch (fileChannelType) {
89          case NON_BLOCKING:
90              return new NonBlockingFileChannelProxy(fc);
91          case STOCK:
92              return fc;
93          case PROXY:
94              return new FileChannelProxy(fc);
95          case FIXED_READ_SIZE:
96              return new FixedReadSizeFileChannelProxy(fc, readSize);
97          default:
98              throw new UnsupportedOperationException("Unexpected FileChannelType " + fileChannelType);
99          }
100     }
101 
102     private boolean contentEquals(final InputStream in1, final InputStream in2, final int bufferCapacity) throws IOException {
103         return FileChannels.contentEquals(Channels.newChannel(in1), Channels.newChannel(in2), bufferCapacity);
104     }
105 
106     private void testContentEquals(final String content1, final String content2, final int bufferSize, final FileChannelType fileChannelType)
107             throws IOException {
108         assertTrue(FileChannels.contentEquals(null, null, bufferSize));
109         // Prepare test files with same size but different content
110         // (first 3 bytes are different, followed by a large amount of equal content)
111         final File file1 = new File(tempDirFile, "test1.txt");
112         final File file2 = new File(tempDirFile, "test2.txt");
113         FileUtils.writeStringToFile(file1, content1, US_ASCII);
114         FileUtils.writeStringToFile(file2, content2, US_ASCII);
115         // File checksums are different
116         assertNotEquals(FileUtils.checksumCRC32(file1), FileUtils.checksumCRC32(file2));
117         // content not equals
118         try (FileInputStream in1 = new FileInputStream(file1);
119                 FileInputStream in2 = new FileInputStream(file2);
120                 FileChannel channel1 = getChannel(in1, fileChannelType, bufferSize);
121                 FileChannel channel2 = getChannel(in2, fileChannelType, half(bufferSize))) {
122             assertFalse(FileChannels.contentEquals(channel1, channel2, bufferSize));
123         }
124         // content not equals
125         try (FileInputStream in1 = new FileInputStream(file1);
126                 FileInputStream in2 = new FileInputStream(file2);
127                 FileChannel channel1 = getChannel(in1, fileChannelType, bufferSize);
128                 FileChannel channel2 = getChannel(in2, fileChannelType, half(bufferSize))) {
129             assertTrue(FileChannels.contentEquals(channel1, channel1, bufferSize));
130             assertTrue(FileChannels.contentEquals(channel2, channel2, bufferSize));
131         }
132     }
133 
134     @CartesianTest()
135     void testContentEqualsDifferentPostfix(
136             @Values(ints = { 1, 2, IOUtils.DEFAULT_BUFFER_SIZE / 10, IOUtils.DEFAULT_BUFFER_SIZE, IOUtils.DEFAULT_BUFFER_SIZE * 10 }) final int bufferSize,
137             @CartesianTest.Enum final FileChannelType fileChannelType) throws IOException {
138         testContentEquals(CONTENT + "ABC", CONTENT + "XYZ", bufferSize, fileChannelType);
139     }
140 
141     @CartesianTest()
142     void testContentEqualsDifferentPrefix(
143             @Values(ints = { 1, 2, IOUtils.DEFAULT_BUFFER_SIZE / 10, IOUtils.DEFAULT_BUFFER_SIZE, IOUtils.DEFAULT_BUFFER_SIZE * 10 }) final int bufferSize,
144             @CartesianTest.Enum final FileChannelType fileChannelType) throws IOException {
145         testContentEquals("ABC" + CONTENT, "XYZ" + CONTENT, bufferSize, fileChannelType);
146     }
147 
148     @CartesianTest()
149     void testContentEqualsEmpty(
150             @Values(ints = { 1, 2, IOUtils.DEFAULT_BUFFER_SIZE / 10, IOUtils.DEFAULT_BUFFER_SIZE, IOUtils.DEFAULT_BUFFER_SIZE * 10 }) final int bufferSize,
151             @CartesianTest.Enum final FileChannelType fileChannelType) throws IOException {
152         assertTrue(FileChannels.contentEquals(null, null, bufferSize));
153         // setup fixtures
154         final File empty = new File(tempDirFile, "empty.txt");
155         final File notEmpty = new File(tempDirFile, "not-empty.txt");
156         FileUtils.writeStringToFile(empty, StringUtils.EMPTY, US_ASCII);
157         FileUtils.writeStringToFile(notEmpty, "X", US_ASCII);
158         assertTrue(isEmpty(empty));
159         assertFalse(isEmpty(notEmpty));
160         // File checksums are different
161         assertNotEquals(FileUtils.checksumCRC32(empty), FileUtils.checksumCRC32(notEmpty));
162         try (FileInputStream inEmpty = new FileInputStream(empty);
163                 FileInputStream inNotEmpty = new FileInputStream(notEmpty);
164                 FileChannel channelEmpty = getChannel(inEmpty, fileChannelType, bufferSize);
165                 FileChannel channelNotEmpty = getChannel(inNotEmpty, fileChannelType, half(bufferSize))) {
166             assertFalse(FileChannels.contentEquals(channelEmpty, channelNotEmpty, bufferSize));
167             assertFalse(FileChannels.contentEquals(null, channelNotEmpty, bufferSize));
168             assertFalse(FileChannels.contentEquals(channelNotEmpty, null, bufferSize));
169             assertTrue(FileChannels.contentEquals(channelEmpty, channelEmpty, bufferSize));
170             assertTrue(FileChannels.contentEquals(null, channelEmpty, bufferSize));
171             assertTrue(FileChannels.contentEquals(channelEmpty, null, bufferSize));
172             assertTrue(FileChannels.contentEquals(channelNotEmpty, channelNotEmpty, bufferSize));
173         }
174     }
175 
176     @CartesianTest
177     void testContentEqualsFileChannel(
178             @Values(ints = { 1, 2, IOUtils.DEFAULT_BUFFER_SIZE / 10, IOUtils.DEFAULT_BUFFER_SIZE, IOUtils.DEFAULT_BUFFER_SIZE * 10 }) final int bufferSize)
179             throws IOException {
180         final FileChannelType fileChannelType = FileChannelType.STOCK;
181         final Path bigFile1 = Files.createTempFile(getClass().getSimpleName(), "-1.bin");
182         final Path bigFile2 = Files.createTempFile(getClass().getSimpleName(), "-2.bin");
183         final Path bigFile3 = Files.createTempFile(getClass().getSimpleName(), "-3.bin");
184         try {
185             // This length must match any restriction from the Surefire configuration.
186             final int newLength = LARGE_FILE_SIZE;
187             final byte[] bytes1 = new byte[newLength];
188             final byte[] bytes2 = new byte[newLength];
189             // Make sure bytes1 and bytes2 are different despite the shuffle
190             ArrayUtils.shuffle(bytes1);
191             bytes1[0] = 1;
192             ArrayUtils.shuffle(bytes2);
193             bytes2[0] = 2;
194             Files.write(bigFile1, bytes1);
195             Files.write(bigFile2, bytes2);
196             try (FileChannel fc1 = open(bigFile1, fileChannelType, bufferSize); FileChannel fc2 = open(bigFile2, fileChannelType, half(bufferSize))) {
197                 assertFalse(FileChannels.contentEquals(fc1, fc2, bufferSize));
198                 assertFalse(FileChannels.contentEquals(reset(fc2), reset(fc1), bufferSize));
199                 assertTrue(FileChannels.contentEquals(reset(fc1), reset(fc1), bufferSize));
200             }
201             // Make the LAST byte different.
202             byte[] bytes3 = bytes1.clone();
203             final int last = bytes3.length - 1;
204             bytes3[last] = reverse(bytes3[last]);
205             Files.write(bigFile3, bytes3);
206             try (FileChannel fc1 = open(bigFile1, fileChannelType, bufferSize); FileChannel fc3 = open(bigFile3, fileChannelType, half(bufferSize))) {
207                 assertFalse(FileChannels.contentEquals(fc1, fc3, bufferSize));
208                 assertFalse(FileChannels.contentEquals(reset(fc3), reset(fc1), bufferSize));
209                 // Test just the last byte
210                 fc1.position(last);
211                 fc3.position(last);
212                 assertFalse(FileChannels.contentEquals(fc1, fc3, bufferSize));
213             }
214             // Make the LAST byte equal.
215             bytes3 = bytes1.clone();
216             Files.write(bigFile3, bytes3);
217             try (FileChannel fc1 = open(bigFile1, fileChannelType, bufferSize); FileChannel fc3 = open(bigFile3, fileChannelType, half(bufferSize))) {
218                 // Test just the last byte
219                 fc1.position(last);
220                 fc3.position(last);
221                 assertTrue(FileChannels.contentEquals(fc1, fc3, bufferSize));
222             }
223             // Make a byte in the middle different
224             bytes3 = bytes1.clone();
225             final int middle = bytes3.length / 2;
226             bytes3[middle] = reverse(bytes3[middle]);
227             Files.write(bigFile3, bytes3);
228             try (FileChannel fc1 = open(bigFile1, fileChannelType, bufferSize); FileChannel fc3 = open(bigFile3, fileChannelType, half(bufferSize))) {
229                 assertFalse(FileChannels.contentEquals(fc1, fc3, bufferSize));
230                 assertFalse(FileChannels.contentEquals(reset(fc3), reset(fc1), bufferSize));
231             }
232         } finally {
233             // Delete ASAP
234             Files.deleteIfExists(bigFile1);
235             Files.deleteIfExists(bigFile2);
236             Files.deleteIfExists(bigFile3);
237         }
238     }
239 
240     @CartesianTest()
241     void testContentEqualsSeekableByteChannel(
242             @Values(ints = { 1, 2, IOUtils.DEFAULT_BUFFER_SIZE / 10, IOUtils.DEFAULT_BUFFER_SIZE, IOUtils.DEFAULT_BUFFER_SIZE * 10 }) final int bufferSize,
243             @CartesianTest.Enum final FileChannelType fileChannelType1, @CartesianTest.Enum final FileChannelType fileChannelType2) throws IOException {
244         final Path bigFile1 = Files.createTempFile(getClass().getSimpleName(), "-1.bin");
245         final Path bigFile2 = Files.createTempFile(getClass().getSimpleName(), "-2.bin");
246         final Path bigFile3 = Files.createTempFile(getClass().getSimpleName(), "-3.bin");
247         try {
248             // This length must match any restriction from the Surefire configuration.
249             final int newLength = LARGE_FILE_SIZE;
250             final byte[] bytes1 = new byte[newLength];
251             final byte[] bytes2 = new byte[newLength];
252             // Make sure bytes1 and bytes2 are different despite the shuffle
253             ArrayUtils.shuffle(bytes1);
254             bytes1[0] = 1;
255             ArrayUtils.shuffle(bytes2);
256             bytes2[0] = 2;
257             Files.write(bigFile1, bytes1);
258             Files.write(bigFile2, bytes2);
259             try (FileChannel fc1 = open(bigFile1, fileChannelType1, bufferSize); FileChannel fc2 = open(bigFile2, fileChannelType2, half(bufferSize))) {
260                 assertFalse(FileChannels.contentEquals((SeekableByteChannel) fc1, fc2, bufferSize));
261                 assertFalse(FileChannels.contentEquals((SeekableByteChannel) reset(fc2), reset(fc1), bufferSize));
262                 assertTrue(FileChannels.contentEquals((SeekableByteChannel) reset(fc1), reset(fc1), bufferSize));
263             }
264             // Make the LAST byte different.
265             byte[] bytes3 = bytes1.clone();
266             final int last = bytes3.length - 1;
267             bytes3[last] = reverse(bytes3[last]);
268             Files.write(bigFile3, bytes3);
269             try (FileChannel fc1 = open(bigFile1, fileChannelType1, bufferSize); FileChannel fc3 = open(bigFile3, fileChannelType2, half(bufferSize))) {
270                 assertFalse(FileChannels.contentEquals((SeekableByteChannel) fc1, fc3, bufferSize));
271                 assertFalse(FileChannels.contentEquals((SeekableByteChannel) reset(fc3), reset(fc1), bufferSize));
272                 // Test just the last byte
273                 fc1.position(last);
274                 fc3.position(last);
275                 assertFalse(FileChannels.contentEquals((SeekableByteChannel) fc1, fc3, bufferSize));
276             }
277             // Make the LAST byte equal.
278             bytes3 = bytes1.clone();
279             Files.write(bigFile3, bytes3);
280             try (FileChannel fc1 = open(bigFile1, fileChannelType1, bufferSize); FileChannel fc3 = open(bigFile3, fileChannelType2, half(bufferSize))) {
281                 // Test just the last byte
282                 fc1.position(last);
283                 fc3.position(last);
284                 assertTrue(FileChannels.contentEquals((SeekableByteChannel) fc1, fc3, bufferSize));
285             }
286             // Make a byte in the middle different
287             bytes3 = bytes1.clone();
288             final int middle = bytes3.length / 2;
289             bytes3[middle] = reverse(bytes3[middle]);
290             Files.write(bigFile3, bytes3);
291             try (FileChannel fc1 = open(bigFile1, fileChannelType1, bufferSize); FileChannel fc3 = open(bigFile3, fileChannelType2, half(bufferSize))) {
292                 assertFalse(FileChannels.contentEquals((SeekableByteChannel) fc1, fc3, bufferSize));
293                 assertFalse(FileChannels.contentEquals((SeekableByteChannel) reset(fc3), reset(fc1), bufferSize));
294             }
295         } finally {
296             // Delete ASAP
297             Files.deleteIfExists(bigFile1);
298             Files.deleteIfExists(bigFile2);
299             Files.deleteIfExists(bigFile3);
300         }
301     }
302 
303     @ParameterizedTest
304     @ValueSource(ints = { 1, 2, 4, 8, 16, 1024, 4096, 8192 })
305     void testContentEqualsSequenceInputStream(final int bufferCapacity) throws Exception {
306         // not equals
307         // @formatter:off
308         assertFalse(contentEquals(
309                 new ByteArrayInputStream("ab".getBytes()),
310                 new SequenceInputStream(
311                     new ByteArrayInputStream("a".getBytes()),
312                     new ByteArrayInputStream("b-".getBytes())), bufferCapacity));
313         assertFalse(contentEquals(
314                 new ByteArrayInputStream("ab".getBytes()),
315                 new SequenceInputStream(
316                     new ByteArrayInputStream("a-".getBytes()),
317                     new ByteArrayInputStream("b".getBytes())), bufferCapacity));
318         assertFalse(contentEquals(
319                 new ByteArrayInputStream("ab-".getBytes()),
320                 new SequenceInputStream(
321                     new ByteArrayInputStream("a".getBytes()),
322                     new ByteArrayInputStream("b".getBytes())), bufferCapacity));
323         assertFalse(contentEquals(
324                 new ByteArrayInputStream("".getBytes()),
325                 new SequenceInputStream(
326                     new ByteArrayInputStream("a".getBytes()),
327                     new ByteArrayInputStream("b".getBytes())), bufferCapacity));
328         assertFalse(contentEquals(
329                 new ByteArrayInputStream("".getBytes()),
330                 new SequenceInputStream(
331                     new ByteArrayInputStream("".getBytes()),
332                     new ByteArrayInputStream("b".getBytes())), bufferCapacity));
333         assertFalse(contentEquals(
334                 new ByteArrayInputStream("ab".getBytes()),
335                 new SequenceInputStream(
336                     new ByteArrayInputStream("".getBytes()),
337                     new ByteArrayInputStream("".getBytes())), bufferCapacity));
338         // equals
339         assertTrue(contentEquals(
340                 new ByteArrayInputStream("".getBytes()),
341                 new SequenceInputStream(
342                     new ByteArrayInputStream("".getBytes()),
343                     new ByteArrayInputStream("".getBytes())), bufferCapacity));
344         assertTrue(contentEquals(
345                 new ByteArrayInputStream("ab".getBytes()),
346                 new SequenceInputStream(
347                     new ByteArrayInputStream("a".getBytes()),
348                     new ByteArrayInputStream("b".getBytes())), bufferCapacity));
349         assertTrue(contentEquals(
350                 new ByteArrayInputStream("ab".getBytes()),
351                 new SequenceInputStream(
352                     new ByteArrayInputStream("ab".getBytes()),
353                     new ByteArrayInputStream("".getBytes())), bufferCapacity));
354         assertTrue(contentEquals(
355                 new ByteArrayInputStream("ab".getBytes()),
356                 new SequenceInputStream(
357                     new ByteArrayInputStream("".getBytes()),
358                     new ByteArrayInputStream("ab".getBytes())), bufferCapacity));
359         // @formatter:on
360     }
361 }