Skip to main content
aboutsummaryrefslogtreecommitdiffstats
blob: 681d76208d876d9068b702f1c372e7d446a88613 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
/*******************************************************************************
 * Copyright (c) 2006, 2016 Symbian 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:
 *     Symbian - Initial implementation
 *     Markus Schorn (Wind River Systems)
 *******************************************************************************/
package org.eclipse.cdt.internal.pdom.tests;

import java.io.File;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import java.util.SortedSet;
import java.util.TreeSet;

import org.eclipse.cdt.core.testplugin.util.BaseTestCase;
import org.eclipse.cdt.internal.core.pdom.db.BTree;
import org.eclipse.cdt.internal.core.pdom.db.ChunkCache;
import org.eclipse.cdt.internal.core.pdom.db.Database;
import org.eclipse.cdt.internal.core.pdom.db.IBTreeComparator;
import org.eclipse.cdt.internal.core.pdom.db.IBTreeVisitor;
import org.eclipse.core.runtime.CoreException;

import junit.framework.Test;

/**
 * Test insertion/deletion of records of a mock record type in a B-tree.
 *
 * @author aferguso
 */
public class BTreeTests extends BaseTestCase {
	private static int DEBUG= 0;
	protected File dbFile;
	protected Database db;
	protected BTree btree;
	protected int rootRecord;
	protected IBTreeComparator comparator;

	public static Test suite() {
		return suite(BTreeTests.class);
	}

	// setUp is not used since we need to parameterize this method,
	// and invoke it multiple times per Junit test
	protected void init(int degree) throws Exception {
		dbFile = File.createTempFile("pdomtest", "db");
		db = new Database(dbFile, new ChunkCache(), 0, false);
		db.setExclusiveLock();
		rootRecord = Database.DATA_AREA;
		comparator = new BTMockRecordComparator();
		btree = new BTree(db, rootRecord, degree, comparator);
	}

	// tearDown is not used for the same reason as above
	protected void finish() throws Exception {
		db.close();
		dbFile.deleteOnExit();
	}


	public void testBySortedSetMirrorLite() throws Exception {
		sortedMirrorTest(8);
	}

	/**
	 * Test random (but reproducible via known seed) sequences of insertions/deletions
	 * and use TreeSet as a reference implementation to check behaviour against.
	 * @throws Exception
	 */
	protected void sortedMirrorTest(int noTrials) throws Exception {
		Random seeder = new Random(90210);

		for (int i = 0; i < noTrials; i++) {
			int seed = seeder.nextInt();
			if (DEBUG > 0)
				System.out.println("Iteration #" + i);
			trial(seed, false);
		}
	}

	/**
	 * Test random (but reproducible via known seed) sequence of insertions
	 * and use TreeSet as a reference implementation to check behaviour against.
	 * @throws Exception
	 */
	public void testInsertion() throws Exception {
		Random seeder = new Random();

		for (int i = 0; i < 6; i++) {
			int seed = seeder.nextInt();
			if (DEBUG > 0)
				System.out.println("Iteration #" + i);
			trialImp(seed, false, new Random(seed * 2), 1);
		}
	}

	/**
	 * Bug 402177: BTree.insert should return the matching record if the new record was not inserted.
	 */
	public void testEquivalentRecordInsert_Bug402177() throws Exception {
		init(8);
		try {
			BTMockRecord value1 = new BTMockRecord(db, 42);
			BTMockRecord value2 = new BTMockRecord(db, 42);

			long insert1 = btree.insert(value1.getRecord());
			long insert2 = btree.insert(value2.getRecord());
			assertEquals(insert1, insert2);
		} finally {
			finish();
		}
	}

	/**
	 * Insert/Delete a random number of records into/from the B-tree
	 * @param seed the seed for obtaining the deterministic random testing
	 * @param checkCorrectnessEachIteration if true, then on every single insertion/deletion check that the B-tree invariants
	 * still hold
	 * @throws Exception
	 */
	protected void trial(int seed, final boolean checkCorrectnessEachIteration) throws Exception {
		Random random = new Random(seed);

		// the probabilty that a particular iterations action will be an insertion
		double pInsert = Math.min(0.5 + random.nextDouble(), 1);

		trialImp(seed, checkCorrectnessEachIteration, random, pInsert);
	}

	private void trialImp(int seed, final boolean checkCorrectnessEachIteration, Random random,
			double pInsert) throws Exception {
		final int degree = 2 + random.nextInt(11);
		final int nIterations = random.nextInt(100000);
		final SortedSet expected = new TreeSet();
		final List history = new ArrayList();

		init(degree);

		if (DEBUG > 0)
			System.out.print("\t " + seed + " " + (nIterations/1000) + "K: ");
		for (int i = 0; i < nIterations; i++) {
			if (random.nextDouble() < pInsert) {
				Integer value = random.nextInt(Integer.MAX_VALUE);
				boolean newEntry = expected.add(value);
				if (newEntry) {
					BTMockRecord btValue = new BTMockRecord(db, value.intValue());
					history.add(btValue);
					if (DEBUG > 1)
						System.out.println("Add: " + value + " @ " + btValue.record);
					btree.insert(btValue.getRecord());
				}
			} else {
				if (!history.isEmpty()) {
					int index = random.nextInt(history.size());
					BTMockRecord btValue = (BTMockRecord) history.get(index);
					history.remove(index);
					expected.remove(Integer.valueOf(btValue.intValue()));
					if (DEBUG > 1)
						System.out.println("Remove: " + btValue.intValue() + " @ " + btValue.record);
					btree.delete(btValue.getRecord());
				}
			}
			if (i % 1000 == 0 && DEBUG > 0) {
				System.out.print(".");
			}
			if (checkCorrectnessEachIteration) {
				assertBTreeMatchesSortedSet("[iteration " + i + "] ", btree, expected);
				assertBTreeInvariantsHold("[iteration " + i + "] ");
			}
		}
		if (DEBUG > 0)
			System.out.println();

		assertBTreeMatchesSortedSet("[Trial end] ", btree, expected);
		assertBTreeInvariantsHold("[Trial end]");

		finish();
	}

	public void assertBTreeInvariantsHold(String msg) throws CoreException {
		String errorReport = btree.getInvariantsErrorReport();
		if (!errorReport.equals("")) {
			fail("Invariants do not hold: " + errorReport);
		}
	}

	public void assertBTreeMatchesSortedSet(final String msg, BTree actual, SortedSet expected) throws CoreException {
		final Iterator i = expected.iterator();
		btree.accept(new IBTreeVisitor() {
			int k;
			@Override
			public int compare(long record) throws CoreException {
				return 0;
			}

			@Override
			public boolean visit(long record) throws CoreException {
				if (record != 0) {
					BTMockRecord btValue = new BTMockRecord(record, db);
					if (i.hasNext()) {
						Integer exp = ((Integer)i.next());
						assertEquals(msg + " Differ at index: " + k, btValue.intValue(), exp.intValue());
						k++;
					} else {
						fail("Sizes different");
						return false;
					}
				}
				return true;
			}
		});
	}

	private static class BTMockRecord {
		public static final int VALUE_PTR = 0;
		public static final int RECORD_SIZE = Database.INT_SIZE;
		long record;
		Database db;

		/**
		 * Make a new record
		 */
		public BTMockRecord(Database db, int value) throws CoreException {
			this.db = db;
			record = db.malloc(BTMockRecord.RECORD_SIZE);
			db.putInt(record + VALUE_PTR, value);
		}

		/**
		 * Get an existing record
		 */
		public BTMockRecord(long record, Database db) {
			this.db = db;
			this.record = record;
		}

		public int intValue() throws CoreException {
			return db.getInt(record);
		}

		public long getRecord() {
			return record;
		}
	}

	private class BTMockRecordComparator implements IBTreeComparator {
		@Override
		public int compare(long record1, long record2) throws CoreException {
			return db.getInt(record1) - db.getInt(record2);
		}
	}
}

Back to the top