blob: 19dff888e73255ed376755b2659e7999ccbfccc7 [file] [log] [blame]
/*
* 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.
*/
package org.apache.ignite.testframework.junits;
import java.io.Externalizable;
import java.io.File;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import org.apache.commons.io.FileUtils;
import org.apache.ignite.Ignition;
import org.apache.ignite.binary.BinaryObjectException;
import org.apache.ignite.binary.BinaryReader;
import org.apache.ignite.binary.BinaryWriter;
import org.apache.ignite.binary.Binarylizable;
import org.apache.ignite.configuration.IgniteConfiguration;
import org.apache.ignite.internal.IgniteEx;
import org.apache.ignite.internal.util.typedef.F;
import org.apache.ignite.internal.util.typedef.internal.U;
import org.apache.ignite.marshaller.jdk.JdkMarshaller;
import org.apache.ignite.testframework.configvariations.VariationsTestsConfig;
import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
import org.junit.runners.model.Statement;
/**
* Common abstract test for Ignite tests based on configurations variations.
*/
public abstract class IgniteConfigVariationsAbstractTest extends GridCommonAbstractTest {
/** */
protected static final int SERVER_NODE_IDX = 0;
/** */
protected static final int CLIENT_NODE_IDX = 1;
/** */
protected int testedNodeIdx;
/** */
private static final File workDir = new File(U.getIgniteHome() + File.separator + "workOfConfigVariationsTests");
/** Dummy initial stub to just let people launch test classes not from suite. */
protected VariationsTestsConfig testsCfg = new VariationsTestsConfig(null, "Dummy config", false, null, 1, false);
/** */
protected volatile DataMode dataMode = DataMode.PLANE_OBJECT;
/** See {@link IgniteConfigVariationsAbstractTest#injectTestsConfiguration} */
private static VariationsTestsConfig testsCfgInjected;
/** {@inheritDoc} */
@Override public String getTestIgniteInstanceName(int idx) {
return getTestIgniteInstanceName() + idx;
}
/** {@inheritDoc} */
@Override public String getTestIgniteInstanceName() {
return "testGrid";
}
/**
* Invoked by reflection from {@code ConfigVariationsTestSuiteBuilder}.
*
* @param testsCfgInjected Tests configuration.
*/
@SuppressWarnings("unused")
protected static void injectTestsConfiguration(VariationsTestsConfig testsCfgInjected) {
IgniteConfigVariationsAbstractTest.testsCfgInjected = testsCfgInjected;
}
/**
* {@inheritDoc}
* <p>
* IMPL NOTE when this override was introduced, alternative was to replace multiple usages of instance member
* {@code testsCfg} splattered all over the project with those of static one {@code testsCfgInjected} - kind
* of cumbersome, risky and potentially redundant change given the chance of later migration to JUnit 5 and
* further rework to use dynamic test parameters that would likely cause removal of the static member.</p>
*/
@Override protected void runTestCase(Statement testRoutine) throws Throwable {
testsCfg = testsCfgInjected;
super.runTestCase(testRoutine);
}
/** {@inheritDoc} */
@Override protected boolean isSafeTopology() {
return false;
}
/** {@inheritDoc} */
@Override protected void beforeTestsStarted() throws Exception {
assert testsCfg != null;
if (Ignition.allGrids().size() != testsCfg.gridCount()) {
info("All nodes will be stopped, new " + testsCfg.gridCount() + " nodes will be started.");
Ignition.stopAll(true);
FileUtils.deleteDirectory(workDir);
info("Ignite's 'work' directory has been cleaned.");
startGrids(testsCfg.gridCount());
for (int i = 0; i < testsCfg.gridCount(); i++)
info("Grid " + i + ": " + grid(i).localNode().id());
}
assert testsCfg.testedNodeIndex() >= 0 : "testedNodeIdx: " + testedNodeIdx;
testedNodeIdx = testsCfg.testedNodeIndex();
if (testsCfg.withClients()) {
for (int i = 0; i < gridCount(); i++)
assertEquals("i: " + i, expectedClient(getTestIgniteInstanceName(i)),
(boolean)grid(i).configuration().isClientMode());
}
}
/**
* @param testGridName Name.
* @return {@code True} if node is client should be client.
*/
protected boolean expectedClient(String testGridName) {
return getTestIgniteInstanceName(CLIENT_NODE_IDX).equals(testGridName);
}
/** {@inheritDoc} */
@Override protected void afterTestsStopped() throws Exception {
if (testsCfg.isStopNodes()) {
info("Stopping all grids...");
stopAllGrids();
FileUtils.deleteDirectory(workDir);
info("Ignite's 'work' directory has been cleaned.");
memoryUsage();
System.gc();
memoryUsage();
}
}
/**
* Prints memory usage.
*/
private void memoryUsage() {
int mb = 1024 * 1024;
Runtime runtime = Runtime.getRuntime();
info("##### Heap utilization statistics [MB] #####");
info("Used Memory (mb): " + (runtime.totalMemory() - runtime.freeMemory()) / mb);
info("Free Memory (mb): " + runtime.freeMemory() / mb);
info("Total Memory (mb): " + runtime.totalMemory() / mb);
info("Max Memory (mb): " + runtime.maxMemory() / mb);
}
/** {@inheritDoc} */
@Override protected String testClassDescription() {
assert testsCfg != null: "Tests should be run using test suite.";
return super.testClassDescription() + '-' + testsCfg.description() + '-' + testsCfg.gridCount() + "-node(s)";
}
/** {@inheritDoc} */
@Override protected String testDescription() {
assert testsCfg != null: "Tests should be run using test suite.";
return super.testDescription() + '-' + testsCfg.description() + '-' + testsCfg.gridCount() + "-node(s)";
}
/** {@inheritDoc} */
@Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception {
IgniteConfiguration cfg = super.getConfiguration(igniteInstanceName);
IgniteConfiguration resCfg = testsCfg.configurationFactory().getConfiguration(igniteInstanceName, cfg);
resCfg.setWorkDirectory(workDir.getAbsolutePath());
if (testsCfg.withClients())
resCfg.setClientMode(expectedClient(igniteInstanceName));
return resCfg;
}
/** {@inheritDoc} */
protected final int gridCount() {
return testsCfg.gridCount();
}
/**
* @return Count of clients.
*/
protected int clientsCount() {
int cnt = 0;
for (int i = 0; i < gridCount(); i++) {
if (grid(i).configuration().isClientMode())
cnt++;
}
return cnt;
}
/** {@inheritDoc} */
@Override protected IgniteEx grid() {
throw new UnsupportedOperationException("Not supported, grid(int idx) or testedGrid() should be used instead.");
}
/**
* @return Grid which should be tested.
*/
protected IgniteEx testedGrid() {
return grid(testedNodeIdx);
}
/**
* @return Tested grid in client mode or not.
*/
protected boolean isClientMode() {
return grid(testedNodeIdx).configuration().isClientMode();
}
/**
* @return Count of server nodes at topology.
*/
protected int serversGridCount() {
int cnt = 0;
for (int i = 0; i < gridCount(); i++) {
if (!grid(i).configuration().isClientMode())
cnt++;
}
return cnt;
}
/**
* Runs in all data modes.
*
* @throws Exception If failed.
*/
protected void runInAllDataModes(TestRunnable call, DataMode... dataModes) throws Exception {
if (F.isEmpty(dataModes))
dataModes = DataMode.values();
for (int i = 0; i < dataModes.length; i++) {
dataMode = dataModes[i];
if (!isCompatible()) {
info("Skipping test in data mode: " + dataMode);
continue;
}
info("Running test in data mode: " + dataMode);
if (i != 0)
beforeTest();
try {
call.run();
}
catch (Throwable e) {
e.printStackTrace();
throw e;
}
finally {
if (i + 1 != DataMode.values().length)
afterTest();
}
}
}
/**
* @param keyId Key Id.
* @return Key.
* @see #valueOf(Object)
*/
public Object key(int keyId) {
return key(keyId, dataMode);
}
/**
* @param valId Key Id.
* @return Value.
* @see #valueOf(Object)
*/
public Object value(int valId) {
return value(valId, dataMode);
}
/**
* @param keyId Key Id.
* @param mode Mode.
* @return Key.
*/
public static Object key(int keyId, DataMode mode) {
if (mode == null)
mode = DataMode.SERIALIZABLE;
switch (mode) {
case SERIALIZABLE:
return new SerializableObject(keyId);
case CUSTOM_SERIALIZABLE:
return new CustomSerializableObject(keyId);
case EXTERNALIZABLE:
return new ExternalizableObject(keyId);
case PLANE_OBJECT:
return new PlaneObject(keyId);
case BINARILIZABLE:
return new BinarylizableObject(keyId);
default:
throw new IllegalArgumentException("mode: " + mode);
}
}
/**
* @param obj Key or value object
* @return Value.
*/
public static int valueOf(Object obj) {
assertNotNull(obj);
if (obj instanceof TestObject)
return ((TestObject)obj).value();
else
throw new IllegalArgumentException("Unknown tested object type: " + obj);
}
/**
* @param idx Index.
* @param mode Mode.
* @return Value.
*/
public static Object value(int idx, DataMode mode) {
if (mode == null)
mode = DataMode.SERIALIZABLE;
switch (mode) {
case SERIALIZABLE:
return new SerializableObject(idx);
case CUSTOM_SERIALIZABLE:
return new CustomSerializableObject(idx);
case EXTERNALIZABLE:
return new ExternalizableObject(idx);
case PLANE_OBJECT:
return new PlaneObject(idx);
case BINARILIZABLE:
return new BinarylizableObject(idx);
default:
throw new IllegalArgumentException("mode: " + mode);
}
}
/**
*
*/
public static interface TestObject {
/**
* @return Value.
*/
public int value();
}
/**
*
*/
public static class PlaneObject implements TestObject {
/** */
protected int val;
/** */
protected String strVal;
/** */
protected TestEnum enumVal;
/**
* Default constructor must be accessible for deserialize subclasses by JDK serialization API.
*/
PlaneObject() {
// No-op.
}
/**
* @param val Value.
*/
PlaneObject(int val) {
this.val = val;
strVal = "val" + val;
TestEnum[] values = TestEnum.values();
enumVal = values[Math.abs(val) % values.length];
}
/** {@inheritDoc} */
@Override public int value() {
return val;
}
/** {@inheritDoc} */
@Override public boolean equals(Object o) {
if (this == o)
return true;
if (!(o instanceof PlaneObject))
return false;
PlaneObject val = (PlaneObject)o;
return getClass().equals(o.getClass()) && this.val == val.val && enumVal == val.enumVal
&& strVal.equals(val.strVal);
}
/** {@inheritDoc} */
@Override public int hashCode() {
return val;
}
/** {@inheritDoc} */
@Override public String toString() {
return getClass().getSimpleName() + "[" +
"val=" + val +
", strVal='" + strVal + '\'' +
", enumVal=" + enumVal +
']';
}
}
/**
*
*/
protected static class SerializableObject implements Serializable, TestObject {
/** */
private static final long serialVersionUID = 0;
/** */
protected int val;
/** */
protected String strVal;
/** */
protected TestEnum enumVal;
/**
* Default constructor.
*/
public SerializableObject() {
// No-op.
}
/**
* @param val Value.
*/
public SerializableObject(int val) {
this.val = val;
strVal = "val" + val;
TestEnum[] values = TestEnum.values();
enumVal = values[Math.abs(val) % values.length];
}
/** {@inheritDoc} */
@Override public int value() {
return val;
}
/** {@inheritDoc} */
@Override public boolean equals(Object o) {
if (this == o)
return true;
if (!(o instanceof SerializableObject))
return false;
SerializableObject val = (SerializableObject)o;
return getClass().equals(o.getClass()) && this.val == val.val && enumVal == val.enumVal
&& strVal.equals(val.strVal);
}
/** {@inheritDoc} */
@Override public int hashCode() {
return val;
}
/** {@inheritDoc} */
@Override public String toString() {
return getClass().getSimpleName() + "[" +
"val=" + val +
", strVal='" + strVal + '\'' +
", enumVal=" + enumVal +
']';
}
}
/**
*
*/
protected static class CustomSerializableObject extends PlaneObject implements Serializable {
/** */
private static final long serialVersionUID = 0;
/**
* Default constructor.
*/
public CustomSerializableObject() {
// No-op.
}
/**
* @param val Value.
*/
public CustomSerializableObject(int val) {
super(val);
}
/**
* Custom serialization of superclass because {@link PlaneObject} is non-serializable.
*
* @param out output stream.
* @throws IOException if de-serialization failed.
*/
private void writeObject(ObjectOutputStream out) throws IOException {
out.writeInt(val);
out.writeObject(strVal);
out.writeObject(enumVal);
}
/**
* Custom deserialization of superclass because {@link PlaneObject} is non-serializable.
*
* @param in input stream
* @throws IOException if de-serialization failed.
* @throws ClassNotFoundException if de-serialization failed.
*/
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
val = in.readInt();
strVal = (String)in.readObject();
enumVal = (TestEnum)in.readObject();
}
}
/**
*
*/
private static class ExternalizableObject extends PlaneObject implements Externalizable {
/** */
private static final long serialVersionUID = 0;
/**
* Default constructor.
*/
public ExternalizableObject() {
super(-1);
}
/**
* @param val Value.
*/
ExternalizableObject(int val) {
super(val);
}
/** {@inheritDoc} */
@Override public void writeExternal(ObjectOutput out) throws IOException {
out.writeInt(val);
out.writeObject(strVal);
out.writeObject(enumVal);
}
/** {@inheritDoc} */
@Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
val = in.readInt();
strVal = (String)in.readObject();
enumVal = (TestEnum)in.readObject();
}
}
/**
*
*/
public static class BinarylizableObject extends PlaneObject implements Binarylizable {
/**
* Default constructor.
*/
public BinarylizableObject() {
super(-1);
}
/**
* @param val Value.
*/
public BinarylizableObject(int val) {
super(val);
}
/** {@inheritDoc} */
@Override public void writeBinary(BinaryWriter writer) throws BinaryObjectException {
writer.writeInt("val", val);
writer.writeString("strVal", strVal);
writer.writeEnum("enumVal", enumVal);
}
/** {@inheritDoc} */
@Override public void readBinary(BinaryReader reader) throws BinaryObjectException {
val = reader.readInt("val");
strVal = reader.readString("strVal");
enumVal = reader.readEnum("enumVal");
}
}
/**
* Data mode.
*/
public enum DataMode {
/** Serializable objects. */
SERIALIZABLE,
/** Serializable objects with custom serialization. */
CUSTOM_SERIALIZABLE,
/** Externalizable objects. */
EXTERNALIZABLE,
/** Objects without Serializable and Externalizable. */
PLANE_OBJECT,
/** Binarylizable objects. Compatible only with binary marshaller */
BINARILIZABLE
}
/**
*
*/
private enum TestEnum {
/** */
TEST_VALUE_1,
/** */
TEST_VALUE_2,
/** */
TEST_VALUE_3
}
/**
*
*/
public interface TestRunnable {
/**
* @throws Exception If failed.
*/
void run() throws Exception;
}
/**
* Check test compatibility with current data mode
*
* @return {@code True} if compatible.
* @throws Exception If failed.
*/
protected boolean isCompatible() throws Exception {
switch (dataMode) {
case BINARILIZABLE:
case PLANE_OBJECT:
return !(getConfiguration().getMarshaller() instanceof JdkMarshaller);
}
return false;
}
}