forked from openrs2/openrs2
Similar to the equivalent class in the client. Signed-off-by: Graham <gpe@openrs2.dev>
parent
1e1711820d
commit
9f1b2dbc29
@ -1,53 +1,300 @@ |
|||||||
package dev.openrs2.cache |
package dev.openrs2.cache |
||||||
|
|
||||||
import io.netty.buffer.ByteBuf |
import io.netty.buffer.ByteBuf |
||||||
|
import io.netty.buffer.ByteBufAllocator |
||||||
import java.io.Closeable |
import java.io.Closeable |
||||||
import java.io.EOFException |
import java.io.EOFException |
||||||
import java.io.Flushable |
import java.io.Flushable |
||||||
import java.nio.channels.FileChannel |
import java.nio.channels.FileChannel |
||||||
|
import kotlin.math.max |
||||||
|
import kotlin.math.min |
||||||
|
|
||||||
// TODO(gpe): actually implement buffering |
|
||||||
class BufferedFileChannel( |
class BufferedFileChannel( |
||||||
private val channel: FileChannel |
private val channel: FileChannel, |
||||||
|
readBufferSize: Int, |
||||||
|
writeBufferSize: Int, |
||||||
|
alloc: ByteBufAllocator = ByteBufAllocator.DEFAULT |
||||||
) : Flushable, Closeable { |
) : Flushable, Closeable { |
||||||
|
private var size = channel.size() |
||||||
|
|
||||||
|
private val readBuffer = alloc.buffer(readBufferSize, readBufferSize) |
||||||
|
private var readPos = -1L |
||||||
|
|
||||||
|
private val writeBuffer = alloc.buffer(writeBufferSize, writeBufferSize) |
||||||
|
private var writePos = -1L |
||||||
|
|
||||||
fun read(pos: Long, dest: ByteBuf, len: Int) { |
fun read(pos: Long, dest: ByteBuf, len: Int) { |
||||||
|
require(pos >= 0) |
||||||
require(len <= dest.writableBytes()) |
require(len <= dest.writableBytes()) |
||||||
|
|
||||||
|
val originalDestIndex = dest.writerIndex() |
||||||
|
|
||||||
var off = pos |
var off = pos |
||||||
var remaining = len |
var remaining = len |
||||||
|
|
||||||
|
/* |
||||||
|
* Service the whole read from the write buffer, if we can. This code |
||||||
|
* isn't necessary, but it is more optimal than following the whole |
||||||
|
* sequence of reads below. |
||||||
|
*/ |
||||||
|
val writeLen = writeBuffer.readableBytes() |
||||||
|
if (writePos != -1L && off >= writePos && off + remaining <= writePos + writeLen) { |
||||||
|
val copyOff = (off - writePos).toInt() |
||||||
|
dest.writeBytes(writeBuffer, copyOff, remaining) |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
// Service the first part of the read from the read buffer. |
||||||
|
if (readPos != -1L && off >= readPos && off < readPos + readBuffer.readableBytes()) { |
||||||
|
val copyOff = (off - readPos).toInt() |
||||||
|
val copyLen = min(readBuffer.readableBytes() - copyOff, remaining) |
||||||
|
|
||||||
|
dest.writeBytes(readBuffer, copyOff, copyLen) |
||||||
|
|
||||||
|
off += copyLen |
||||||
|
remaining -= copyLen |
||||||
|
} |
||||||
|
|
||||||
|
if (remaining > readBuffer.capacity()) { |
||||||
|
/* |
||||||
|
* If the remaining part of the read is larger than the read |
||||||
|
* buffer, read directly from the file into the destination buffer. |
||||||
|
*/ |
||||||
while (remaining > 0) { |
while (remaining > 0) { |
||||||
val n = dest.writeBytes(channel, off, remaining) |
val n = dest.writeBytes(channel, off, remaining) |
||||||
if (n == -1) { |
if (n == -1) { |
||||||
throw EOFException() |
break |
||||||
} |
} |
||||||
off += n |
off += n |
||||||
remaining -= n |
remaining -= n |
||||||
} |
} |
||||||
|
} else if (remaining > 0) { |
||||||
|
/* |
||||||
|
* Otherwise clear and repopulate the entire read buffer from the |
||||||
|
* current position, then copy into the destination buffer. |
||||||
|
*/ |
||||||
|
fill(off) |
||||||
|
|
||||||
|
val copyLen = min(readBuffer.readableBytes(), remaining) |
||||||
|
|
||||||
|
dest.writeBytes(readBuffer, 0, copyLen) |
||||||
|
|
||||||
|
off += copyLen |
||||||
|
remaining -= copyLen |
||||||
|
} |
||||||
|
|
||||||
|
if (writePos != -1L) { |
||||||
|
/* |
||||||
|
* If an unflushed write extended the length of the file, fill in |
||||||
|
* the gap between the current position and the write position with |
||||||
|
* zeroes to reflect what the filesystem would do. |
||||||
|
*/ |
||||||
|
if (off < writePos && remaining > 0) { |
||||||
|
val zeroLen = min((writePos - off).toInt(), remaining) |
||||||
|
|
||||||
|
dest.writeZero(zeroLen) |
||||||
|
|
||||||
|
off += zeroLen |
||||||
|
remaining -= zeroLen |
||||||
|
} |
||||||
|
|
||||||
|
/* |
||||||
|
* If a subset of the write buffer overlaps with a subset of the |
||||||
|
* destination buffer, overwrite that subset of the destination |
||||||
|
* buffer with the write buffer as the write buffer must take |
||||||
|
* precedence over the read buffer. |
||||||
|
*/ |
||||||
|
val start = if (writePos >= pos && writePos < pos + len) { |
||||||
|
writePos |
||||||
|
} else if (pos >= writePos && pos < writePos + writeLen) { |
||||||
|
pos |
||||||
|
} else { |
||||||
|
-1L |
||||||
|
} |
||||||
|
|
||||||
|
val end = if (writePos + writeLen > pos && writePos + writeLen <= pos + len) { |
||||||
|
writePos + writeLen |
||||||
|
} else if (pos + len > writePos && pos + len <= writePos + writeLen) { |
||||||
|
pos + len |
||||||
|
} else { |
||||||
|
-1L |
||||||
|
} |
||||||
|
|
||||||
|
if (start != -1L && end != -1L && start < end) { |
||||||
|
val destIndex = originalDestIndex + (start - pos).toInt() |
||||||
|
|
||||||
|
val copyOff = (start - writePos).toInt() |
||||||
|
val copyLen = (end - start).toInt() |
||||||
|
|
||||||
|
dest.setBytes(destIndex, writeBuffer, copyOff, copyLen) |
||||||
|
|
||||||
|
/* |
||||||
|
* If we filled in any remaining bytes in the destination |
||||||
|
* buffer from the write buffer then adjust the indexes to take |
||||||
|
* that into account. |
||||||
|
*/ |
||||||
|
if (end > off) { |
||||||
|
val n = (end - off).toInt() |
||||||
|
|
||||||
|
dest.writerIndex(dest.writerIndex() + n) |
||||||
|
|
||||||
|
off += n |
||||||
|
remaining -= n |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (remaining > 0) { |
||||||
|
throw EOFException() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private fun fill(pos: Long) { |
||||||
|
require(pos >= 0) |
||||||
|
|
||||||
|
readBuffer.clear() |
||||||
|
readPos = pos |
||||||
|
|
||||||
|
var off = pos |
||||||
|
while (readBuffer.isWritable) { |
||||||
|
val n = readBuffer.writeBytes(channel, off, readBuffer.writableBytes()) |
||||||
|
if (n == -1) { |
||||||
|
break |
||||||
|
} |
||||||
|
off += n |
||||||
|
} |
||||||
} |
} |
||||||
|
|
||||||
fun write(pos: Long, src: ByteBuf, len: Int) { |
fun write(pos: Long, src: ByteBuf, len: Int) { |
||||||
|
require(pos >= 0) |
||||||
|
require(len <= src.readableBytes()) |
||||||
|
|
||||||
|
size = max(size, pos + len.toLong()) |
||||||
|
|
||||||
|
var off = pos |
||||||
|
var remaining = len |
||||||
|
|
||||||
|
/* |
||||||
|
* If the start of the write doesn't overlap with the write buffer, |
||||||
|
* flush the existing write buffer. |
||||||
|
*/ |
||||||
|
if (writePos != -1L && (off < writePos || off > writePos + writeBuffer.readableBytes())) { |
||||||
|
flush() |
||||||
|
} |
||||||
|
|
||||||
|
/* |
||||||
|
* If the start of the write does overlap with the write buffer |
||||||
|
* (implicit due to the if condition and flush() call above) and the |
||||||
|
* end of the write runs beyond the end of the write buffer, overwrite |
||||||
|
* the relevant part of the write buffer with the start of the source |
||||||
|
* buffer and then flush the whole write buffer. |
||||||
|
*/ |
||||||
|
if (writePos != -1L && off + remaining > writePos + writeBuffer.capacity()) { |
||||||
|
val copyOff = (off - writePos).toInt() |
||||||
|
val copyLen = writeBuffer.capacity() - copyOff |
||||||
|
|
||||||
|
src.readBytes(writeBuffer, copyOff, copyLen) |
||||||
|
|
||||||
|
off += copyLen |
||||||
|
remaining -= copyLen |
||||||
|
|
||||||
|
writeBuffer.writerIndex(writeBuffer.capacity()) |
||||||
|
|
||||||
|
flush() |
||||||
|
} |
||||||
|
|
||||||
|
if (remaining > writeBuffer.capacity()) { |
||||||
|
/* |
||||||
|
* If the remaining part of the write is longer than the write |
||||||
|
* buffer, write directly to the underlying file. |
||||||
|
*/ |
||||||
|
val originalSrcIndex = src.readerIndex() |
||||||
|
writeFully(off, src, remaining) |
||||||
|
|
||||||
|
/* |
||||||
|
* If the write overlaps with the read buffer, update the relevant |
||||||
|
* portion of the read buffer. (As we bypassed the write buffer, we |
||||||
|
* can't rely on the write buffer taking precedence over the read |
||||||
|
* buffer.) |
||||||
|
*/ |
||||||
|
val readLen = readBuffer.readableBytes() |
||||||
|
|
||||||
|
val start = if (off >= readPos && off < readPos + readLen) { |
||||||
|
off |
||||||
|
} else if (readPos >= off && readPos < off + remaining) { |
||||||
|
readPos |
||||||
|
} else { |
||||||
|
-1L |
||||||
|
} |
||||||
|
|
||||||
|
val end = if (off + remaining > readPos && off + remaining <= readPos + readLen) { |
||||||
|
off + remaining |
||||||
|
} else if (readPos + readLen > off && readPos + readLen <= off + remaining) { |
||||||
|
readPos + readLen |
||||||
|
} else { |
||||||
|
-1L |
||||||
|
} |
||||||
|
|
||||||
|
if (start != -1L && end != -1L && start < end) { |
||||||
|
val srcIndex = originalSrcIndex + (start - off).toInt() |
||||||
|
|
||||||
|
val copyOff = (start - readPos).toInt() |
||||||
|
val copyLen = (end - start).toInt() |
||||||
|
|
||||||
|
src.getBytes(srcIndex, readBuffer, copyOff, copyLen) |
||||||
|
} |
||||||
|
} else if (remaining > 0) { |
||||||
|
// Otherwise write to the write buffer. |
||||||
|
if (writePos == -1L) { |
||||||
|
writePos = off |
||||||
|
} |
||||||
|
|
||||||
|
val copyOff = (off - writePos).toInt() |
||||||
|
|
||||||
|
src.readBytes(writeBuffer, copyOff, remaining) |
||||||
|
|
||||||
|
off += remaining |
||||||
|
|
||||||
|
// Increase write buffer length if necessary. |
||||||
|
val newWriteLen = (off - writePos).toInt() |
||||||
|
if (newWriteLen > writeBuffer.readableBytes()) { |
||||||
|
writeBuffer.writerIndex(newWriteLen) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private fun writeFully(pos: Long, src: ByteBuf, len: Int) { |
||||||
|
require(pos >= 0) |
||||||
require(len <= src.readableBytes()) |
require(len <= src.readableBytes()) |
||||||
|
|
||||||
var off = pos |
var off = pos |
||||||
var remaining = len |
var remaining = len |
||||||
|
|
||||||
while (remaining > 0) { |
while (remaining > 0) { |
||||||
val n = src.readBytes(channel, off, remaining) |
val n = src.readBytes(channel, off, len) |
||||||
off += n |
off += n |
||||||
remaining -= n |
remaining -= n |
||||||
} |
} |
||||||
} |
} |
||||||
|
|
||||||
fun size(): Long { |
fun size(): Long { |
||||||
return channel.size() |
return size |
||||||
} |
} |
||||||
|
|
||||||
override fun flush() { |
override fun flush() { |
||||||
// empty |
if (writePos != -1L) { |
||||||
|
writeFully(writePos, writeBuffer, writeBuffer.readableBytes()) |
||||||
|
writeBuffer.clear() |
||||||
|
writePos = -1L |
||||||
|
} |
||||||
} |
} |
||||||
|
|
||||||
override fun close() { |
override fun close() { |
||||||
|
flush() |
||||||
|
|
||||||
channel.close() |
channel.close() |
||||||
|
|
||||||
|
readBuffer.release() |
||||||
|
writeBuffer.release() |
||||||
} |
} |
||||||
} |
} |
||||||
|
@ -0,0 +1,559 @@ |
|||||||
|
package dev.openrs2.cache |
||||||
|
|
||||||
|
import com.google.common.jimfs.Configuration |
||||||
|
import com.google.common.jimfs.Jimfs |
||||||
|
import dev.openrs2.buffer.use |
||||||
|
import io.netty.buffer.Unpooled |
||||||
|
import org.junit.jupiter.api.Test |
||||||
|
import org.junit.jupiter.api.assertThrows |
||||||
|
import java.io.EOFException |
||||||
|
import java.nio.channels.FileChannel |
||||||
|
import java.nio.file.Files |
||||||
|
import java.nio.file.StandardOpenOption.CREATE |
||||||
|
import java.nio.file.StandardOpenOption.READ |
||||||
|
import java.nio.file.StandardOpenOption.WRITE |
||||||
|
import kotlin.test.assertEquals |
||||||
|
|
||||||
|
object BufferedFileChannelTest { |
||||||
|
@Test |
||||||
|
fun testEmpty() { |
||||||
|
Jimfs.newFileSystem(Configuration.unix()).use { fs -> |
||||||
|
val path = fs.getPath("/test.dat") |
||||||
|
|
||||||
|
BufferedFileChannel(FileChannel.open(path, CREATE, READ, WRITE), 8, 8).use { channel -> |
||||||
|
assertEquals(0, channel.size()) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun testBufferedWrite() { |
||||||
|
Jimfs.newFileSystem(Configuration.unix()).use { fs -> |
||||||
|
val path = fs.getPath("/test.dat") |
||||||
|
|
||||||
|
BufferedFileChannel(FileChannel.open(path, CREATE, READ, WRITE), 8, 8).use { channel -> |
||||||
|
Unpooled.wrappedBuffer("OpenRS2".toByteArray()).use { buf -> |
||||||
|
channel.write(0, buf, buf.readableBytes()) |
||||||
|
} |
||||||
|
|
||||||
|
assertEquals(7, channel.size()) |
||||||
|
} |
||||||
|
|
||||||
|
Unpooled.wrappedBuffer(Files.readAllBytes(path)).use { actual -> |
||||||
|
Unpooled.wrappedBuffer("OpenRS2".toByteArray()).use { expected -> |
||||||
|
assertEquals(expected, actual) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun testBufferedWriteOverlapStart() { |
||||||
|
Jimfs.newFileSystem(Configuration.unix()).use { fs -> |
||||||
|
val path = fs.getPath("/test.dat") |
||||||
|
|
||||||
|
BufferedFileChannel(FileChannel.open(path, CREATE, READ, WRITE), 8, 8).use { channel -> |
||||||
|
Unpooled.wrappedBuffer("OpenRS2".toByteArray()).use { buf -> |
||||||
|
channel.write(1, buf.slice(), buf.readableBytes()) |
||||||
|
channel.write(0, buf.slice(), buf.readableBytes()) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
Unpooled.wrappedBuffer(Files.readAllBytes(path)).use { actual -> |
||||||
|
Unpooled.wrappedBuffer("OpenRS22".toByteArray()).use { expected -> |
||||||
|
assertEquals(expected, actual) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun testBufferedWriteOverlapEnd() { |
||||||
|
Jimfs.newFileSystem(Configuration.unix()).use { fs -> |
||||||
|
val path = fs.getPath("/test.dat") |
||||||
|
|
||||||
|
BufferedFileChannel(FileChannel.open(path, CREATE, READ, WRITE), 8, 8).use { channel -> |
||||||
|
Unpooled.wrappedBuffer("OpenRS2".toByteArray()).use { buf -> |
||||||
|
channel.write(0, buf.slice(), buf.readableBytes()) |
||||||
|
channel.write(1, buf.slice(), buf.readableBytes()) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
Unpooled.wrappedBuffer(Files.readAllBytes(path)).use { actual -> |
||||||
|
Unpooled.wrappedBuffer("OOpenRS2".toByteArray()).use { expected -> |
||||||
|
assertEquals(expected, actual) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun testBufferedWriteAdjacent() { |
||||||
|
Jimfs.newFileSystem(Configuration.unix()).use { fs -> |
||||||
|
val path = fs.getPath("/test.dat") |
||||||
|
|
||||||
|
BufferedFileChannel(FileChannel.open(path, CREATE, READ, WRITE), 8, 8).use { channel -> |
||||||
|
Unpooled.wrappedBuffer("OpenRS2".toByteArray()).use { buf -> |
||||||
|
channel.write(0, buf.slice(), buf.readableBytes()) |
||||||
|
channel.write(7, buf.slice(), buf.readableBytes()) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
Unpooled.wrappedBuffer(Files.readAllBytes(path)).use { actual -> |
||||||
|
Unpooled.wrappedBuffer("OpenRS2OpenRS2".toByteArray()).use { expected -> |
||||||
|
assertEquals(expected, actual) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun testBufferedWriteNoOverlap() { |
||||||
|
Jimfs.newFileSystem(Configuration.unix()).use { fs -> |
||||||
|
val path = fs.getPath("/test.dat") |
||||||
|
|
||||||
|
BufferedFileChannel(FileChannel.open(path, CREATE, READ, WRITE), 8, 8).use { channel -> |
||||||
|
Unpooled.wrappedBuffer("OpenRS2".toByteArray()).use { buf -> |
||||||
|
channel.write(0, buf.slice(), buf.readableBytes()) |
||||||
|
channel.write(8, buf.slice(), buf.readableBytes()) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
Unpooled.wrappedBuffer(Files.readAllBytes(path)).use { actual -> |
||||||
|
Unpooled.wrappedBuffer("OpenRS2\u0000OpenRS2".toByteArray()).use { expected -> |
||||||
|
assertEquals(expected, actual) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun testUnbufferedWrite() { |
||||||
|
Jimfs.newFileSystem(Configuration.unix()).use { fs -> |
||||||
|
val path = fs.getPath("/test.dat") |
||||||
|
|
||||||
|
BufferedFileChannel(FileChannel.open(path, CREATE, READ, WRITE), 8, 8).use { channel -> |
||||||
|
Unpooled.wrappedBuffer("Hello, world!".toByteArray()).use { buf -> |
||||||
|
channel.write(0, buf, buf.readableBytes()) |
||||||
|
} |
||||||
|
|
||||||
|
assertEquals(13, channel.size()) |
||||||
|
} |
||||||
|
|
||||||
|
Unpooled.wrappedBuffer(Files.readAllBytes(path)).use { actual -> |
||||||
|
Unpooled.wrappedBuffer("Hello, world!".toByteArray()).use { expected -> |
||||||
|
assertEquals(expected, actual) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun testBufferedRead() { |
||||||
|
Jimfs.newFileSystem(Configuration.unix()).use { fs -> |
||||||
|
val path = fs.getPath("/test.dat") |
||||||
|
Files.write(path, "OpenRS2OpenRS2".toByteArray()) |
||||||
|
|
||||||
|
BufferedFileChannel(FileChannel.open(path, READ), 8, 8).use { channel -> |
||||||
|
Unpooled.buffer(7, 7).use { actual -> |
||||||
|
Unpooled.wrappedBuffer("OpenRS2".toByteArray()).use { expected -> |
||||||
|
channel.read(0, actual, actual.writableBytes()) |
||||||
|
assertEquals(expected, actual) |
||||||
|
|
||||||
|
actual.clear() |
||||||
|
|
||||||
|
channel.read(7, actual, actual.writableBytes()) |
||||||
|
assertEquals(expected, actual) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun testUnbufferedRead() { |
||||||
|
Jimfs.newFileSystem(Configuration.unix()).use { fs -> |
||||||
|
val path = fs.getPath("/test.dat") |
||||||
|
Files.write(path, "OpenRS2OpenRS2".toByteArray()) |
||||||
|
|
||||||
|
BufferedFileChannel(FileChannel.open(path, READ), 8, 8).use { channel -> |
||||||
|
Unpooled.buffer(14, 14).use { actual -> |
||||||
|
Unpooled.wrappedBuffer("OpenRS2OpenRS2".toByteArray()).use { expected -> |
||||||
|
channel.read(0, actual, actual.writableBytes()) |
||||||
|
assertEquals(expected, actual) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun testBufferedEof() { |
||||||
|
Jimfs.newFileSystem(Configuration.unix()).use { fs -> |
||||||
|
val path = fs.getPath("/test.dat") |
||||||
|
|
||||||
|
BufferedFileChannel(FileChannel.open(path, CREATE, READ, WRITE), 8, 8).use { channel -> |
||||||
|
Unpooled.buffer(1, 1).use { buf -> |
||||||
|
assertThrows<EOFException> { |
||||||
|
channel.read(0, buf, buf.writableBytes()) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun testUnbufferedEof() { |
||||||
|
Jimfs.newFileSystem(Configuration.unix()).use { fs -> |
||||||
|
val path = fs.getPath("/test.dat") |
||||||
|
Files.write(path, "OpenRS2OpenRS2".toByteArray()) |
||||||
|
|
||||||
|
BufferedFileChannel(FileChannel.open(path, READ), 8, 8).use { channel -> |
||||||
|
Unpooled.buffer(15, 15).use { buf -> |
||||||
|
assertThrows<EOFException> { |
||||||
|
channel.read(0, buf, buf.writableBytes()) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun testZeroExtension() { |
||||||
|
Jimfs.newFileSystem(Configuration.unix()).use { fs -> |
||||||
|
val path = fs.getPath("/test.dat") |
||||||
|
|
||||||
|
BufferedFileChannel(FileChannel.open(path, CREATE, READ, WRITE), 8, 8).use { channel -> |
||||||
|
Unpooled.wrappedBuffer(byteArrayOf(1)).use { buf -> |
||||||
|
channel.write(7, buf, buf.readableBytes()) |
||||||
|
} |
||||||
|
|
||||||
|
Unpooled.buffer(8, 8).use { actual -> |
||||||
|
channel.read(0, actual, actual.writableBytes()) |
||||||
|
|
||||||
|
Unpooled.wrappedBuffer(byteArrayOf(0, 0, 0, 0, 0, 0, 0, 1)).use { expected -> |
||||||
|
assertEquals(expected, actual) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun testBufferedWriteThenRead() { |
||||||
|
Jimfs.newFileSystem(Configuration.unix()).use { fs -> |
||||||
|
val path = fs.getPath("/test.dat") |
||||||
|
|
||||||
|
BufferedFileChannel(FileChannel.open(path, CREATE, READ, WRITE), 8, 8).use { channel -> |
||||||
|
Unpooled.wrappedBuffer("OpenRS2".toByteArray()).use { buf -> |
||||||
|
channel.write(0, buf, buf.readableBytes()) |
||||||
|
} |
||||||
|
|
||||||
|
Unpooled.buffer(7, 7).use { actual -> |
||||||
|
channel.read(0, actual, actual.writableBytes()) |
||||||
|
|
||||||
|
Unpooled.wrappedBuffer("OpenRS2".toByteArray()).use { expected -> |
||||||
|
assertEquals(expected, actual) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun testBufferedWriteThenReadSubsetStart() { |
||||||
|
Jimfs.newFileSystem(Configuration.unix()).use { fs -> |
||||||
|
val path = fs.getPath("/test.dat") |
||||||
|
|
||||||
|
BufferedFileChannel(FileChannel.open(path, CREATE, READ, WRITE), 8, 8).use { channel -> |
||||||
|
Unpooled.wrappedBuffer("OpenRS2".toByteArray()).use { buf -> |
||||||
|
channel.write(0, buf, buf.readableBytes()) |
||||||
|
} |
||||||
|
|
||||||
|
Unpooled.buffer(6, 6).use { actual -> |
||||||
|
channel.read(0, actual, actual.writableBytes()) |
||||||
|
|
||||||
|
Unpooled.wrappedBuffer("OpenRS".toByteArray()).use { expected -> |
||||||
|
assertEquals(expected, actual) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun testBufferedWriteThenReadSubsetEnd() { |
||||||
|
Jimfs.newFileSystem(Configuration.unix()).use { fs -> |
||||||
|
val path = fs.getPath("/test.dat") |
||||||
|
|
||||||
|
BufferedFileChannel(FileChannel.open(path, CREATE, READ, WRITE), 8, 8).use { channel -> |
||||||
|
Unpooled.wrappedBuffer("OpenRS2".toByteArray()).use { buf -> |
||||||
|
channel.write(0, buf, buf.readableBytes()) |
||||||
|
} |
||||||
|
|
||||||
|
Unpooled.buffer(6, 6).use { actual -> |
||||||
|
channel.read(1, actual, actual.writableBytes()) |
||||||
|
|
||||||
|
Unpooled.wrappedBuffer("penRS2".toByteArray()).use { expected -> |
||||||
|
assertEquals(expected, actual) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun testBufferedWriteThenReadSuperset() { |
||||||
|
Jimfs.newFileSystem(Configuration.unix()).use { fs -> |
||||||
|
val path = fs.getPath("/test.dat") |
||||||
|
|
||||||
|
Files.write(path, "OpenRS2OpenRS2".toByteArray()) |
||||||
|
|
||||||
|
BufferedFileChannel(FileChannel.open(path, CREATE, READ, WRITE), 8, 8).use { channel -> |
||||||
|
Unpooled.wrappedBuffer("Hello".toByteArray()).use { buf -> |
||||||
|
channel.write(4, buf, buf.readableBytes()) |
||||||
|
} |
||||||
|
|
||||||
|
Unpooled.buffer(14, 14).use { actual -> |
||||||
|
channel.read(0, actual, actual.writableBytes()) |
||||||
|
|
||||||
|
Unpooled.wrappedBuffer("OpenHelloenRS2".toByteArray()).use { expected -> |
||||||
|
assertEquals(expected, actual) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun testBufferedWriteThenReadSupersetStart() { |
||||||
|
Jimfs.newFileSystem(Configuration.unix()).use { fs -> |
||||||
|
val path = fs.getPath("/test.dat") |
||||||
|
|
||||||
|
Files.write(path, "OpenRS2OpenRS2".toByteArray()) |
||||||
|
|
||||||
|
BufferedFileChannel(FileChannel.open(path, CREATE, READ, WRITE), 8, 8).use { channel -> |
||||||
|
Unpooled.wrappedBuffer("Hello".toByteArray()).use { buf -> |
||||||
|
channel.write(4, buf, buf.readableBytes()) |
||||||
|
} |
||||||
|
|
||||||
|
Unpooled.buffer(7, 7).use { actual -> |
||||||
|
channel.read(0, actual, actual.writableBytes()) |
||||||
|
|
||||||
|
Unpooled.wrappedBuffer("OpenHel".toByteArray()).use { expected -> |
||||||
|
assertEquals(expected, actual) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun testBufferedWriteThenReadSupersetEnd() { |
||||||
|
Jimfs.newFileSystem(Configuration.unix()).use { fs -> |
||||||
|
val path = fs.getPath("/test.dat") |
||||||
|
|
||||||
|
Files.write(path, "OpenRS2OpenRS2".toByteArray()) |
||||||
|
|
||||||
|
BufferedFileChannel(FileChannel.open(path, CREATE, READ, WRITE), 8, 8).use { channel -> |
||||||
|
Unpooled.wrappedBuffer("Hello".toByteArray()).use { buf -> |
||||||
|
channel.write(4, buf, buf.readableBytes()) |
||||||
|
} |
||||||
|
|
||||||
|
Unpooled.buffer(7, 7).use { actual -> |
||||||
|
channel.read(7, actual, actual.writableBytes()) |
||||||
|
|
||||||
|
Unpooled.wrappedBuffer("loenRS2".toByteArray()).use { expected -> |
||||||
|
assertEquals(expected, actual) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun testBufferedWriteThenReadNoOverlap() { |
||||||
|
Jimfs.newFileSystem(Configuration.unix()).use { fs -> |
||||||
|
val path = fs.getPath("/test.dat") |
||||||
|
|
||||||
|
Files.write(path, "OpenRS2OpenRS2".toByteArray()) |
||||||
|
|
||||||
|
BufferedFileChannel(FileChannel.open(path, READ, WRITE), 4, 8).use { channel -> |
||||||
|
Unpooled.wrappedBuffer("Hello".toByteArray()).use { buf -> |
||||||
|
channel.write(4, buf, buf.readableBytes()) |
||||||
|
} |
||||||
|
|
||||||
|
Unpooled.buffer(4, 4).use { actual -> |
||||||
|
channel.read(0, actual, actual.writableBytes()) |
||||||
|
|
||||||
|
Unpooled.wrappedBuffer("Open".toByteArray()).use { expected -> |
||||||
|
assertEquals(expected, actual) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
Unpooled.buffer(5, 5).use { actual -> |
||||||
|
channel.read(9, actual, actual.writableBytes()) |
||||||
|
|
||||||
|
Unpooled.wrappedBuffer("enRS2".toByteArray()).use { expected -> |
||||||
|
assertEquals(expected, actual) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun testBufferedReadThenWrite() { |
||||||
|
Jimfs.newFileSystem(Configuration.unix()).use { fs -> |
||||||
|
val path = fs.getPath("/test.dat") |
||||||
|
|
||||||
|
Files.write(path, "OpenRS2OpenRS2".toByteArray()) |
||||||
|
|
||||||
|
BufferedFileChannel(FileChannel.open(path, READ, WRITE), 4, 0).use { channel -> |
||||||
|
Unpooled.buffer(4, 4).use { actual -> |
||||||
|
channel.read(4, actual, actual.writableBytes()) |
||||||
|
|
||||||
|
Unpooled.wrappedBuffer("RS2O".toByteArray()).use { expected -> |
||||||
|
assertEquals(expected, actual) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
Unpooled.wrappedBuffer("ABCD".toByteArray()).use { buf -> |
||||||
|
channel.write(4, buf.slice(), buf.readableBytes()) |
||||||
|
} |
||||||
|
|
||||||
|
Unpooled.buffer(4, 4).use { actual -> |
||||||
|
channel.read(4, actual, actual.writableBytes()) |
||||||
|
|
||||||
|
Unpooled.wrappedBuffer("ABCD".toByteArray()).use { expected -> |
||||||
|
assertEquals(expected, actual) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun testBufferedReadThenWriteSubsetStart() { |
||||||
|
Jimfs.newFileSystem(Configuration.unix()).use { fs -> |
||||||
|
val path = fs.getPath("/test.dat") |
||||||
|
|
||||||
|
Files.write(path, "OpenRS2OpenRS2".toByteArray()) |
||||||
|
|
||||||
|
BufferedFileChannel(FileChannel.open(path, READ, WRITE), 4, 0).use { channel -> |
||||||
|
Unpooled.buffer(4, 4).use { actual -> |
||||||
|
channel.read(4, actual, actual.writableBytes()) |
||||||
|
|
||||||
|
Unpooled.wrappedBuffer("RS2O".toByteArray()).use { expected -> |
||||||
|
assertEquals(expected, actual) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
Unpooled.wrappedBuffer("ABC".toByteArray()).use { buf -> |
||||||
|
channel.write(4, buf.slice(), buf.readableBytes()) |
||||||
|
} |
||||||
|
|
||||||
|
Unpooled.buffer(4, 4).use { actual -> |
||||||
|
channel.read(4, actual, actual.writableBytes()) |
||||||
|
|
||||||
|
Unpooled.wrappedBuffer("ABCO".toByteArray()).use { expected -> |
||||||
|
assertEquals(expected, actual) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun testBufferedReadThenWriteSubsetEnd() { |
||||||
|
Jimfs.newFileSystem(Configuration.unix()).use { fs -> |
||||||
|
val path = fs.getPath("/test.dat") |
||||||
|
|
||||||
|
Files.write(path, "OpenRS2OpenRS2".toByteArray()) |
||||||
|
|
||||||
|
BufferedFileChannel(FileChannel.open(path, READ, WRITE), 4, 0).use { channel -> |
||||||
|
Unpooled.buffer(4, 4).use { actual -> |
||||||
|
channel.read(4, actual, actual.writableBytes()) |
||||||
|
|
||||||
|
Unpooled.wrappedBuffer("RS2O".toByteArray()).use { expected -> |
||||||
|
assertEquals(expected, actual) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
Unpooled.wrappedBuffer("BCD".toByteArray()).use { buf -> |
||||||
|
channel.write(5, buf.slice(), buf.readableBytes()) |
||||||
|
} |
||||||
|
|
||||||
|
Unpooled.buffer(4, 4).use { actual -> |
||||||
|
channel.read(4, actual, actual.writableBytes()) |
||||||
|
|
||||||
|
Unpooled.wrappedBuffer("RBCD".toByteArray()).use { expected -> |
||||||
|
assertEquals(expected, actual) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun testBufferedReadThenWriteSupersetStart() { |
||||||
|
Jimfs.newFileSystem(Configuration.unix()).use { fs -> |
||||||
|
val path = fs.getPath("/test.dat") |
||||||
|
|
||||||
|
Files.write(path, "OpenRS2OpenRS2".toByteArray()) |
||||||
|
|
||||||
|
BufferedFileChannel(FileChannel.open(path, READ, WRITE), 4, 0).use { channel -> |
||||||
|
Unpooled.buffer(4, 4).use { actual -> |
||||||
|
channel.read(4, actual, actual.writableBytes()) |
||||||
|
|
||||||
|
Unpooled.wrappedBuffer("RS2O".toByteArray()).use { expected -> |
||||||
|
assertEquals(expected, actual) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
Unpooled.wrappedBuffer("ZABCD".toByteArray()).use { buf -> |
||||||
|
channel.write(3, buf.slice(), buf.readableBytes()) |
||||||
|
} |
||||||
|
|
||||||
|
Unpooled.buffer(4, 4).use { actual -> |
||||||
|
channel.read(4, actual, actual.writableBytes()) |
||||||
|
|
||||||
|
Unpooled.wrappedBuffer("ABCD".toByteArray()).use { expected -> |
||||||
|
assertEquals(expected, actual) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun testBufferedReadThenWriteSupersetEnd() { |
||||||
|
Jimfs.newFileSystem(Configuration.unix()).use { fs -> |
||||||
|
val path = fs.getPath("/test.dat") |
||||||
|
|
||||||
|
Files.write(path, "OpenRS2OpenRS2".toByteArray()) |
||||||
|
|
||||||
|
BufferedFileChannel(FileChannel.open(path, READ, WRITE), 4, 0).use { channel -> |
||||||
|
Unpooled.buffer(4, 4).use { actual -> |
||||||
|
channel.read(4, actual, actual.writableBytes()) |
||||||
|
|
||||||
|
Unpooled.wrappedBuffer("RS2O".toByteArray()).use { expected -> |
||||||
|
assertEquals(expected, actual) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
Unpooled.wrappedBuffer("ABCDZ".toByteArray()).use { buf -> |
||||||
|
channel.write(4, buf.slice(), buf.readableBytes()) |
||||||
|
} |
||||||
|
|
||||||
|
Unpooled.buffer(4, 4).use { actual -> |
||||||
|
channel.read(4, actual, actual.writableBytes()) |
||||||
|
|
||||||
|
Unpooled.wrappedBuffer("ABCD".toByteArray()).use { expected -> |
||||||
|
assertEquals(expected, actual) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue