| /* |
| * Copyright 2017 HugeGraph Authors |
| * |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with this |
| * work for additional information regarding copyright ownership. The ASF |
| * licenses this file to You under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| * License for the specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| package com.baidu.hugegraph.computer.core.store; |
| |
| import java.io.EOFException; |
| import java.io.File; |
| import java.io.IOException; |
| import java.util.Random; |
| import java.util.UUID; |
| |
| import org.apache.commons.io.FileUtils; |
| import org.junit.BeforeClass; |
| import org.junit.Test; |
| |
| import com.baidu.hugegraph.computer.core.config.ComputerOptions; |
| import com.baidu.hugegraph.computer.core.config.Config; |
| import com.baidu.hugegraph.computer.core.io.UnsafeBytesInput; |
| import com.baidu.hugegraph.computer.core.io.UnsafeBytesOutput; |
| import com.baidu.hugegraph.computer.core.store.file.seqfile.ValueFileInput; |
| import com.baidu.hugegraph.computer.core.store.file.seqfile.ValueFileOutput; |
| import com.baidu.hugegraph.computer.suite.unit.UnitTestBase; |
| import com.baidu.hugegraph.testutil.Assert; |
| import com.baidu.hugegraph.testutil.Whitebox; |
| import com.baidu.hugegraph.util.E; |
| |
| public class ValueFileTest { |
| |
| private static Config CONFIG; |
| |
| @BeforeClass |
| public static void init() { |
| CONFIG = UnitTestBase.updateWithRequiredOptions( |
| ComputerOptions.VALUE_FILE_MAX_SEGMENT_SIZE, "32" |
| ); |
| } |
| |
| @Test |
| public void testConstructor() throws IOException { |
| int bufferCapacity = 13; |
| File dir = createTempDir(); |
| try { |
| try (ValueFileOutput output = new ValueFileOutput(CONFIG, dir, |
| bufferCapacity)) { |
| Assert.assertEquals(0, output.position()); |
| } |
| Assert.assertThrows(IllegalArgumentException.class, () -> { |
| new ValueFileOutput(CONFIG, dir, 1); |
| }, e -> { |
| Assert.assertContains("bufferCapacity must be >= 8", |
| e.getMessage()); |
| }); |
| |
| try (ValueFileInput input = new ValueFileInput(CONFIG, dir, |
| bufferCapacity)) { |
| Assert.assertEquals(0, input.position()); |
| } |
| Assert.assertThrows(IllegalArgumentException.class, () -> { |
| new ValueFileInput(CONFIG, dir, 1); |
| }, e -> { |
| Assert.assertContains("The parameter bufferSize must be >= 8", |
| e.getMessage()); |
| }); |
| |
| Config config = UnitTestBase.updateWithRequiredOptions( |
| ComputerOptions.VALUE_FILE_MAX_SEGMENT_SIZE, |
| String.valueOf(Integer.MAX_VALUE) |
| ); |
| try (ValueFileOutput output = new ValueFileOutput(config, dir)) { |
| Assert.assertEquals(0, output.position()); |
| } |
| try (ValueFileInput input = new ValueFileInput(config, dir)) { |
| Assert.assertEquals(0, input.position()); |
| } |
| } finally { |
| FileUtils.deleteQuietly(dir); |
| } |
| } |
| |
| @Test |
| public void testByteArray() throws IOException { |
| File dir = createTempDir(); |
| final int bufferSize = 13; |
| |
| byte[] bytes; |
| try { |
| try (ValueFileOutput output = new ValueFileOutput(CONFIG, dir, |
| bufferSize)) { |
| // The remaining capacity of the buffer can hold |
| bytes = orderBytesBySize(5); |
| output.write(bytes); |
| |
| /* |
| * The remaining capacity of the buffer can't hold and write |
| * size is smaller than buffer capacity |
| */ |
| bytes = orderBytesBySize(11); |
| output.write(bytes); |
| |
| /* |
| * Write size is greater than buffer capacity and remaining of |
| * segment can hold write size |
| */ |
| bytes = orderBytesBySize(14); |
| output.write(bytes); |
| |
| /* |
| * Write size is greater than buffer capacity and remaining of |
| * segment can't hold write size |
| */ |
| bytes = orderBytesBySize(80); |
| output.write(bytes); |
| } |
| |
| try (ValueFileInput input = new ValueFileInput(CONFIG, dir, |
| bufferSize)) { |
| assertBytes(5, input.readBytes(5)); |
| assertBytes(11, input.readBytes(11)); |
| assertBytes(14, input.readBytes(14)); |
| assertBytes(80, input.readBytes(80)); |
| } |
| } finally { |
| FileUtils.deleteQuietly(dir); |
| } |
| } |
| |
| @Test |
| public void testWriteInt() throws IOException { |
| File dir = createTempDir(); |
| final int bufferSize = 10; |
| |
| try { |
| try (ValueFileOutput output = new ValueFileOutput(CONFIG, dir, |
| bufferSize)) { |
| for (int i = 0; i < 50; i++) { |
| output.writeInt(i); |
| } |
| } |
| |
| try (ValueFileInput input = new ValueFileInput(CONFIG, dir, |
| bufferSize)) { |
| for (int i = 0; i < 50; i++) { |
| Assert.assertEquals(i, input.readInt()); |
| } |
| Assert.assertThrows(IOException.class, input::readInt, e -> { |
| Assert.assertContains("overflows buffer", |
| e.getMessage()); |
| }); |
| } |
| } finally { |
| FileUtils.deleteQuietly(dir); |
| } |
| } |
| |
| @Test |
| public void testWriteIntWithPosition() throws IOException { |
| File dir = createTempDir(); |
| final int bufferSize = 20; |
| |
| try { |
| try (ValueFileOutput output = new ValueFileOutput(CONFIG, dir, |
| bufferSize)) { |
| for (int i = 0; i < 50; i++) { |
| // Write position in buffer |
| if (i == 2) { |
| output.writeFixedInt(4, 100); |
| } |
| output.writeInt(i); |
| } |
| output.writeFixedInt(Integer.BYTES * 10, 100); |
| } |
| |
| try (ValueFileInput input = new ValueFileInput(CONFIG, dir, |
| bufferSize)) { |
| for (int i = 0; i < 50; i++) { |
| if (i == 1 || i == 10) { |
| Assert.assertEquals(100, input.readInt()); |
| continue; |
| } |
| Assert.assertEquals(i, input.readInt()); |
| } |
| } |
| } finally { |
| FileUtils.deleteQuietly(dir); |
| } |
| } |
| |
| @Test |
| public void testSeek() throws IOException { |
| File dir = createTempDir(); |
| final int bufferSize = 15; |
| |
| try { |
| // Test ValueFileInput |
| try (ValueFileOutput output = new ValueFileOutput(CONFIG, dir, |
| bufferSize)) { |
| for (int i = 0; i < 50; i++) { |
| output.writeInt(i); |
| } |
| } |
| |
| try (ValueFileInput input = new ValueFileInput(CONFIG, dir, |
| bufferSize)) { |
| Long fileLength = Whitebox.getInternalState(input, |
| "fileLength"); |
| Assert.assertEquals(200L, fileLength); |
| |
| long position; |
| // Position in buffer |
| position = 13L; |
| assertSeek(fileLength, input, position); |
| |
| // Position not in buffer but in current segment |
| position = 28L; |
| assertSeek(fileLength, input, position); |
| |
| // Position not in buffer and not in current segment |
| position = 200L; |
| assertSeek(fileLength, input, position); |
| position = 3L; |
| assertSeek(fileLength, input, position); |
| position = 3L; |
| assertSeek(fileLength, input, position); |
| position = 68L; |
| assertSeek(fileLength, input, position); |
| position = 0L; |
| assertSeek(fileLength, input, position); |
| |
| // Position out of bound |
| position = 300L; |
| long finalPosition = position; |
| Assert.assertThrows(EOFException.class, () -> { |
| input.seek(finalPosition); |
| }, e -> { |
| Assert.assertContains("reach the end of file", |
| e.getMessage()); |
| }); |
| |
| input.seek(16 * Integer.BYTES); |
| Assert.assertEquals(16, input.readInt()); |
| |
| // Random seek and assert data |
| Random random = new Random(); |
| for (int i = 0; i < 10; i++) { |
| int num = random.nextInt(49); |
| input.seek(num * Integer.BYTES); |
| Assert.assertEquals(num, input.readInt()); |
| } |
| } |
| |
| // Test ValueFileOutput |
| try (ValueFileOutput output = new ValueFileOutput(CONFIG, dir, |
| bufferSize)) { |
| // 200, 100, 2, 3, 4..... |
| for (int i = 0; i < 50; i++) { |
| // Position in buffer |
| if (i == 2) { |
| output.seek(4); |
| output.writeInt(100); |
| } |
| // Position not int buffer |
| if (i == 10) { |
| long oldPosition = output.position(); |
| output.seek(0); |
| output.writeInt(200); |
| output.seek(oldPosition); |
| } |
| output.writeInt(i); |
| } |
| |
| Assert.assertThrows(EOFException.class, () -> { |
| output.seek(500L); |
| }, e -> { |
| Assert.assertContains("reach the end of file", |
| e.getMessage()); |
| }); |
| } |
| |
| try (ValueFileInput input = new ValueFileInput(CONFIG, dir, |
| bufferSize)) { |
| for (int i = 0; i < 50; i++) { |
| if (i == 0) { |
| Assert.assertEquals(200, input.readInt()); |
| continue; |
| } |
| if (i == 1) { |
| Assert.assertEquals(100, input.readInt()); |
| continue; |
| } |
| Assert.assertEquals(i, input.readInt()); |
| } |
| } |
| } finally { |
| FileUtils.deleteQuietly(dir); |
| } |
| } |
| |
| @Test |
| public void testSkip() throws IOException { |
| File dir = createTempDir(); |
| final int bufferSize = 15; |
| |
| try { |
| try (ValueFileOutput output = new ValueFileOutput(CONFIG, dir, |
| bufferSize)) { |
| // 100, 1, 2, 200, 4, 5 ...... |
| for (int i = 0; i < 50; i++) { |
| output.writeInt(i); |
| } |
| output.seek(0); |
| output.writeInt(100); |
| |
| output.skip(8); |
| output.writeInt(200); |
| } |
| |
| try (ValueFileInput input = new ValueFileInput(CONFIG, dir, |
| bufferSize)) { |
| Assert.assertEquals(100, input.readInt()); |
| |
| input.skip(8); |
| Assert.assertEquals(200, input.readInt()); |
| |
| input.seek(20 * Integer.BYTES); |
| Assert.assertEquals(20, input.readInt()); |
| |
| input.skip(10 * Integer.BYTES); |
| Assert.assertEquals(31, input.readInt()); |
| |
| Assert.assertThrows(IllegalArgumentException.class, () -> { |
| input.skip(500L); |
| }, e -> { |
| Assert.assertContains("because don't have enough data", |
| e.getMessage()); |
| }); |
| } |
| } finally { |
| FileUtils.deleteQuietly(dir); |
| } |
| } |
| |
| @Test |
| public void testDuplicate() throws IOException { |
| File dir = createTempDir(); |
| final int bufferSize = 15; |
| |
| try { |
| try (ValueFileOutput output = new ValueFileOutput(CONFIG, dir, |
| bufferSize)) { |
| for (int i = 0; i < 50; i++) { |
| output.writeInt(i); |
| } |
| } |
| |
| try (ValueFileInput input = new ValueFileInput(CONFIG, dir, |
| bufferSize)) { |
| input.seek(20 * Integer.BYTES); |
| Assert.assertEquals(20, input.readInt()); |
| |
| UnsafeBytesInput other = input.duplicate(); |
| Assert.assertEquals(21, other.readInt()); |
| Assert.assertEquals(21, input.readInt()); |
| } |
| } finally { |
| FileUtils.deleteQuietly(dir); |
| } |
| } |
| |
| @Test |
| public void testCompare() throws IOException { |
| File dir = createTempDir(); |
| final int bufferSize = 15; |
| |
| try { |
| try (ValueFileOutput output = new ValueFileOutput(CONFIG, dir, |
| bufferSize)) { |
| String data = "HugeGraph is a fast-speed and highly-scalable " + |
| "graph database"; |
| output.write(data.getBytes()); |
| } |
| |
| try (ValueFileInput input1 = new ValueFileInput(CONFIG, dir, |
| bufferSize); |
| ValueFileInput input2 = new ValueFileInput(CONFIG, dir, |
| bufferSize)) { |
| int result; |
| |
| // All in buffer |
| result = input1.compare(0, 3, input2, 1, 3); |
| Assert.assertLt(0, result); |
| |
| result = input1.compare(0, 3, input2, 0, 5); |
| Assert.assertLt(0, result); |
| |
| result = input1.compare(1, 3, input2, 1, 3); |
| Assert.assertEquals(0, result); |
| |
| // The input1 in buffer, input2 not in buffer |
| result = input1.compare(0, 3, input2, 21, 3); |
| Assert.assertLt(0, result); |
| |
| result = input1.compare(1, 3, input2, 23, 3); |
| Assert.assertGt(0, result); |
| |
| result = input1.compare(0, 3, input2, 23, 5); |
| Assert.assertLt(0, result); |
| |
| result = input1.compare(3, 1, input2, 23, 1); |
| Assert.assertEquals(0, result); |
| |
| // The input1 not in buffer, input2 in buffer |
| input2.seek(20); |
| result = input1.compare(23, 5, input2, 23, 5); |
| Assert.assertEquals(0, result); |
| |
| result = input1.compare(23, 12, input2, 23, 5); |
| Assert.assertGt(0, result); |
| |
| // All not in buffer |
| input2.seek(0); |
| result = input1.compare(23, 5, input2, 23, 5); |
| Assert.assertEquals(0, result); |
| |
| result = input1.compare(23, 12, input2, 23, 5); |
| Assert.assertGt(0, result); |
| |
| // Compare with different class |
| UnsafeBytesOutput output = new UnsafeBytesOutput(20); |
| output.writeBytes("banana"); |
| UnsafeBytesInput input = new UnsafeBytesInput(output.buffer()); |
| output.close(); |
| |
| result = input1.compare(0, 2, input, 0, 4); |
| Assert.assertLt(0, result); |
| |
| result = input1.compare(1, 5, input, 0, 4); |
| Assert.assertGt(0, result); |
| } |
| } finally { |
| FileUtils.deleteQuietly(dir); |
| } |
| } |
| |
| private static byte[] orderBytesBySize(int size) { |
| E.checkArgument(size <= Byte.MAX_VALUE, |
| "Size must be <= %s but get '%s'", |
| Byte.MAX_VALUE, size); |
| byte[] bytes = new byte[size]; |
| for (int i = 0; i < size; i++) { |
| bytes[i] = (byte) i; |
| } |
| return bytes; |
| } |
| |
| private static void assertBytes(int expectSize, byte[] bytes) { |
| E.checkArgument(expectSize <= Byte.MAX_VALUE, |
| "Size must be <= %s but get '%s'", |
| Byte.MAX_VALUE, expectSize); |
| for (int i = 0; i < expectSize; i++) { |
| Assert.assertEquals(i, bytes[i]); |
| } |
| } |
| |
| private static File createTempDir() { |
| File dir = new File(UUID.randomUUID().toString()); |
| dir.mkdirs(); |
| return dir; |
| } |
| |
| private static void assertSeek(long fileLength, ValueFileInput input, |
| long position) throws IOException { |
| input.seek(position); |
| Assert.assertEquals(position, input.position()); |
| Assert.assertEquals(fileLength - position, input.available()); |
| } |
| } |