blob: d239287ffc9a8af9327f0d2c12c640b24387569a [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2005, 2016 QNX Software Systems and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* QNX Software Systems - initial API and implementation
* Andrew Ferguson (Symbian)
* Markus Schorn (Wind River Systems)
*******************************************************************************/
package org.eclipse.jdt.core.tests.nd;
import org.eclipse.jdt.core.tests.nd.util.BaseTestCase;
import org.eclipse.jdt.internal.core.nd.Nd;
import org.eclipse.jdt.internal.core.nd.db.Database;
import junit.framework.Test;
/**
* Tests for the {@link Database} class.
*/
public class LargeBlockTest extends BaseTestCase {
private Nd nd;
protected Database db;
@Override
protected void setUp() throws Exception {
super.setUp();
String testName = getName();
this.nd = DatabaseTestUtil.createWithoutNodeRegistry(testName);
this.db = this.nd.getDB();
this.db.setExclusiveLock();
this.db.flush();
}
public static Test suite() {
return BaseTestCase.suite(LargeBlockTest.class);
}
@Override
protected void tearDown() throws Exception {
DatabaseTestUtil.deleteDatabase(this.db);
this.db = null;
}
private long mallocChunks(int chunks) {
return malloc(Database.getBytesThatFitInChunks(chunks));
}
private long malloc(long bytes) {
return this.db.malloc(bytes, Database.POOL_MISC);
}
private void free(long address) {
this.db.free(address, Database.POOL_MISC);
}
/**
* Allocate the maximum number of bytes that can fit in 3 chunks and verify
* that it doesn't overflow.
*/
public void testAllocationThatFillsMultipleChunksDoesntOverflow() throws Exception {
int chunkCount = this.db.getChunkCount();
int numChunks = 5;
mallocChunks(numChunks);
assertEquals("The database should not allocate more (or less) memory than is needed", numChunks + chunkCount,
this.db.getChunkCount());
}
/**
* Allocates a few blocks, frees them, then allocates more blocks. Verifies
* that the database reuses the chunks from the first allocation when it
* tries to allocate the larger block later.
*/
public void testLastChunkIsReused() throws Exception {
int chunkCount = this.db.getChunkCount();
int numChunks = 10;
long temporaryBlockAddress = mallocChunks(3);
free(temporaryBlockAddress);
mallocChunks(numChunks);
assertEquals("If the last chunk is free, it should be resized if necessary when a new chunk is requested",
numChunks + chunkCount, this.db.getChunkCount());
}
/**
* Tests that if there is a single large free block available, that block
* will be split and reused if necessary to satisfy a number of smaller
* requests.
*
* @throws Exception
*/
public void testLargeAllocationIsSplitAndReused() throws Exception {
long tempAddress = malloc(Database.getBytesThatFitInChunks(10));
// Use some space at the end of the database to prevent the allocator
// from using the end of the database, where stuff can be easily resized
mallocChunks(1);
free(tempAddress);
// Keep track of how much memory we are currently using, so we can
// ensure that any further allocations come from the freed block rather
// than the end of the database.
int chunkCount = this.db.getChunkCount();
long firstAllocation = mallocChunks(7);
assertEquals("The freed chunk should be reused (there should be 10 chunks available)", chunkCount,
this.db.getChunkCount());
long secondAllocation = mallocChunks(1);
assertEquals("The freed chunk should be reused (there should be 3 chunks available)", chunkCount,
this.db.getChunkCount());
long thirdAllocation = mallocChunks(2);
assertEquals("The freed chunk should be reused (there should be exactly 2 chunks available)", chunkCount,
this.db.getChunkCount());
assertTrue(
"Allocations should happen from the start of the database if it makes no difference to fragmentation",
secondAllocation > firstAllocation);
assertTrue("Free space should have been kept next to the largest block for as long as possible",
secondAllocation > thirdAllocation);
// Do another allocation when there are no free chunks
mallocChunks(1);
assertEquals("New chunks should be allocated when the database is out of free blocks", chunkCount + 1,
this.db.getChunkCount());
}
/**
* Verifies that if a block is freed and the previous block is also free,
* the two free blocks will be combined into a single larger block.
*/
public void testFreeBlockMergesWithPrevious() throws Exception {
long firstBlock = mallocChunks(1);
long secondBlock = mallocChunks(1);
mallocChunks(1);
free(firstBlock);
free(secondBlock);
int chunkCount = this.db.getChunkCount();
mallocChunks(2);
assertEquals("The merged block should have been used", chunkCount, this.db.getChunkCount());
}
/**
* Verifies that if a block is freed and the next block is also free, the
* two free blocks will be combined into a single larger block.
*/
public void testFreeBlockMergesWithNext() throws Exception {
long firstBlock = mallocChunks(1);
long secondBlock = mallocChunks(1);
mallocChunks(1);
free(secondBlock);
free(firstBlock);
int chunkCount = this.db.getChunkCount();
mallocChunks(2);
assertEquals("The merged block should have been used", chunkCount, this.db.getChunkCount());
}
/**
* Verifies that if a block is freed and the blocks on both sides are also
* free, the three free blocks will be combined into a single larger block.
*/
public void testFreeBlockMergesWithBothNextAndPrevious() throws Exception {
long firstBlock = mallocChunks(1);
long secondBlock = mallocChunks(1);
long thirdBlock = mallocChunks(1);
mallocChunks(1);
free(firstBlock);
free(thirdBlock);
free(secondBlock);
int chunkCount = this.db.getChunkCount();
mallocChunks(3);
assertEquals("The merged block should have been used", chunkCount, this.db.getChunkCount());
}
/**
* Tests removal of a chunk from the free space trie when there are
* duplicate free space nodes with the same size and the node being removed
* isn't the one with the embedded trie node.
*/
public void testRemoveFreeSpaceNodeFromDuplicateList() throws Exception {
long chunk1 = mallocChunks(1);
mallocChunks(1);
long chunk3 = mallocChunks(1);
long chunk4 = mallocChunks(1);
mallocChunks(1);
int chunkCount = this.db.getChunkCount();
free(chunk1);
free(chunk3);
// At this point chunks 1 and 3 should be in the same linked list. Chunk
// 1 contains the embedded trie.
free(chunk4);
// Should merge with chunk3, causing it to be removed from the list
// Verify that we can allocate the merged chunk 3+4
mallocChunks(2);
assertEquals("Chunks 3 and 4 should have been merged", chunkCount, this.db.getChunkCount());
}
/**
* Tests removal of a chunk from the free space trie when the node being
* removed was part of the embedded trie and it has a non-empty list of
* other nodes of the same size.
*/
public void testRemoveFreeSpaceNodeFromTrieWithDuplicates() throws Exception {
long chunk1 = mallocChunks(1);
mallocChunks(1);
long chunk3 = mallocChunks(1);
long chunk4 = mallocChunks(1);
mallocChunks(1);
int chunkCount = this.db.getChunkCount();
free(chunk3);
free(chunk1);
// At this point chunks 1 and 3 should be in the same linked list. Chunk
// 3 contains the embedded trie.
free(chunk4);
// Should merge with chunk3, causing it to be removed from the list
// Verify that we can allocate the merged chunk 3+4
mallocChunks(2);
assertEquals("Chunks 3 and 4 should have been merged", chunkCount, this.db.getChunkCount());
}
/**
* Tests reusing a chunk from the free space trie when it contains
* duplicates.
*/
public void testReuseDeallocatedChunksWithMultipleFreeSpaceNodesOfTheSameSize() throws Exception {
long chunk1 = mallocChunks(2);
mallocChunks(1);
long chunk3 = mallocChunks(2);
mallocChunks(1);
long chunk5 = mallocChunks(2);
mallocChunks(1);
int chunkCount = this.db.getChunkCount();
free(chunk1);
free(chunk3);
free(chunk5);
mallocChunks(2);
assertEquals("A chunk should have been reused", chunkCount, this.db.getChunkCount());
}
public void testEndOfFreeBlockIsUsedIfThePreviousBlockIsLargerThanTheNextBlock() throws Exception {
long prevChunk = mallocChunks(4);
long middleChunk = mallocChunks(4);
long nextChunk = mallocChunks(2);
free(middleChunk);
// This should be taken from the end of "middleChunk", since that's closer to the smaller neighbor
long smallChunk1 = mallocChunks(1);
// This should also be taken from the end of the remaining portion of "middleChunk"
long smallChunk2 = mallocChunks(1);
assertTrue("The small chunks should have been allocated from space after 'prevChunk'",
prevChunk < smallChunk2);
assertTrue("The small chunks should have been allocated from the end of the free block",
smallChunk2 < smallChunk1);
assertTrue("The small chunks should have been allocated from space before 'nextChunk'",
smallChunk1 < nextChunk);
}
/**
* Tests various corner cases in the trie map.
*/
public void testTriesOfVariousSize() throws Exception {
long chunk1 = mallocChunks(1);
mallocChunks(1);
long chunk2 = mallocChunks(2);
mallocChunks(1);
long chunk3 = mallocChunks(3);
mallocChunks(1);
long chunk4 = mallocChunks(5);
mallocChunks(1);
long chunk5 = mallocChunks(6);
mallocChunks(1);
long chunk6 = mallocChunks(6);
mallocChunks(1);
long chunk7 = mallocChunks(10);
mallocChunks(1);
long chunk8 = mallocChunks(20);
mallocChunks(1);
int chunkCount = this.db.getChunkCount();
free(chunk7);
free(chunk4);
free(chunk1);
free(chunk3);
free(chunk8);
free(chunk5);
free(chunk2);
free(chunk6);
mallocChunks(4);
mallocChunks(10);
assertEquals("A chunk should have been reused", chunkCount, this.db.getChunkCount());
}
/**
* Tests that if there are multiple free blocks of different sizes and of
* exactly one of the requested size, that one is always selected.
*/
public void testBestBlockIsAlwaysSelected() throws Exception {
int[] sizes = { 11, 2, 6, 1, 9, 10, 7, 8, 12, 20, 15, 3 };
long[] pointers = new long[sizes.length];
for (int idx = 0; idx < sizes.length; idx++) {
pointers[idx] = mallocChunks(sizes[idx]);
mallocChunks(1);
}
int chunkCount = this.db.getChunkCount();
for (int idx = 0; idx < pointers.length; idx++) {
free(pointers[idx]);
}
for (int idx = 0; idx < sizes.length; idx++) {
long nextPointer = mallocChunks(sizes[idx]);
assertEquals("Returned wrong pointer for malloc of " + sizes[idx] + " chunks", pointers[idx], nextPointer);
assertEquals("A chunk should have been reused", chunkCount, this.db.getChunkCount());
}
}
/**
* Tests removals from the large chunk sibling list. Ensures that such removals don't interfere with
* the trie itself.
*/
public void testUnlinkedBlockNotInTrieGetsRelinked() throws Exception {
long rootChunk = mallocChunks(1);
mallocChunks(1);
long unusedChunk = mallocChunks(1);
mallocChunks(1);
long chunkToBeMerged = mallocChunks(1);
long chunkThatWillCauseMerging = mallocChunks(1);
mallocChunks(1);
long followingChunkInFreeSpaceList = mallocChunks(1);
mallocChunks(1);
long chunkOfSize2 = mallocChunks(2);
mallocChunks(1);
int chunkCount = this.db.getChunkCount();
free(rootChunk);
free(unusedChunk);
free(chunkToBeMerged);
free(followingChunkInFreeSpaceList);
free(chunkOfSize2);
// At this point, the free space trie looks like this:
//
// size 1: rootChunk -> unusedChunk -> chunkToBeMerged -> followingChunkInFreeSpaceList
// -> size 2: chunkOfSize2
//
// By freeing chunkThatWillCauseMerging, chunkToBeMerged will be removed from the
// list of size 1 and inserted into the list of size 2. This test verifies that the
// code won't mess with the root pointer by inserting unusedChunk or chunk into the root
// This call will corrupt the database if we don't do the removal correctly
free(chunkThatWillCauseMerging);
// Now reallocate the original chunks to ensure they can all be found in the list
long firstDoubleChunk = mallocChunks(2);
long secondDoubleChunk = mallocChunks(2);
mallocChunks(1);
mallocChunks(1);
mallocChunks(1);
assertEquals("A chunk should have been reused", chunkCount, this.db.getChunkCount());
assertNotSame("Both new chunks should have been unique", firstDoubleChunk, secondDoubleChunk);
assertNotSame("The first chunk should not be null", 0, firstDoubleChunk);
assertNotSame("The second chunk should not be null", 0, secondDoubleChunk);
this.db.validateFreeSpace();
}
}