d8b0b32f9f
Reviewed-by: alanb
585 lines
17 KiB
Java
585 lines
17 KiB
Java
/*
|
|
* Copyright (c) 2002, 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
|
|
* @summary Comprehensive test for FileChannel.transfer{From,To}
|
|
* @bug 4708120 8274113
|
|
* @author Mark Reinhold
|
|
* @run main/timeout=300 Transfers
|
|
*/
|
|
|
|
import java.io.*;
|
|
import java.nio.*;
|
|
import java.nio.channels.*;
|
|
import java.util.*;
|
|
|
|
|
|
public class Transfers {
|
|
|
|
static PrintStream out = System.out;
|
|
|
|
private static class Failure
|
|
extends RuntimeException
|
|
{
|
|
|
|
Failure(Exception x) {
|
|
super(x);
|
|
}
|
|
|
|
Failure(String s) {
|
|
super(s);
|
|
}
|
|
|
|
}
|
|
|
|
|
|
// -- Writing and reading random bytes --
|
|
|
|
private static void writeBytes(byte[] ba, FileChannel fc,
|
|
int off, int len)
|
|
throws IOException
|
|
{
|
|
fc.position(off);
|
|
if (fc.write(ByteBuffer.wrap(ba, 0, len)) != len)
|
|
throw new IOException("Incomplete write");
|
|
fc.position(0);
|
|
}
|
|
|
|
private static void writeRandomBytes(long seed,
|
|
FileChannel fc, int off, int len)
|
|
throws IOException
|
|
{
|
|
Random r = new Random(seed);
|
|
byte[] ba = new byte[len];
|
|
r.nextBytes(ba);
|
|
writeBytes(ba, fc, off, len);
|
|
}
|
|
|
|
private static void writeZeroBytes(FileChannel fc, int off, int len)
|
|
throws IOException
|
|
{
|
|
byte[] ba = new byte[len];
|
|
writeBytes(ba, fc, off, len);
|
|
}
|
|
|
|
private static void checkBytes(FileChannel fc, int off, int len,
|
|
byte[] bytes)
|
|
throws IOException
|
|
{
|
|
ByteBuffer bb = ByteBuffer.allocate(len);
|
|
fc.position(off);
|
|
if (fc.read(bb) != len)
|
|
throw new IOException("Incomplete read");
|
|
bb.flip();
|
|
ByteBuffer bab = ByteBuffer.wrap(bytes, 0, len);
|
|
if (!bb.equals(bab))
|
|
throw new Failure("Wrong data written");
|
|
}
|
|
|
|
private static void checkRandomBytes(FileChannel fc, int off, int len,
|
|
long seed)
|
|
throws IOException
|
|
{
|
|
byte[] ba = new byte[len];
|
|
Random r = new Random(seed);
|
|
r.nextBytes(ba);
|
|
checkBytes(fc, off, len, ba);
|
|
}
|
|
|
|
private static void checkZeroBytes(FileChannel fc, int off, int len)
|
|
throws IOException
|
|
{
|
|
byte[] ba = new byte[len];
|
|
checkBytes(fc, off, len, ba);
|
|
}
|
|
|
|
// For debugging
|
|
//
|
|
private static void dump(FileChannel fc)
|
|
throws IOException
|
|
{
|
|
int sz = (int)fc.size();
|
|
ByteBuffer bb = ByteBuffer.allocate(sz);
|
|
fc.position(0);
|
|
if (fc.read(bb) != sz)
|
|
throw new IOException("Incomplete read");
|
|
bb.flip();
|
|
byte prev = -1;
|
|
int r = 0; // Repeats
|
|
int n = 0;
|
|
while (bb.hasRemaining() && (n < 32)) {
|
|
byte b = bb.get();
|
|
if (b == prev) {
|
|
r++;
|
|
continue;
|
|
}
|
|
if (r > 0) {
|
|
int c = prev & 0xff;
|
|
if (c < 0x10)
|
|
out.print('0');
|
|
out.print(Integer.toHexString(c));
|
|
if (r > 1) {
|
|
out.print("[");
|
|
out.print(r);
|
|
out.print("]");
|
|
}
|
|
n++;
|
|
}
|
|
prev = b;
|
|
r = 1;
|
|
}
|
|
if (r > 0) {
|
|
int c = prev & 0xff;
|
|
if (c < 0x10)
|
|
out.print('0');
|
|
out.print(Integer.toHexString(c));
|
|
if (r > 1) {
|
|
out.print("[");
|
|
out.print(r);
|
|
out.print("]");
|
|
}
|
|
n++;
|
|
}
|
|
if (bb.hasRemaining())
|
|
out.print("...");
|
|
out.println();
|
|
}
|
|
|
|
|
|
|
|
static File sourceFile;
|
|
static File targetFile;
|
|
|
|
// -- Self-verifying sources and targets --
|
|
|
|
static abstract class Source {
|
|
|
|
protected final int size;
|
|
protected final long seed;
|
|
private final String name;
|
|
|
|
Source(int size, long seed, String name) {
|
|
this.size = size;
|
|
this.seed = seed;
|
|
this.name = name;
|
|
}
|
|
|
|
String name() {
|
|
return name;
|
|
}
|
|
|
|
abstract ReadableByteChannel channel();
|
|
|
|
abstract void verify() throws IOException;
|
|
|
|
}
|
|
|
|
static class FileSource
|
|
extends Source
|
|
{
|
|
private final File fn;
|
|
private final RandomAccessFile raf;
|
|
private final FileChannel fc;
|
|
|
|
FileSource(int size, long seed) throws IOException {
|
|
super(size, seed, "FileChannel");
|
|
fn = sourceFile;
|
|
raf = new RandomAccessFile(fn, "rw");
|
|
fc = raf.getChannel();
|
|
fc.position(0);
|
|
writeRandomBytes(seed, fc, 0, size);
|
|
}
|
|
|
|
ReadableByteChannel channel() {
|
|
return fc;
|
|
}
|
|
|
|
void verify() throws IOException {
|
|
if (fc.position() != size)
|
|
throw new Failure("Wrong position: "
|
|
+ fc.position() + " (expected " + size +
|
|
")");
|
|
checkRandomBytes(fc, 0, size, seed);
|
|
fc.close();
|
|
raf.close(); // Bug in 1.4.0
|
|
}
|
|
|
|
}
|
|
|
|
static class UserSource
|
|
extends Source
|
|
{
|
|
private ReadableByteChannel ch;
|
|
private final ByteBuffer src;
|
|
|
|
UserSource(int size, long seed) {
|
|
super(size, seed, "UserChannel");
|
|
|
|
final byte[] bytes = new byte[size + 1];
|
|
Random r = new Random(seed);
|
|
r.nextBytes(bytes);
|
|
src = ByteBuffer.wrap(bytes);
|
|
|
|
ch = new ReadableByteChannel() {
|
|
public int read(ByteBuffer dst) {
|
|
if (!src.hasRemaining())
|
|
return -1;
|
|
int nr = Math.min(src.remaining(), dst.remaining());
|
|
ByteBuffer s = src.duplicate();
|
|
s.limit(s.position() + nr);
|
|
dst.put(s);
|
|
src.position(src.position() + nr);
|
|
return nr;
|
|
}
|
|
public boolean isOpen() {
|
|
return true;
|
|
}
|
|
public void close() { }
|
|
};
|
|
}
|
|
|
|
ReadableByteChannel channel() {
|
|
return ch;
|
|
}
|
|
|
|
void verify() {
|
|
if (src.remaining() != 1)
|
|
throw new Failure("Source has " + src.remaining()
|
|
+ " bytes remaining (expected 1)");
|
|
}
|
|
|
|
}
|
|
|
|
static abstract class Target {
|
|
|
|
protected final int size;
|
|
protected final long seed;
|
|
private final String name;
|
|
|
|
Target(int size, long seed, String name) {
|
|
this.size = size;
|
|
this.seed = seed;
|
|
this.name = name;
|
|
}
|
|
|
|
String name() {
|
|
return name;
|
|
}
|
|
|
|
abstract WritableByteChannel channel();
|
|
|
|
abstract void verify() throws IOException;
|
|
|
|
}
|
|
|
|
static class FileTarget
|
|
extends Target
|
|
{
|
|
private final File fn;
|
|
private final RandomAccessFile raf;
|
|
private final FileChannel fc;
|
|
|
|
FileTarget(int size, long seed) throws IOException {
|
|
super(size, seed, "FileChannel");
|
|
fn = targetFile;
|
|
raf = new RandomAccessFile(fn, "rw");
|
|
fc = raf.getChannel();
|
|
fc.position(0);
|
|
}
|
|
|
|
WritableByteChannel channel() {
|
|
return fc;
|
|
}
|
|
|
|
void verify() throws IOException {
|
|
if (fc.position() != size)
|
|
throw new Failure("Wrong position: "
|
|
+ fc.position() + " (expected " + size + ")");
|
|
checkRandomBytes(fc, 0, size, seed);
|
|
fc.close();
|
|
raf.close(); // Bug in 1.4.0
|
|
}
|
|
|
|
}
|
|
|
|
static class UserTarget
|
|
extends Target
|
|
{
|
|
private WritableByteChannel ch;
|
|
private final ByteBuffer dst;
|
|
|
|
UserTarget(int size, long seed) {
|
|
super(size, seed, "UserChannel");
|
|
dst = ByteBuffer.wrap(new byte[size + 1]);
|
|
|
|
ch = new WritableByteChannel() {
|
|
public int write(ByteBuffer src) {
|
|
int nr = Math.min(src.remaining(), dst.remaining());
|
|
ByteBuffer s = src.duplicate();
|
|
s.limit(s.position() + nr);
|
|
dst.put(s);
|
|
src.position(src.position() + nr);
|
|
return nr;
|
|
}
|
|
public boolean isOpen() {
|
|
return true;
|
|
}
|
|
public void close() { }
|
|
};
|
|
}
|
|
|
|
WritableByteChannel channel() {
|
|
return ch;
|
|
}
|
|
|
|
void verify() {
|
|
if (dst.remaining() != 1)
|
|
throw new Failure("Destination has " + dst.remaining()
|
|
+ " bytes remaining (expected 1)");
|
|
byte[] ba = new byte[size];
|
|
Random r = new Random(seed);
|
|
r.nextBytes(ba);
|
|
dst.flip();
|
|
ByteBuffer rbb = ByteBuffer.wrap(ba, 0, size);
|
|
if (!dst.equals(rbb))
|
|
throw new Failure("Wrong data written");
|
|
}
|
|
|
|
}
|
|
|
|
|
|
// Generates a sequence of ints of the form 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
|
|
// 15, 16, 17, 31, 32, 33, ..., 2^i-1, 2^i, 2^i+1, ..., max.
|
|
|
|
static class IntGenerator {
|
|
|
|
private int max;
|
|
private int cur = -1;
|
|
private int p2 = 8;
|
|
|
|
IntGenerator(int max) {
|
|
this.max = max;
|
|
}
|
|
|
|
boolean hasNext() {
|
|
return cur < max;
|
|
}
|
|
|
|
int next() {
|
|
if (cur >= max)
|
|
throw new IllegalStateException();
|
|
if (cur < 6) {
|
|
cur++;
|
|
return cur;
|
|
}
|
|
if (cur == p2 + 1) {
|
|
p2 <<= 1;
|
|
cur = p2 - 1;
|
|
return cur;
|
|
}
|
|
cur++;
|
|
return cur;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
// -- Tests --
|
|
|
|
private static final int MAX_XFER_SIZE = 1 << 14;
|
|
private static final int MAX_FILE_SIZE = MAX_XFER_SIZE << 1;
|
|
|
|
private static boolean debug = false;
|
|
private static boolean verbose = false;
|
|
|
|
static void show(String dir, String channelName, int off, int len) {
|
|
if (!verbose)
|
|
return;
|
|
out.println(dir + " " + channelName +
|
|
": offset " + off + ", length " + len);
|
|
}
|
|
|
|
static void testTo(long seed, FileChannel fc, int off, int len, Target tgt)
|
|
throws IOException
|
|
{
|
|
show("To", tgt.name(), off, len);
|
|
|
|
// Clear source, then randomize just the source region
|
|
writeZeroBytes(fc, 0, MAX_FILE_SIZE);
|
|
writeRandomBytes(seed, fc, off, len);
|
|
|
|
// Randomize position
|
|
int pos = (int)seed & 0xfff;
|
|
fc.position(pos);
|
|
|
|
long position = off;
|
|
long count = len;
|
|
while (count > 0) {
|
|
long n = (int)fc.transferTo(position, count, tgt.channel());
|
|
if (n < 0 || n > count)
|
|
throw new Failure("Incorrect transfer length n = : " + n
|
|
+ " (expected 0 <= n <= " + len + ")");
|
|
position += n;
|
|
count -= n;
|
|
}
|
|
|
|
// Check that source wasn't changed
|
|
if (fc.position() != pos)
|
|
throw new Failure("Position changed");
|
|
if (debug)
|
|
dump(fc);
|
|
checkRandomBytes(fc, off, len, seed);
|
|
writeZeroBytes(fc, off, len);
|
|
checkZeroBytes(fc, 0, MAX_FILE_SIZE);
|
|
|
|
// Check that target was updated correctly
|
|
tgt.verify();
|
|
}
|
|
|
|
static void testFrom(long seed, Source src, FileChannel fc, int off, int len)
|
|
throws IOException
|
|
{
|
|
show("From", src.name(), off, len);
|
|
|
|
// Clear target
|
|
writeZeroBytes(fc, 0, MAX_FILE_SIZE);
|
|
|
|
// Randomize position
|
|
int pos = (int)seed & 0xfff;
|
|
fc.position(pos);
|
|
|
|
long position = off;
|
|
long count = len;
|
|
while (count > 0) {
|
|
int n = (int)fc.transferFrom(src.channel(), position, count);
|
|
if (n < 0 || n > count)
|
|
throw new Failure("Incorrect transfer length n = : " + n
|
|
+ " (expected 0 <= n <= " + len + ")");
|
|
position += n;
|
|
count -= n;
|
|
}
|
|
|
|
// Check that source didn't change, and was read correctly
|
|
src.verify();
|
|
|
|
// Check that target was updated correctly
|
|
if (fc.position() != pos)
|
|
throw new Failure("Position changed");
|
|
if (debug)
|
|
dump(fc);
|
|
checkRandomBytes(fc, off, len, seed);
|
|
writeZeroBytes(fc, off, len);
|
|
checkZeroBytes(fc, 0, MAX_FILE_SIZE);
|
|
}
|
|
|
|
public static void main(String[] args)
|
|
throws Exception
|
|
{
|
|
if (args.length > 0) {
|
|
if (args[0].indexOf('v') >= 0)
|
|
verbose = true;
|
|
if (args[0].indexOf('d') >= 0)
|
|
debug = verbose = true;
|
|
}
|
|
|
|
File testDir = new File(System.getProperty("test.dir", "."));
|
|
|
|
sourceFile = File.createTempFile("xfer.src.", "", testDir);
|
|
sourceFile.deleteOnExit();
|
|
targetFile = File.createTempFile("xfer.tgt.", "", testDir);
|
|
targetFile.deleteOnExit();
|
|
|
|
File fn = File.createTempFile("xfer.fch.", "", testDir);
|
|
fn.deleteOnExit();
|
|
|
|
Random rnd = new Random();
|
|
int failures = 0;
|
|
|
|
try (FileChannel fc = new RandomAccessFile(fn, "rw").getChannel()) {
|
|
for (boolean to = false;; to = true) {
|
|
for (boolean user = false;; user = true) {
|
|
if (!verbose)
|
|
out.print((to ? "To " : "From ") +
|
|
(user ? "user channel" : "file channel")
|
|
+ ":");
|
|
IntGenerator offGen = new IntGenerator(MAX_XFER_SIZE + 2);
|
|
while (offGen.hasNext()) {
|
|
int off = offGen.next();
|
|
if (!verbose) out.print(" " + off);
|
|
IntGenerator lenGen = new IntGenerator(MAX_XFER_SIZE + 2);
|
|
while (lenGen.hasNext()) {
|
|
int len = lenGen.next();
|
|
long s = rnd.nextLong();
|
|
String chName = null;
|
|
try {
|
|
if (to) {
|
|
Target tgt;
|
|
if (user)
|
|
tgt = new UserTarget(len, s);
|
|
else
|
|
tgt = new FileTarget(len, s);
|
|
chName = tgt.name();
|
|
testTo(s, fc, off, len, tgt);
|
|
}
|
|
else {
|
|
Source src;
|
|
if (user)
|
|
src = new UserSource(len, s);
|
|
else
|
|
src = new FileSource(len, s);
|
|
chName = src.name();
|
|
testFrom(s, src, fc, off, len);
|
|
}
|
|
} catch (Failure x) {
|
|
out.println();
|
|
out.println("FAILURE: " + chName
|
|
+ ", offset " + off
|
|
+ ", length " + len);
|
|
x.printStackTrace(out);
|
|
failures++;
|
|
}
|
|
}
|
|
}
|
|
if (!verbose)
|
|
out.println();
|
|
if (user)
|
|
break;
|
|
}
|
|
if (to)
|
|
break;
|
|
}
|
|
}
|
|
|
|
sourceFile.delete();
|
|
targetFile.delete();
|
|
fn.delete();
|
|
|
|
if (failures > 0) {
|
|
out.println();
|
|
throw new RuntimeException("Some tests failed");
|
|
}
|
|
|
|
out.println("Test succeeded.");
|
|
}
|
|
}
|