blob: 9dd4557c435bcc0e9639f8204bee07332a9d424f [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2005, 2016 QNX Software Systems and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* QNX Software Systems - initial API and implementation
* Andrew Ferguson (Symbian)
* Markus Schorn (Wind River Systems)
*******************************************************************************/
package org.eclipse.jdt.core.tests.nd;
import java.io.File;
import java.util.Random;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.jdt.core.tests.nd.util.BaseTestCase;
import org.eclipse.jdt.internal.core.nd.Nd;
import org.eclipse.jdt.internal.core.nd.NdNode;
import org.eclipse.jdt.internal.core.nd.NdNodeTypeRegistry;
import org.eclipse.jdt.internal.core.nd.db.BTree;
import org.eclipse.jdt.internal.core.nd.db.ChunkCache;
import org.eclipse.jdt.internal.core.nd.db.Database;
import org.eclipse.jdt.internal.core.nd.db.IBTreeComparator;
import org.eclipse.jdt.internal.core.nd.db.IBTreeVisitor;
import org.eclipse.jdt.internal.core.nd.db.IString;
import org.eclipse.jdt.internal.core.nd.db.IndexException;
import org.eclipse.jdt.internal.core.nd.db.ShortString;
import junit.framework.Test;
/**
* Tests for the {@link Database} class.
*/
public class DatabaseTest extends BaseTestCase {
// This constant can be used to run the test with very large databases.
// Try, for example, setting it to Integer.MAX_VALUE * 7L;
private static final long TEST_OFFSET = 0;
private Nd nd;
protected Database db;
private static final int CURRENT_VERSION = 10;
@Override
protected void setUp() throws Exception {
super.setUp();
String testName = getName();
NdNodeTypeRegistry<NdNode> registry = new NdNodeTypeRegistry<>();
this.nd = new Nd(DatabaseTestUtil.getTempDbName(testName), new ChunkCache(), registry,
0, 100, CURRENT_VERSION);
this.db = this.nd.getDB();
this.db.setExclusiveLock();
// Allocate all database chunks up to TEST_OFFSET.
int count = 0;
for (long offset = 0; offset < TEST_OFFSET;) {
offset = this.db.malloc(Database.MAX_MALLOC_SIZE, Database.POOL_MISC);
if (++count >= 1000) {
this.db.flush();
count = 0;
}
}
this.db.flush();
}
public static Test suite() {
return BaseTestCase.suite(DatabaseTest.class);
}
@Override
protected void tearDown() throws Exception {
this.db.close();
if (!this.db.getLocation().delete()) {
this.db.getLocation().deleteOnExit();
}
this.db= null;
}
public void testBlockSizeAndFirstBlock() throws Exception {
assertEquals(CURRENT_VERSION, this.db.getVersion());
final int realsize = 42;
final int deltas = (realsize + Database.BLOCK_HEADER_SIZE + Database.BLOCK_SIZE_DELTA - 1) / Database.BLOCK_SIZE_DELTA;
final int blocksize = deltas * Database.BLOCK_SIZE_DELTA;
final int freeDeltas= Database.CHUNK_SIZE / Database.BLOCK_SIZE_DELTA - deltas;
long mem = this.db.malloc(realsize, Database.POOL_MISC);
assertEquals(-blocksize, this.db.getShort(mem - Database.BLOCK_HEADER_SIZE));
this.db.free(mem, Database.POOL_MISC);
assertEquals(blocksize, this.db.getShort(mem - Database.BLOCK_HEADER_SIZE));
assertEquals(mem, this.db.getRecPtr((deltas - Database.MIN_BLOCK_DELTAS +1 ) * Database.INT_SIZE));
assertEquals(mem + blocksize, this.db.getRecPtr((freeDeltas - Database.MIN_BLOCK_DELTAS + 1) * Database.INT_SIZE));
}
public void testBug192437() throws Exception {
File tmp= File.createTempFile("readOnlyEmpty", ".db");
try {
tmp.setReadOnly();
/* check opening a readonly file for rw access fails */
try {
new Database(tmp, ChunkCache.getSharedInstance(), 0, false);
fail("A readonly file should not be openable with write-access");
} catch (IndexException e) {
// we expect to get a failure here
}
/* check opening a readonly file for read access does not fail */
try {
new Database(tmp, ChunkCache.getSharedInstance(), 0, true);
} catch (IndexException e) {
fail("A readonly file should be readable by a permanently readonly database " + e);
}
} finally {
tmp.delete(); // this may be pointless on some platforms
}
}
public void testFreeBlockLinking() throws Exception {
final int realsize = 42;
final int deltas = (realsize + Database.BLOCK_HEADER_SIZE + Database.BLOCK_SIZE_DELTA - 1) / Database.BLOCK_SIZE_DELTA;
long mem1 = this.db.malloc(realsize, Database.POOL_MISC);
long mem2 = this.db.malloc(realsize, Database.POOL_MISC);
this.db.free(mem1, Database.POOL_MISC);
this.db.free(mem2, Database.POOL_MISC);
assertEquals(mem2, this.db.getRecPtr((deltas - Database.MIN_BLOCK_DELTAS + 1) * Database.INT_SIZE));
assertEquals(0, this.db.getRecPtr(mem2));
assertEquals(mem1, this.db.getRecPtr(mem2 + Database.INT_SIZE));
assertEquals(mem2, this.db.getRecPtr(mem1));
assertEquals(0, this.db.getRecPtr(mem1 + Database.INT_SIZE));
}
public void testSimpleAllocationLifecycle() throws Exception {
long mem1 = this.db.malloc(42, Database.POOL_MISC);
this.db.free(mem1, Database.POOL_MISC);
long mem2 = this.db.malloc(42, Database.POOL_MISC);
assertEquals(mem2, mem1);
}
private static class FindVisitor implements IBTreeVisitor {
private Database db;
private String key;
private long address;
public FindVisitor(Database db, String key) {
this.db = db;
this.key = key;
}
@Override
public int compare(long toCompare) {
return this.db.getString(this.db.getRecPtr(toCompare + 4)).compare(this.key, true);
}
@Override
public boolean visit(long toCompare) {
this.address = toCompare;
return false;
}
public long getRecord() {
return this.address;
}
}
public void testStringsInBTree() throws Exception {
String[] names = {
"ARLENE",
"BRET",
"CINDY",
"DENNIS",
"EMILY",
"FRANKLIN",
"GERT",
"HARVEY",
"IRENE",
"JOSE",
"KATRINA",
"LEE",
"MARIA",
"NATE",
"OPHELIA",
"PHILIPPE",
"RITA",
"STAN",
"TAMMY",
"VINCE",
"WILMA",
"ALPHA",
"BETA"
};
IBTreeComparator comparator = new IBTreeComparator() {
@Override
public int compare(Nd ndToCompare, long record1, long record2) {
IString string1 = DatabaseTest.this.db.getString(DatabaseTest.this.db.getRecPtr(record1 + 4));
IString string2 = DatabaseTest.this.db.getString(DatabaseTest.this.db.getRecPtr(record2 + 4));
return string1.compare(string2, true);
}
};
BTree btree = new BTree(this.nd, Database.DATA_AREA_OFFSET, comparator);
for (int i = 0; i < names.length; ++i) {
String name = names[i];
long record = this.db.malloc(8, Database.POOL_MISC);
this.db.putInt(record + 0, i);
IString string = this.db.newString(name);
this.db.putRecPtr(record + 4, string.getRecord());
btree.insert(record);
}
for (int i = 0; i < names.length; ++i) {
String name = names[i];
FindVisitor finder = new FindVisitor(this.db, name);
btree.accept(finder);
long record = finder.getRecord();
assertTrue(record != 0);
assertEquals(i, this.db.getInt(record));
IString rname = this.db.getString(this.db.getRecPtr(record + 4));
assertTrue(rname.equals(name));
}
}
private final int GT = 1, LT = -1, EQ = 0;
public void testShortStringComparison() throws CoreException {
Random r= new Random(90210);
assertCMP("", this.EQ, "", true);
assertCMP("", this.EQ, "", false);
doTrials(1000, 1, ShortString.MAX_BYTE_LENGTH / 2, r, true);
doTrials(1000, 1, ShortString.MAX_BYTE_LENGTH / 2, r, false);
doTrials(1000, 1, ShortString.MAX_BYTE_LENGTH, r, true);
doTrials(1000, 1, ShortString.MAX_BYTE_LENGTH, r, false);
assertCMP("a", this.LT, "b", true);
assertCMP("aa", this.LT, "ab", true);
assertCMP("a", this.EQ, "a", true);
assertCMP("a", this.GT, "A", true);
assertCMP("aa", this.GT, "aA", true);
assertCMP("a", this.GT, "B", true);
assertCMP("a", this.EQ, "a", false);
assertCMP("a", this.EQ, "A", false);
}
public void testLongStringComparison() throws CoreException {
Random r= new Random(314159265);
doTrials(100, ShortString.MAX_BYTE_LENGTH + 1, ShortString.MAX_BYTE_LENGTH * 2, r, true);
doTrials(100, ShortString.MAX_BYTE_LENGTH + 1, ShortString.MAX_BYTE_LENGTH * 2, r, false);
}
private void doTrials(int n, int min, int max, Random r, boolean caseSensitive) throws CoreException {
// long start = System.currentTimeMillis();
for (int i= 0; i < n; i++) {
String a = randomString(min, max, r);
String b = randomString(min, max, r);
int expected = caseSensitive ? a.compareTo(b) : a.compareToIgnoreCase(b);
assertCMP(a, expected, b, caseSensitive);
}
// System.out.print("Trials: " + n + " Max length: " + max + " ignoreCase: " + !caseSensitive);
// System.out.println(" Time: " + (System.currentTimeMillis() - start));
}
private String randomString(int min, int max, Random r) {
int len = min + r.nextInt(max - min);
return randomString(len, r);
}
private String randomString(int len, Random r) {
StringBuilder result = new StringBuilder(len);
for (int i= 0; i < len; i++) {
result.append(randomChar(r));
}
return result.toString();
}
private char randomChar(Random r) {
// We only match String.compareToIgnoreCase behavior within this limited range.
return (char) (32 + r.nextInt(40));
}
private void assertCMP(String a, int expected, String b, boolean caseSensitive) throws CoreException {
char[] acs = a.toCharArray();
char[] bcs = b.toCharArray();
IString aiss = this.db.newString(a);
IString biss = this.db.newString(b);
IString aisc = this.db.newString(acs);
IString bisc = this.db.newString(bcs);
assertEquals(a.hashCode(), aiss.hashCode());
assertEquals(a.hashCode(), aisc.hashCode());
assertEquals(b.hashCode(), biss.hashCode());
assertEquals(b.hashCode(), bisc.hashCode());
assertEquals(aiss, a);
assertEquals(aisc, a);
assertEquals(biss, b);
assertEquals(bisc, b);
assertSignEquals(expected, aiss.compare(bcs, caseSensitive));
assertSignEquals(expected, aiss.compare(biss, caseSensitive));
assertSignEquals(expected, aiss.compare(bisc, caseSensitive));
assertSignEquals(expected, aiss.compare(b, caseSensitive));
assertSignEquals(expected, aiss.comparePrefix(bcs, caseSensitive));
assertSignEquals(expected, -biss.compare(acs, caseSensitive));
assertSignEquals(expected, -biss.compare(aiss, caseSensitive));
assertSignEquals(expected, -biss.compare(aisc, caseSensitive));
assertSignEquals(expected, -biss.compare(a, caseSensitive));
assertSignEquals(expected, -biss.comparePrefix(acs, caseSensitive));
if (!caseSensitive && expected != 0) {
assertSignEquals(expected, aiss.compareCompatibleWithIgnoreCase(bcs));
assertSignEquals(expected, aiss.compareCompatibleWithIgnoreCase(biss));
assertSignEquals(expected, aiss.compareCompatibleWithIgnoreCase(bisc));
assertSignEquals(expected, -biss.compareCompatibleWithIgnoreCase(acs));
assertSignEquals(expected, -biss.compareCompatibleWithIgnoreCase(aiss));
assertSignEquals(expected, -biss.compareCompatibleWithIgnoreCase(aisc));
}
}
private void assertSignEquals(int a, int b) {
a= a < 0 ? -1 : (a > 0 ? 1 : 0);
b= b < 0 ? -1 : (b > 0 ? 1 : 0);
assertEquals(a, b);
}
}