blob: b242f37d2bd5d65f8078b5230cd49b95b8e0f004 [file] [log] [blame]
// Copyright 2014 The Bazel Authors. All rights reserved.
//
// Licensed 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.google.devtools.build.lib.util;
import static com.google.common.truth.Truth.assertThat;
import com.google.common.base.Function;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.devtools.build.lib.testutil.TestUtils;
import java.util.List;
import java.util.SortedMap;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/**
* Test for the StringIndexer classes.
*/
public abstract class StringIndexerTest {
private static final int ATTEMPTS = 1000;
private SortedMap<Integer, String> mappings;
protected StringIndexer indexer;
private final Object lock = new Object();
@Before
public final void createIndexer() throws Exception {
indexer = newIndexer();
mappings = Maps.newTreeMap();
}
protected abstract StringIndexer newIndexer();
protected void assertSize(int expected) {
assertThat(indexer.size()).isEqualTo(expected);
}
protected void assertNoIndex(String s) {
int size = indexer.size();
assertThat(indexer.getIndex(s)).isEqualTo(-1);
assertThat(indexer.size()).isEqualTo(size);
}
protected void assertIndex(int expected, String s) {
// System.out.println("Adding " + s + ", expecting " + expected);
int index = indexer.getOrCreateIndex(s);
// System.out.println(csi);
assertThat(index).isEqualTo(expected);
mappings.put(expected, s);
}
protected void assertContent() {
for (int i = 0; i < indexer.size(); i++) {
assertThat(mappings.get(i)).isNotNull();
assertThat(mappings).containsEntry(i, indexer.getStringForIndex(i));
}
}
private void assertConcurrentUpdates(Function<Integer, String> keyGenerator) throws Exception {
final AtomicInteger safeIndex = new AtomicInteger(-1);
List<String> keys = Lists.newArrayListWithCapacity(ATTEMPTS);
ThreadPoolExecutor executor = new ThreadPoolExecutor(3, 3, 5, TimeUnit.SECONDS,
new ArrayBlockingQueue<Runnable>(ATTEMPTS));
synchronized(lock) {
for (int i = 0; i < ATTEMPTS; i++) {
final String key = keyGenerator.apply(i);
keys.add(key);
executor.execute(
() -> {
int index = indexer.getOrCreateIndex(key);
if (safeIndex.get() < index) {
safeIndex.set(index);
}
indexer.addString(key);
});
}
}
try {
while(!executor.getQueue().isEmpty()) {
// Validate that we can execute concurrent queries too.
if (safeIndex.get() >= 0) {
int index = safeIndex.get();
// Retrieve string using random existing index and validate reverse mapping.
String key = indexer.getStringForIndex(index);
assertThat(key).isNotNull();
assertThat(indexer.getIndex(key)).isEqualTo(index);
}
}
} finally {
executor.shutdown();
executor.awaitTermination(TestUtils.WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS);
}
for (String key : keys) {
// Validate mapping between keys and indices.
assertThat(indexer.getStringForIndex(indexer.getIndex(key))).isEqualTo(key);
}
}
@Test
public void concurrentAddChildNode() throws Exception {
assertConcurrentUpdates(from -> Strings.repeat("a", from + 1));
}
@Test
public void concurrentSplitNodeSuffix() throws Exception {
assertConcurrentUpdates(from -> Strings.repeat("b", ATTEMPTS - from));
}
@Test
public void concurrentAddBranch() throws Exception {
assertConcurrentUpdates(from -> String.format("%08o", from));
}
@RunWith(JUnit4.class)
public static class CompactStringIndexerTest extends StringIndexerTest {
@Override
protected StringIndexer newIndexer() {
return new CompactStringIndexer(1);
}
@Test
public void basicOperations() {
assertSize(0);
assertNoIndex("abcdef");
assertIndex(0, "abcdef"); // root node creation
assertIndex(0, "abcdef"); // root node match
assertSize(1);
assertIndex(2, "abddef"); // node branching, index 1 went to "ab" node.
assertSize(3);
assertIndex(1, "ab");
assertSize(3);
assertIndex(3, "abcdefghik"); // new leaf creation
assertSize(4);
assertIndex(4, "abcdefgh"); // node split
assertSize(5);
assertNoIndex("a");
assertNoIndex("abc");
assertNoIndex("abcdefg");
assertNoIndex("abcdefghil");
assertNoIndex("abcdefghikl");
assertContent();
indexer.clear();
assertSize(0);
assertThat(indexer.getStringForIndex(0)).isNull();
assertThat(indexer.getStringForIndex(1000)).isNull();
}
@Test
public void parentIndexUpdate() {
assertSize(0);
assertIndex(0, "abcdefghik"); // Create 3 nodes with single common parent "abcdefgh".
assertIndex(2, "abcdefghlm"); // Index 1 went to "abcdefgh".
assertIndex(3, "abcdefghxyz");
assertSize(4);
assertIndex(5, "abcdpqr"); // Split parent. Index 4 went to "abcd".
assertSize(6);
assertIndex(1, "abcdefgh"); // Check branch node indices.
assertIndex(4, "abcd");
assertSize(6);
assertContent();
}
@Test
public void emptyRootNode() {
assertSize(0);
assertIndex(0, "abc");
assertNoIndex("");
assertIndex(2, "def"); // root node key is now empty string and has index 1.
assertSize(3);
assertIndex(1, "");
assertSize(3);
assertContent();
}
protected void setupTestContent() {
assertSize(0);
assertIndex(0, "abcdefghi"); // Create leafs
assertIndex(2, "abcdefjkl");
assertIndex(3, "abcdefmno");
assertIndex(4, "abcdefjklpr");
assertIndex(6, "abcdstr");
assertIndex(8, "012345");
assertSize(9);
assertIndex(1, "abcdef"); // Validate inner nodes
assertIndex(5, "abcd");
assertIndex(7, "");
assertSize(9);
assertContent();
}
@Test
public void dumpContent() {
indexer = newIndexer();
indexer.addString("abc");
String content = indexer.toString();
assertThat(content).contains("size = 1");
assertThat(content).contains("contentSize = 5");
indexer = newIndexer();
setupTestContent();
content = indexer.toString();
assertThat(content).contains("size = 9");
assertThat(content).contains("contentSize = 60");
System.out.println(indexer);
}
@Test
public void addStringResult() {
assertSize(0);
assertThat(indexer.addString("abcdef")).isTrue();
assertThat(indexer.addString("abcdgh")).isTrue();
assertThat(indexer.addString("abcd")).isFalse();
assertThat(indexer.addString("ab")).isTrue();
}
}
@RunWith(JUnit4.class)
public static class CanonicalStringIndexerTest extends StringIndexerTest{
@Override
protected StringIndexer newIndexer() {
return new CanonicalStringIndexer(new ConcurrentHashMap<String, Integer>(),
new ConcurrentHashMap<Integer, String>());
}
@Test
public void basicOperations() {
assertSize(0);
assertNoIndex("abcdef");
assertIndex(0, "abcdef");
assertIndex(0, "abcdef");
assertSize(1);
assertIndex(1, "abddef");
assertSize(2);
assertIndex(2, "ab");
assertSize(3);
assertIndex(3, "abcdefghik");
assertSize(4);
assertIndex(4, "abcdefgh");
assertSize(5);
assertNoIndex("a");
assertNoIndex("abc");
assertNoIndex("abcdefg");
assertNoIndex("abcdefghil");
assertNoIndex("abcdefghikl");
assertContent();
indexer.clear();
assertSize(0);
assertThat(indexer.getStringForIndex(0)).isNull();
assertThat(indexer.getStringForIndex(1000)).isNull();
}
@Test
public void addStringResult() {
assertSize(0);
assertThat(indexer.addString("abcdef")).isTrue();
assertThat(indexer.addString("abcdgh")).isTrue();
assertThat(indexer.addString("abcd")).isTrue();
assertThat(indexer.addString("ab")).isTrue();
assertThat(indexer.addString("ab")).isFalse();
}
protected void setupTestContent() {
assertSize(0);
assertIndex(0, "abcdefghi");
assertIndex(1, "abcdefjkl");
assertIndex(2, "abcdefmno");
assertIndex(3, "abcdefjklpr");
assertIndex(4, "abcdstr");
assertIndex(5, "012345");
assertSize(6);
assertIndex(6, "abcdef");
assertIndex(7, "abcd");
assertIndex(8, "");
assertIndex(2, "abcdefmno");
assertSize(9);
assertContent();
}
}
}