| /* | |
| * Copyright (c) 2021, 2022, Oracle and/or its affiliates. All rights reserved. | |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | |
| * | |
| * This code is free software; you can redistribute it and/or modify it | |
| * under the terms of the GNU General Public License version 2 only, as | |
| * published by the Free Software Foundation. | |
| * | |
| * This code is distributed in the hope that it will be useful, but WITHOUT | |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |
| * version 2 for more details (a copy is included in the LICENSE file that | |
| * accompanied this code). | |
| * | |
| * You should have received a copy of the GNU General Public License version | |
| * 2 along with this work; if not, write to the Free Software Foundation, | |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | |
| * | |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | |
| * or visit www.oracle.com if you need additional information or have any | |
| * questions. | |
| */ | |
| /* | |
| * @test | |
| * @bug 8268435 8274780 | |
| * @summary Verify ChannelInputStream methods readAllBytes and readNBytes | |
| * @requires (sun.arch.data.model == "64" & os.maxMemory >= 16g) | |
| * @library .. | |
| * @library /test/lib | |
| * @build jdk.test.lib.RandomFactory | |
| * @modules java.base/jdk.internal.util | |
| * @run testng/othervm/timeout=900 -Xmx12G ReadXBytes | |
| * @key randomness | |
| */ | |
| import java.io.File; | |
| import java.io.FileInputStream; | |
| import java.io.FilterInputStream; | |
| import java.io.InputStream; | |
| import java.io.IOException; | |
| import java.nio.ByteBuffer; | |
| import java.nio.channels.Channel; | |
| import java.nio.channels.Channels; | |
| import java.nio.channels.FileChannel; | |
| import java.nio.channels.ReadableByteChannel; | |
| import java.nio.channels.SeekableByteChannel; | |
| import java.nio.file.Files; | |
| import java.nio.file.Path; | |
| import java.util.List; | |
| import java.util.Random; | |
| import jdk.internal.util.ArraysSupport; | |
| import static java.nio.file.StandardOpenOption.*; | |
| import jdk.test.lib.RandomFactory; | |
| import org.testng.Assert; | |
| import static org.testng.Assert.assertEquals; | |
| import static org.testng.Assert.assertNotNull; | |
| import static org.testng.Assert.assertThrows; | |
| import static org.testng.Assert.assertFalse; | |
| import static org.testng.Assert.assertTrue; | |
| import org.testng.annotations.DataProvider; | |
| import org.testng.annotations.Test; | |
| public class ReadXBytes { | |
| private static final Random RAND = RandomFactory.getRandom(); | |
| // The largest source from which to read all bytes | |
| private static final int BIG_LENGTH = ArraysSupport.SOFT_MAX_ARRAY_LENGTH; | |
| // A length greater than a 32-bit integer can accommodate | |
| private static final long HUGE_LENGTH = Integer.MAX_VALUE + 27L; | |
| // Current directory | |
| private static final Path DIR = Path.of(System.getProperty("test.dir", ".")); | |
| // --- Framework --- | |
| // Create a temporary file path | |
| static Path createFilePath() { | |
| String name = String.format("ReadXBytes%d.tmp", System.nanoTime()); | |
| return DIR.resolve(name); | |
| } | |
| // Creates a temporary file of a specified length with undefined content | |
| static Path createFile(long length) throws IOException { | |
| Path path = createFilePath(); | |
| path.toFile().deleteOnExit(); | |
| try (FileChannel fc = FileChannel.open(path, CREATE_NEW, SPARSE, WRITE)) { | |
| if (length > 0) { | |
| fc.position(length - 1); | |
| fc.write(ByteBuffer.wrap(new byte[] {27})); | |
| } | |
| } | |
| return path; | |
| } | |
| // Creates a temporary file of a specified length with random content | |
| static Path createFileWithRandomContent(long length) throws IOException { | |
| Path file = createFile(length); | |
| try (FileChannel fc = FileChannel.open(file, WRITE)) { | |
| if (length < 65536) { | |
| // if the length is less than 64k, write the entire file | |
| byte[] buf = new byte[(int)length]; | |
| RAND.nextBytes(buf); | |
| ByteBuffer bb = ByteBuffer.wrap(buf); | |
| while (bb.hasRemaining()) { | |
| fc.write(bb); | |
| } | |
| } else { | |
| // write the first and the last 32k only | |
| byte[] buf = new byte[32768]; | |
| RAND.nextBytes(buf); | |
| ByteBuffer bb = ByteBuffer.wrap(buf); | |
| while (bb.hasRemaining()) { | |
| fc.write(bb); | |
| } | |
| bb.clear(); | |
| fc.position(length - buf.length); | |
| RAND.nextBytes(buf); | |
| while (bb.hasRemaining()) { | |
| fc.write(bb); | |
| } | |
| } | |
| } | |
| return file; | |
| } | |
| // Creates a file of a specified length | |
| @FunctionalInterface | |
| interface FileCreator { | |
| Path create(long length) throws IOException; | |
| } | |
| // Performs a test for checking edge cases | |
| @FunctionalInterface | |
| interface EdgeTest { | |
| void test(long length, InputStream source) throws IOException; | |
| } | |
| // Performs a test for evaluating correctness of content | |
| @FunctionalInterface | |
| interface DataTest { | |
| void test(long length, InputStream source, InputStream reference) | |
| throws IOException; | |
| } | |
| // Construct for testing zero length, EOF, and IAE | |
| public void edgeTest(long length, FileCreator c, EdgeTest f) | |
| throws IOException { | |
| Path file = c.create(length); | |
| try (FileChannel fc = FileChannel.open(file, READ); | |
| InputStream cis = Channels.newInputStream(fc)) { | |
| f.test(length, cis); | |
| } finally { | |
| Files.delete(file); | |
| } | |
| } | |
| // Construct for testing correctness of content | |
| public void dataTest(long length, FileCreator c, DataTest f) | |
| throws IOException { | |
| Path file = c.create(length); | |
| try { | |
| for (boolean seekable : List.of(false, true)) { | |
| try (FileInputStream fis = new FileInputStream(file.toFile())) { | |
| ReadableByteChannel ch; | |
| if (seekable) { | |
| ch = FileChannel.open(file, READ); | |
| } else { | |
| InputStream fis2 = new FileInputStream(file.toFile()); | |
| ch = Channels.newChannel(new FilterInputStream(fis2) {}); | |
| assertFalse(ch instanceof SeekableByteChannel); | |
| } | |
| try (InputStream cis = Channels.newInputStream(ch)) { | |
| f.test(length, cis, fis); | |
| } | |
| } | |
| } | |
| } finally { | |
| Files.delete(file); | |
| } | |
| } | |
| // --- readAllBytes tests --- | |
| // Verifies readAllBytes() behavior for an empty file | |
| @Test | |
| public void readAllBytesFromEmptyFile() throws IOException { | |
| edgeTest(0L, (length) -> createFile(length), | |
| (length, cis) -> { | |
| byte[] bytes = cis.readAllBytes(); | |
| assertNotNull(bytes); | |
| assertEquals(bytes.length, 0L); | |
| } | |
| ); | |
| } | |
| // Verifies readAllBytes() behavior at EOF | |
| @Test | |
| public void readAllBytesAtEOF() throws IOException { | |
| edgeTest(RAND.nextInt(Short.MAX_VALUE), (length) -> createFile(length), | |
| (length, cis) -> { | |
| cis.skipNBytes(length); | |
| byte[] bytes = cis.readAllBytes(); | |
| assertNotNull(bytes); | |
| assertEquals(bytes.length, 0); | |
| } | |
| ); | |
| } | |
| // Verifies readAllBytes() behavior for a maximal length source | |
| @Test | |
| public void readAllBytesFromMaxLengthFile() throws IOException { | |
| dataTest(BIG_LENGTH, (length) -> createFileWithRandomContent(length), | |
| (length, cis, fis) -> { | |
| byte[] cisBytes = cis.readAllBytes(); | |
| assertNotNull(cisBytes); | |
| assertEquals(cisBytes.length, (long)length); | |
| byte[] fisBytes = fis.readAllBytes(); | |
| assertEquals(cisBytes, fisBytes); | |
| } | |
| ); | |
| } | |
| // Verifies readAllBytes() throws OOME if the source is too large | |
| @Test | |
| public void readAllBytesFromBeyondMaxLengthFile() throws IOException { | |
| dataTest(HUGE_LENGTH, (length) -> createFile(length), | |
| (length, cis, fis) -> { | |
| assertThrows(OutOfMemoryError.class, | |
| () -> {cis.readAllBytes();}); | |
| } | |
| ); | |
| } | |
| // Provides an array of lengths | |
| @DataProvider | |
| public Object[][] lengthProvider() throws IOException { | |
| return new Object[][] { | |
| {1 + RAND.nextInt(1)}, | |
| {1 + RAND.nextInt(Byte.MAX_VALUE)}, | |
| {1 + RAND.nextInt(Short.MAX_VALUE)}, | |
| {1 + RAND.nextInt(1_000_000)}, | |
| {1 + RAND.nextInt(BIG_LENGTH)} | |
| }; | |
| } | |
| // Verifies readAllBytes() accuracy for random lengths and initial positions | |
| @Test(dataProvider = "lengthProvider") | |
| public void readAllBytes(int len) throws IOException { | |
| dataTest(len, (length) -> createFileWithRandomContent(length), | |
| (length, cis, fis) -> { | |
| long position = RAND.nextInt(Math.toIntExact(length)); | |
| cis.skipNBytes(position); | |
| byte[] cisBytes = cis.readAllBytes(); | |
| assertNotNull(cisBytes); | |
| assertEquals(cisBytes.length, length - position); | |
| fis.skipNBytes(position); | |
| byte[] fisBytes = fis.readAllBytes(); | |
| assertEquals(cisBytes, fisBytes); | |
| } | |
| ); | |
| } | |
| // --- readNBytes tests --- | |
| // Verifies readNBytes() behavior for a negative length | |
| @Test | |
| public void readNBytesWithNegativeLength() throws IOException { | |
| edgeTest(0L, (length) -> createFile(length), | |
| (length, cis) -> { | |
| assertThrows(IllegalArgumentException.class, | |
| () -> {cis.readNBytes(-1);}); | |
| } | |
| ); | |
| } | |
| // Verifies readNBytes() for an empty file | |
| @Test | |
| public void readNBytesFromEmptyFile() throws IOException { | |
| edgeTest(0L, (length) -> createFile(length), | |
| (length, cis) -> { | |
| byte[] bytes = cis.readNBytes(1); | |
| assertNotNull(bytes); | |
| assertEquals(bytes.length, 0); | |
| } | |
| ); | |
| } | |
| // Verifies readNBytes() behavior at EOF | |
| @Test | |
| public void readNBytesAtEOF() throws IOException { | |
| edgeTest(RAND.nextInt(Short.MAX_VALUE), (length) -> createFile(length), | |
| (length, cis) -> { | |
| cis.skipNBytes(length); | |
| byte[] bytes = cis.readNBytes(1); | |
| assertNotNull(bytes); | |
| assertEquals(bytes.length, 0); | |
| } | |
| ); | |
| } | |
| // Verifies readNBytes() behavior for a maximal length source | |
| @Test | |
| public void readNBytesFromMaxLengthFile() throws IOException { | |
| dataTest(BIG_LENGTH, (length) -> createFileWithRandomContent(length), | |
| (length, cis, fis) -> { | |
| byte[] cisBytes = cis.readNBytes(BIG_LENGTH); | |
| assertNotNull(cisBytes); | |
| assertEquals(cisBytes.length, (long)length); | |
| byte[] fisBytes = fis.readNBytes(BIG_LENGTH); | |
| assertEquals(cisBytes, fisBytes); | |
| } | |
| ); | |
| } | |
| // Verifies readNBytes() beyond the maximum length source | |
| @Test | |
| public void readNBytesFromBeyondMaxLengthFile() throws IOException { | |
| dataTest(HUGE_LENGTH, (length) -> createFileWithRandomContent(length), | |
| (length, cis, fis) -> { | |
| assertTrue(BIG_LENGTH < length, length + " >= " + HUGE_LENGTH); | |
| int n = Math.toIntExact(length - BIG_LENGTH); | |
| cis.skipNBytes(BIG_LENGTH); | |
| byte[] cisBytes = cis.readNBytes(n); | |
| assertNotNull(cisBytes); | |
| assertEquals(cisBytes.length, n); | |
| fis.skipNBytes(BIG_LENGTH); | |
| byte[] fisBytes = fis.readNBytes(n); | |
| assertEquals(cisBytes, fisBytes); | |
| } | |
| ); | |
| } | |
| // Verifies readNBytes() accuracy for random lengths and initial positions | |
| @Test(dataProvider = "lengthProvider") | |
| public void readNBytes(int len) throws IOException { | |
| dataTest(len, (length) -> createFileWithRandomContent(length), | |
| (length, cis, fis) -> { | |
| int ilen = Math.toIntExact(len); | |
| int position = RAND.nextInt(ilen); | |
| int n = RAND.nextInt(ilen - position); | |
| cis.skipNBytes(position); | |
| byte[] cisBytes = cis.readNBytes(n); | |
| assertNotNull(cisBytes); | |
| assertEquals(cisBytes.length, n); | |
| fis.skipNBytes(position); | |
| byte[] fisBytes = fis.readNBytes(n); | |
| assertEquals(cisBytes, fisBytes); | |
| } | |
| ); | |
| } | |
| } |