/*
* 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.
*
* Copyright 2013 Near Infinity Corporation.
*/
package integrationtests;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static org.hamcrest.core.IsEqual.equalTo;
import static org.hamcrest.core.IsNot.not;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.google.common.primitives.Longs;
import com.nearinfinity.honeycomb.mysql.HandlerProxy;
import com.nearinfinity.honeycomb.mysql.HandlerProxyFactory;
import com.nearinfinity.honeycomb.mysql.QueryKey;
import com.nearinfinity.honeycomb.mysql.Row;
import com.nearinfinity.honeycomb.mysql.gen.QueryType;
import com.nearinfinity.honeycomb.mysql.schema.IndexSchema;
import com.nearinfinity.honeycomb.mysql.schema.TableSchema;
import com.nearinfinity.honeycomb.util.Verify;
/**
* Integration Test utility methods
*/
public class ITUtils {
/**
* Encodes the provided value as a {@link ByteBuffer}
*
* @param value The value to encode
* @return The buffer representing the provided value
*/
public static ByteBuffer encodeValue(final long value) {
return ByteBuffer.wrap(Longs.toByteArray(value));
}
/**
* Asserts that the rows returned from a table scan performed by the proxy are different
*
* @param proxy The proxy to use for invoking a scan, not null
* @param rowCount The valid number of rows expected to be returned from the scan
*/
public static void assertReceivingDifferentRows(final HandlerProxy proxy,
final int rowCount) {
checkNotNull(proxy);
verifyRowCount(rowCount);
proxy.startTableScan();
assertDifferentRows(proxy, rowCount);
proxy.endScan();
}
/**
* Asserts that the rows returned from an index scan performed by the proxy are different
*
* @param proxy The proxy to use for invoking a scan, not null
* @param key The index used to run an index scan, not null
* @param rowCount The valid number of rows expected to be returned from the scan
*/
public static void assertReceivingDifferentRows(final HandlerProxy proxy,
final QueryKey key,
final int rowCount) {
checkNotNull(proxy);
checkNotNull(key);
verifyRowCount(rowCount);
proxy.startIndexScan(key.serialize());
assertDifferentRows(proxy, rowCount);
proxy.endScan();
}
/**
* Inserts the number of specified rows with a random {@link UUID}
*
* @param proxy
* @param rowCount
* @param keyColumnValue
* @see #insertData(HandlerProxy, int, long, UUID)
*/
public static void insertData(final HandlerProxy proxy, final int rowCount, final long keyColumnValue) {
insertData(proxy, rowCount, keyColumnValue, UUID.randomUUID());
}
/**
* Inserts the number of specified rows with each row containing a column,
* {@link TestConstants#COLUMN1}, with the value provided and another column,
* {@link TestConstants#COLUMN2}, with an arbitrary value
*
* @param proxy The proxy to use for invoking a scan, not null
* @param rowCount The valid number of rows expected to be returned from the scan
* @param keyColumnValue The value stored in the first column, which is intended to be indexed
* @param uuid The unique identifier to associate with each row inserted, not null
*/
public static void insertData(final HandlerProxy proxy, final int rowCount,
final long keyColumnValue, final UUID uuid) {
checkNotNull(proxy);
verifyRowCount(rowCount);
checkNotNull(uuid);
final Map<String, ByteBuffer> map = Maps.newHashMap();
map.put(TestConstants.COLUMN1, encodeValue(keyColumnValue));
for (int x = 0; x < rowCount; x++) {
map.put(TestConstants.COLUMN2, encodeValue(x));
final Row row = new Row(map, uuid);
proxy.insertRow(row.serialize());
}
proxy.flush();
}
/**
* Inserts the number of specified rows with each row containing the column,
* {@link TestConstants#COLUMN2}, with an arbitrary value and random {@link UUID}.
*
* @param proxy
* @param rows
* @see #insertNullData(HandlerProxy, int, String)
*/
public static void insertNullData(HandlerProxy proxy, int rows) {
insertNullData(proxy, rows, TestConstants.COLUMN2);
}
/**
* Inserts the number of specified rows with each row containing the provided
* column name with an arbitrary value and random {@link UUID}. The rows created
* do not use columns used for indexing such as {@link TestConstants#COLUMN1} and
* {@link TestConstants#COLUMN2}
*
* @param proxy The proxy to use for invoking a scan, not null
* @param rowCount The valid number of rows expected to be returned from the scan
* @param columnName The column name to associated with the inserted row, not null or empty
*/
public static void insertNullData(final HandlerProxy proxy, final int rowCount, final String columnName) {
checkNotNull(proxy);
verifyRowCount(rowCount);
Verify.isNotNullOrEmpty(columnName);
final Map<String, ByteBuffer> map = Maps.newHashMap();
for (int x = 0; x < rowCount; x++) {
map.put(columnName, encodeValue(x));
final Row row = new Row(map, UUID.randomUUID());
proxy.insertRow(row.serialize());
}
proxy.flush();
}
/**
* Creates an index on {@link TestConstants#COLUMN1} for the provided value
* with the index name of {@link TestConstants#INDEX1}
*
* @param keyValue The value stored in the indexed column
* @param queryType The query type that this index will be used for
* @return The constructed index with the provided details
*/
public static QueryKey createKey(final int keyValue, final QueryType queryType) {
HashMap<String, ByteBuffer> keys = Maps.newHashMap();
keys.put(TestConstants.COLUMN1, encodeValue(keyValue));
return new QueryKey(TestConstants.INDEX1, queryType, keys);
}
public static Row createRow(final int columnValue) {
final Map<String, ByteBuffer> map = Maps.newHashMap();
map.put(TestConstants.COLUMN1, encodeValue(columnValue));
return new Row(map, UUID.randomUUID());
}
private static void assertDifferentRows(final HandlerProxy proxy, final int rowCount) {
byte[] previous = null;
for (int x = 0; x < rowCount; x++) {
final byte[] current = proxy.getNextRow();
assertNotNull(current);
assertThat(current, not(equalTo(previous)));
previous = current;
}
assertNull(proxy.getNextRow());
}
private static void verifyRowCount(final long rowCount) {
checkArgument(rowCount >= 0, "The provided row count is invalid");
}
/**
* Check that the table open on the proxy has the expected number of data rows
* and index rows on each index (checks both ascending and descending
* directions). Note: this could be very slow for big tables.
* @param proxy HandlerProxy with table already open
* @param schema TableSchema of open table
* @param expectedRowCount Expected number of rows
*/
public static void assertRowCount(final HandlerProxy proxy,
final TableSchema schema,
final long expectedRowCount) {
checkState(proxy.getTableName() != null, "Proxy must have an open table.");
checkNotNull(schema);
verifyRowCount(expectedRowCount);
proxy.startTableScan();
assertResultCount(proxy, expectedRowCount);
proxy.endScan();
QueryKey queryKey;
for(IndexSchema indexSchema : schema.getIndices()) {
queryKey = new QueryKey(indexSchema.getIndexName(),
QueryType.INDEX_FIRST, ImmutableMap.<String, ByteBuffer>of());
proxy.startIndexScan(queryKey.serialize());
assertResultCount(proxy, expectedRowCount);
proxy.endScan();
queryKey = new QueryKey(indexSchema.getIndexName(),
QueryType.INDEX_LAST, ImmutableMap.<String, ByteBuffer>of());
proxy.startIndexScan(queryKey.serialize());
assertResultCount(proxy, expectedRowCount);
proxy.endScan();
}
}
/**
* Checks that the open scan on the proxy has the expected number of results.
* @param proxy
* @param expectedResultCount
*/
private static void assertResultCount(final HandlerProxy proxy,
final long expectedResultCount) {
long actualResultCount = 0;
while(proxy.getNextRow() != null) {
actualResultCount++;
}
assertEquals(expectedResultCount, actualResultCount);
}
/**
* Start concurrency number of actions concurrently and at the same time. This
* method ensures that the actions are started at the same time, and they are
* not being blocked in a thread pool. Obviously, this uses concurrency
* number of threads simultaneously so use caution as thread starvation
* deadlock can occur with high concurrency levels.
*
* @param concurrency Number of handler proxies to run the action against, concurrently
* @param setup Action to be run on each proxy before starting concurrent actions
* @param action Action to be run concurrently and in sync with other proxies
* @param cleanup Action to be run to cleanup proxy
* @param factory HandlerProxyFactory to supply proxies to be run concurrently
* @throws InterruptedException
*/
public static void startProxyActionConcurrently(int concurrency,
final ProxyRunnable setup,
final ProxyRunnable action,
final ProxyRunnable cleanup,
final HandlerProxyFactory factory)
throws InterruptedException{
final ExecutorService executor = Executors.newCachedThreadPool();
final CountDownLatch ready = new CountDownLatch(concurrency);
final CountDownLatch start = new CountDownLatch(1);
final CountDownLatch done = new CountDownLatch(concurrency);
for (int i = 0; i < concurrency; i++) {
executor.execute(new Runnable() {
@Override
public void run() {
HandlerProxy proxy = factory.createHandlerProxy();
setup.run(proxy);
ready.countDown();
try {
start.await();
action.run(proxy);
} catch (InterruptedException e) {
System.out.println("Interuppted");
Thread.currentThread().interrupt();
}
cleanup.run(proxy);
done.countDown();
}
});
}
ready.await();
start.countDown();
done.await();
}
public static ProxyRunnable openTable = new ITUtils.ProxyRunnable() {
@Override
public void run(HandlerProxy proxy) {
proxy.openTable(TestConstants.TABLE_NAME);
}
};
public static ITUtils.ProxyRunnable closeTable = new ITUtils.ProxyRunnable() {
@Override
public void run(HandlerProxy proxy) {
proxy.closeTable();
}
};
/**
* Basically Runnable, but run takes a HandlerProxy which the action
* may use.
*/
public interface ProxyRunnable {
public void run(HandlerProxy proxy);
}
}