/*
* Copyright 2016 higherfrequencytrading.com
*
* 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 net.openhft.chronicle.engine.map;
import net.openhft.chronicle.engine.ThreadMonitoringTest;
import net.openhft.chronicle.engine.api.map.MapView;
import net.openhft.chronicle.engine.api.tree.AssetTree;
import net.openhft.chronicle.engine.api.tree.Assetted;
import net.openhft.chronicle.engine.map.remote.RemoteKeyValueStore;
import net.openhft.chronicle.engine.map.remote.RemoteMapView;
import net.openhft.chronicle.engine.server.ServerEndpoint;
import net.openhft.chronicle.engine.tree.VanillaAssetTree;
import net.openhft.chronicle.network.TCPRegistry;
import net.openhft.chronicle.network.connection.TcpChannelHub;
import net.openhft.chronicle.wire.WireType;
import net.openhft.chronicle.wire.YamlLogging;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import java.io.Closeable;
import java.io.IOException;
import java.util.*;
import java.util.Map.Entry;
import java.util.function.Consumer;
import java.util.function.Supplier;
import static net.openhft.chronicle.core.io.Closeable.closeQuietly;
import static net.openhft.chronicle.engine.Utils.yamlLoggger;
import static org.junit.Assert.*;
/**
* test using the listener both remotely or locally via the engine
*
* @author Rob Austin.
*/
@RunWith(value = Parameterized.class)
public class MapClientTest extends ThreadMonitoringTest {
public static final WireType WIRE_TYPE = WireType.TEXT;
private static int i;
// server has it's own asset tree, to the client.
@NotNull
private final VanillaAssetTree assetTree = new VanillaAssetTree();
@Nullable
private Class<? extends CloseableSupplier> supplier = null;
public MapClientTest(Class<? extends CloseableSupplier> supplier) {
this.supplier = supplier;
}
@Parameters
public static Collection<Object[]> data() {
return Arrays.asList(new Class[][]{
{LocalMapSupplier.class},
{RemoteMapSupplier.class}
});
}
@Before
public void clearState() {
if (supplier == LocalMapSupplier.class)
assetTree.forTesting();
TcpChannelHub.closeAllHubs();
TCPRegistry.reset();
YamlLogging.setAll(false);
}
@Test(timeout = 50000)
public void testPutAndGet() throws IOException, InterruptedException {
yamlLoggger(() -> {
try {
supplyMap(Integer.class, String.class, mapProxy -> {
mapProxy.put(1, "hello");
assertEquals("hello", mapProxy.get(1));
assertEquals(1, mapProxy.size());
assertEquals("{1=hello}", mapProxy.toString());
});
} catch (IOException e) {
e.printStackTrace();
}
});
}
/* @Test(timeout = 50000)
public void testSubscriptionTest() throws IOException, InterruptedException {
yamlLoggger(() -> {
try {
supplyMap(Integer.class, String.class, map -> {
try {
supplyMapEventListener(Integer.class, String.class, mapEventListener -> {
Chassis.registerSubscriber("test", MapEvent.class, e -> e.apply(mapEventListener));
map.put(i, "one");
});
} catch (IOException e) {
Jvm.rethrow(e);
}
});
} catch (IOException e) {
e.printStackTrace();
}
});
}*/
@Test(timeout = 50000)
public void testEntrySetIsEmpty() throws IOException, InterruptedException {
supplyMap(Integer.class, String.class, mapProxy -> {
long size = mapProxy.longSize();
boolean empty = mapProxy.isEmpty();
assertTrue(empty);
assertEquals(0, size);
});
}
@Test
public void testPutAll() throws IOException, InterruptedException {
supplyMap(Integer.class, String.class, mapProxy -> yamlLoggger(() -> {
@NotNull final Set<Entry<Integer, String>> entries = mapProxy.entrySet();
assertEquals(0, entries.size());
assertEquals(true, entries.isEmpty());
@NotNull Map<Integer, String> data = new HashMap<>();
data.put(1, "hello");
data.put(2, "world");
mapProxy.putAll(data);
@NotNull final Set<Entry<Integer, String>> e = mapProxy.entrySet();
@NotNull final Iterator<Entry<Integer, String>> iterator = e.iterator();
Entry<Integer, String> entry = iterator.next();
if (entry.getKey() == 1) {
assertEquals("hello", entry.getValue());
entry = iterator.next();
assertEquals("world", entry.getValue());
} else if (entry.getKey() == 2) {
assertEquals("world", entry.getValue());
entry = iterator.next();
assertEquals("hello", entry.getValue());
}
assertEquals(2, mapProxy.size());
}));
}
@Test
public void testMapsAsValues() throws IOException, InterruptedException {
supplyMap(Integer.class, Map.class, map -> {
{
@NotNull final Map value = new HashMap<String, String>();
value.put("k1", "v1");
value.put("k2", "v2");
map.put(1, value);
}
{
@NotNull final Map value = new HashMap<String, String>();
value.put("k3", "v3");
value.put("k4", "v4");
map.put(2, value);
}
assertEquals("v1", map.get(1).get("k1"));
assertEquals("v2", map.get(1).get("k2"));
assertEquals(null, map.get(1).get("k3"));
assertEquals(null, map.get(1).get("k4"));
assertEquals("v3", map.get(2).get("k3"));
assertEquals("v4", map.get(2).get("k4"));
assertEquals(2, map.size());
});
}
@Test
public void testValuesCollection() throws IOException, InterruptedException {
@NotNull HashMap<String, String> data = new HashMap<>();
data.put("test1", "value1");
data.put("test1", "value1");
supplyMap(String.class, String.class, mapProxy -> {
mapProxy.putAll(data);
assertEquals(data.size(), mapProxy.size());
assertEquals(data.size(), mapProxy.values().size());
@NotNull Iterator<String> it = mapProxy.values().iterator();
@NotNull ArrayList<String> values = new ArrayList<>();
while (it.hasNext()) {
values.add(it.next());
}
Collections.sort(values);
@NotNull Object[] dataValues = data.values().toArray();
Arrays.sort(dataValues);
assertArrayEquals(dataValues, values.toArray());
});
}
@Test
public void testDoubleValues() throws IOException, InterruptedException {
supplyMap(Double.class, Double.class, mapProxy -> {
mapProxy.put(1.0, 1.0);
mapProxy.put(2.0, 2.0);
mapProxy.put(3.0, 0.0);
assertEquals(1.0, mapProxy.get(1.0), 0);
assertEquals(2.0, mapProxy.get(2.0), 0);
assertEquals(0.0, mapProxy.get(3.0), 0);
assertEquals(3, mapProxy.size());
});
}
@Test
public void testFloatValues() throws IOException, InterruptedException {
supplyMap(Float.class, Float.class, mapProxy -> {
mapProxy.put(1.0f, 1.0f);
mapProxy.put(2.0f, 2.0f);
mapProxy.put(3.0f, 0.0f);
assertEquals(1.0f, mapProxy.get(1.0f), 0);
assertEquals(2.0f, mapProxy.get(2.0f), 0);
assertEquals(0.0f, mapProxy.get(3.0f), 0);
assertEquals(3, mapProxy.size());
});
}
@org.junit.Ignore("Will be very slow, of course")
@Test
public void testLargeUpdates() throws IOException, InterruptedException {
String val = new String(new char[1024 * 1024]).replace("\0", "X");
supplyMap(String.class, String.class, mapProxy -> {
for (int j = 0; j < 30 * 1000; j++) {
mapProxy.put("key", val);
}
});
}
@Test
public void testStringString() throws IOException, InterruptedException {
supplyMap(String.class, String.class, mapProxy -> {
mapProxy.put("hello", "world");
assertEquals("world", mapProxy.get("hello"));
assertEquals(1, mapProxy.size());
});
}
@Test
public void testApplyTo() throws IOException, InterruptedException {
supplyMap(String.class, String.class, mapProxy -> {
mapProxy.asyncUpdate(m -> m.put("hello", "world"));
assertEquals("world", mapProxy.applyTo(m -> m.get("hello")));
assertEquals("world2", mapProxy.syncUpdate(m -> m.put("hello", "world2"), m -> m.get("hello")));
assertEquals(1, mapProxy.size());
});
}
@Test
public void testToString() throws IOException, InterruptedException {
supplyMap(Integer.class, String.class, mapProxy -> {
mapProxy.put(1, "Hello");
assertEquals("Hello", mapProxy.get(1));
assertEquals("{1=Hello}", mapProxy.toString());
mapProxy.remove(1);
mapProxy.put(2, "World");
assertEquals("{2=World}", mapProxy.toString());
});
}
/**
* supplies a listener and closes it once the tests are finished
*/
private <K, V>
void supplyMap(@NotNull Class<K> kClass, @NotNull Class<V> vClass, @NotNull Consumer<MapView<K, V>> c)
throws IOException {
CloseableSupplier<MapView<K, V>> result;
if (LocalMapSupplier.class.equals(supplier)) {
result = new LocalMapSupplier<>(kClass, vClass, assetTree);
} else if (RemoteMapSupplier.class.equals(supplier)) {
result = new RemoteMapSupplier<>("test", kClass, vClass, WireType.TEXT, assetTree, "test");
assertTrue(result.get() instanceof RemoteMapView);
} else {
throw new IllegalStateException("unsuported type");
}
try {
c.accept(result.get());
} catch (Exception e) {
e.printStackTrace();
} finally {
result.close();
}
}
private interface CloseableSupplier<X> extends Closeable, Supplier<X> {
}
public static class RemoteMapSupplier<K, V> implements CloseableSupplier<MapView<K, V>> {
@NotNull
private final ServerEndpoint serverEndpoint;
@NotNull
private final MapView<K, V> map;
@NotNull
private final AssetTree serverAssetTree;
@NotNull
private final AssetTree clientAssetTree;
public RemoteMapSupplier(
@NotNull String hostPortDescription,
@NotNull final Class<K> kClass,
@NotNull final Class<V> vClass,
@NotNull final WireType wireType,
@NotNull final AssetTree clientAssetTree,
@NotNull final String name) throws IOException {
this.clientAssetTree = clientAssetTree;
this.serverAssetTree = new VanillaAssetTree().forTesting(false);
TCPRegistry.createServerSocketChannelFor(hostPortDescription);
serverEndpoint = new ServerEndpoint(hostPortDescription, serverAssetTree);
((VanillaAssetTree) clientAssetTree).forRemoteAccess(hostPortDescription, wireType);
map = clientAssetTree.acquireMap(name, kClass, vClass);
if (!(((Assetted) map).underlying() instanceof RemoteKeyValueStore)) {
throw new IllegalStateException();
}
}
@Override
public void close() throws IOException {
closeQuietly(map);
clientAssetTree.close();
serverEndpoint.close();
serverAssetTree.close();
TcpChannelHub.closeAllHubs();
TCPRegistry.reset();
}
@NotNull
@Override
public MapView<K, V> get() {
return map;
}
}
public static class LocalMapSupplier<K, V> implements CloseableSupplier<MapView<K, V>> {
@NotNull
private final MapView<K, V> map;
public LocalMapSupplier(Class<K> kClass, Class<V> vClass, @NotNull AssetTree assetTree) {
map = assetTree.acquireMap("test" + i++ + "?putReturnsNull=false&removeReturnsNull=false", kClass, vClass);
}
@Override
public void close() throws IOException {
if (map instanceof Closeable)
((Closeable) map).close();
}
@NotNull
@Override
public MapView<K, V> get() {
return map;
}
}
}