http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/bd28003b/modules/core/src/test/java/org/apache/ignite/testframework/GridTestUtils.java
----------------------------------------------------------------------
diff --git 
a/modules/core/src/test/java/org/apache/ignite/testframework/GridTestUtils.java 
b/modules/core/src/test/java/org/apache/ignite/testframework/GridTestUtils.java
new file mode 100644
index 0000000..952ee5c
--- /dev/null
+++ 
b/modules/core/src/test/java/org/apache/ignite/testframework/GridTestUtils.java
@@ -0,0 +1,1438 @@
+/*
+ * 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;
+
+import junit.framework.*;
+import org.apache.ignite.*;
+import org.apache.ignite.cache.*;
+import org.apache.ignite.cache.affinity.consistenthash.*;
+import org.apache.ignite.cluster.*;
+import org.apache.ignite.internal.*;
+import org.apache.ignite.internal.processors.cache.*;
+import org.apache.ignite.internal.util.*;
+import org.apache.ignite.lang.*;
+import org.apache.ignite.client.ssl.*;
+import org.apache.ignite.internal.processors.cache.distributed.dht.*;
+import org.apache.ignite.internal.processors.cache.distributed.near.*;
+import org.apache.ignite.internal.util.future.*;
+import org.apache.ignite.internal.util.lang.*;
+import org.apache.ignite.internal.util.typedef.*;
+import org.apache.ignite.internal.util.typedef.internal.*;
+import org.apache.ignite.testframework.config.*;
+import org.jetbrains.annotations.*;
+
+import javax.net.ssl.*;
+import java.io.*;
+import java.lang.annotation.*;
+import java.lang.reflect.*;
+import java.net.*;
+import java.nio.file.attribute.*;
+import java.security.*;
+import java.util.*;
+import java.util.concurrent.*;
+
+/**
+ * Utility class for tests.
+ */
+@SuppressWarnings({"UnusedCatchParameter"})
+public final class GridTestUtils {
+    /** Default busy wait sleep interval in milliseconds.  */
+    public static final long DFLT_BUSYWAIT_SLEEP_INTERVAL = 200;
+
+    /** */
+    private static final Map<Class<? extends Test>, String> addrs = new 
HashMap<>();
+
+    /** */
+    private static final Map<Class<? extends Test>, Integer> mcastPorts = new 
HashMap<>();
+
+    /** */
+    private static final Map<Class<? extends Test>, Integer> discoPorts = new 
HashMap<>();
+
+    /** */
+    private static final Map<Class<? extends Test>, Integer> commPorts = new 
HashMap<>();
+
+    /** */
+    private static int[] addr;
+
+    /** */
+    private static final int default_mcast_port = 50000;
+
+    /** */
+    private static final int max_mcast_port = 54999;
+
+    /** */
+    private static final int default_comm_port = 45000;
+
+    /** */
+    private static final int max_comm_port = 49999;
+
+    /** */
+    private static final int default_disco_port = 55000;
+
+    /** */
+    private static final int max_disco_port = 59999;
+
+    /** */
+    private static int mcastPort = default_mcast_port;
+
+    /** */
+    private static int discoPort = default_disco_port;
+
+    /** */
+    private static int commPort = default_comm_port;
+
+    /** */
+    private static final GridBusyLock busyLock = new GridBusyLock();
+
+    /**
+     * Ensure singleton.
+     */
+    private GridTestUtils() {
+        // No-op.
+    }
+
+    /**
+     * Checks whether callable throws expected exception or not.
+     *
+     * @param log Logger (optional).
+     * @param call Callable.
+     * @param cls Exception class.
+     * @param msg Exception message (optional). If provided exception message
+     *      and this message should be equal.
+     * @return Thrown throwable.
+     */
+    @Nullable public static Throwable assertThrows(@Nullable IgniteLogger log, 
Callable<?> call,
+        Class<? extends Throwable> cls, @Nullable String msg) {
+        assert call != null;
+        assert cls != null;
+
+        try {
+            call.call();
+        }
+        catch (Throwable e) {
+            if (cls != e.getClass()) {
+                U.error(log, "Unexpected exception.", e);
+
+                fail("Exception class is not as expected [expected=" + cls + 
", actual=" + e.getClass() + ']', e);
+            }
+
+            if (msg != null && (e.getMessage() == null || 
!e.getMessage().startsWith(msg))) {
+                U.error(log, "Unexpected exception message.", e);
+
+                fail("Exception message is not as expected [expected=" + msg + 
", actual=" + e.getMessage() + ']', e);
+            }
+
+            if (log != null) {
+                if (log.isInfoEnabled())
+                    log.info("Caught expected exception: " + e.getMessage());
+            }
+            else
+                X.println("Caught expected exception: " + e.getMessage());
+
+            return e;
+        }
+
+        throw new AssertionError("Exception has not been thrown.");
+    }
+
+    /**
+     * Checks whether callable throws expected exception or its child or not.
+     *
+     * @param log Logger (optional).
+     * @param call Callable.
+     * @param cls Exception class.
+     * @param msg Exception message (optional). If provided exception message
+     *      and this message should be equal.
+     * @return Thrown throwable.
+     */
+    @Nullable public static Throwable assertThrowsInherited(@Nullable 
IgniteLogger log, Callable<?> call,
+        Class<? extends Throwable> cls, @Nullable String msg) {
+        assert call != null;
+        assert cls != null;
+
+        try {
+            call.call();
+        }
+        catch (Throwable e) {
+            if (!cls.isAssignableFrom(e.getClass()))
+                fail("Exception class is not as expected [expected=" + cls + 
", actual=" + e.getClass() + ']', e);
+
+            if (msg != null && (e.getMessage() == null || 
!e.getMessage().startsWith(msg)))
+                fail("Exception message is not as expected [expected=" + msg + 
", actual=" + e.getMessage() + ']', e);
+
+            if (log != null) {
+                if (log.isDebugEnabled())
+                    log.debug("Caught expected exception: " + e.getMessage());
+            }
+            else
+                X.println("Caught expected exception: " + e.getMessage());
+
+            return e;
+        }
+
+        throw new AssertionError("Exception has not been thrown.");
+    }
+
+    /**
+     * Checks whether callable throws exception, which is itself of a specified
+     * class, or has a cause of the specified class.
+     *
+     * @param call Callable.
+     * @param cls Expected class.
+     * @return Thrown throwable.
+     */
+    @Nullable public static Throwable assertThrowsWithCause(Callable<?> call, 
Class<? extends Throwable> cls) {
+        assert call != null;
+        assert cls != null;
+
+        try {
+            call.call();
+        }
+        catch (Throwable e) {
+            if (!X.hasCause(e, cls))
+                fail("Exception is neither of a specified class, nor has a 
cause of the specified class: " + cls, e);
+
+            return e;
+        }
+
+        throw new AssertionError("Exception has not been thrown.");
+    }
+
+    /**
+     * Throw assertion error with specified error message and initialized 
cause.
+     *
+     * @param msg Error message.
+     * @param cause Error cause.
+     * @return Assertion error.
+     */
+    private static AssertionError fail(String msg, @Nullable Throwable cause) {
+        AssertionError e = new AssertionError(msg);
+
+        if (cause != null)
+            e.initCause(cause);
+
+        throw e;
+    }
+
+    /**
+     * Checks whether object's method call throws expected exception or not.
+     *
+     * @param log Logger (optional).
+     * @param cls Exception class.
+     * @param msg Exception message (optional). If provided exception message
+     *      and this message should be equal.
+     * @param obj Object to invoke method for.
+     * @param mtd Object's method to invoke.
+     * @param params Method parameters.
+     * @return Thrown throwable.
+     */
+    @Nullable public static Throwable assertThrows(@Nullable IgniteLogger log, 
Class<? extends Throwable> cls,
+        @Nullable String msg, final Object obj, final String mtd, final 
Object... params) {
+        return assertThrows(log, new Callable() {
+            @Override public Object call() throws Exception {
+                return invoke(obj, mtd, params);
+            }
+        }, cls, msg);
+    }
+
+    /**
+     * Asserts that each element in iterable has one-to-one correspondence 
with a
+     * predicate from list.
+     *
+     * @param it Input iterable of elements.
+     * @param ps Array of predicates (by number of elements in iterable).
+     */
+    @SuppressWarnings("ConstantConditions")
+    public static <T> void assertOneToOne(Iterable<T> it, 
IgnitePredicate<T>... ps) {
+        Collection<IgnitePredicate<T>> ps0 = new 
ArrayList<>(Arrays.asList(ps));
+        Collection<T2<IgnitePredicate<T>, T>> passed = new ArrayList<>();
+
+        for (T elem : it) {
+            for (T2<IgnitePredicate<T>, T> p : passed) {
+                if (p.get1().apply(elem))
+                    throw new AssertionError("Two elements match one predicate 
[elem1=" + p.get2() +
+                        ", elem2=" + elem + ", pred=" + p.get1() + ']');
+            }
+
+            IgnitePredicate<T> matched = null;
+
+            for (IgnitePredicate<T> p : ps0) {
+                if (p.apply(elem)) {
+                    if (matched != null)
+                        throw new AssertionError("Element matches more than 
one predicate [elem=" + elem +
+                            ", pred1=" + p + ", pred2=" + matched + ']');
+
+                    matched = p;
+                }
+            }
+
+            if (matched == null) // None matched.
+                throw new AssertionError("The element does not match [elem=" + 
elem +
+                    ", numRemainingPreds=" + ps0.size() + ']');
+
+            ps0.remove(matched);
+            passed.add(new T2<>(matched, elem));
+        }
+    }
+
+    /**
+     * Every invocation of this method will never return a
+     * repeating multicast port for a different test case.
+     *
+     * @param cls Class.
+     * @return Next multicast port.
+     */
+    public static synchronized int getNextMulticastPort(Class<? extends Test> 
cls) {
+        Integer portRet = mcastPorts.get(cls);
+
+        if (portRet != null)
+            return portRet;
+
+        int startPort = mcastPort;
+
+        while (true) {
+            if (mcastPort >= max_mcast_port)
+                mcastPort = default_mcast_port;
+            else
+                mcastPort++;
+
+            if (startPort == mcastPort)
+                break;
+
+            portRet = mcastPort;
+
+            MulticastSocket sock = null;
+
+            try {
+                sock = new MulticastSocket(portRet);
+
+                break;
+            }
+            catch (IOException ignored) {
+                // No-op.
+            }
+            finally {
+                U.closeQuiet(sock);
+            }
+        }
+
+        // Cache port to be reused by the same test.
+        mcastPorts.put(cls, portRet);
+
+        return portRet;
+    }
+
+    /**
+     * Every invocation of this method will never return a
+     * repeating communication port for a different test case.
+     *
+     * @param cls Class.
+     * @return Next communication port.
+     */
+    public static synchronized int getNextCommPort(Class<? extends Test> cls) {
+        Integer portRet = commPorts.get(cls);
+
+        if (portRet != null)
+            return portRet;
+
+        if (commPort >= max_comm_port)
+            commPort = default_comm_port;
+        else
+            // Reserve 10 ports per test.
+            commPort += 10;
+
+        portRet = commPort;
+
+        // Cache port to be reused by the same test.
+        commPorts.put(cls, portRet);
+
+        return portRet;
+    }
+
+    /**
+     * Every invocation of this method will never return a
+     * repeating discovery port for a different test case.
+     *
+     * @param cls Class.
+     * @return Next discovery port.
+     */
+    public static synchronized int getNextDiscoPort(Class<? extends Test> cls) 
{
+        Integer portRet = discoPorts.get(cls);
+
+        if (portRet != null)
+            return portRet;
+
+        if (discoPort >= max_disco_port)
+            discoPort = default_disco_port;
+        else
+            discoPort += 10;
+
+        portRet = discoPort;
+
+        // Cache port to be reused by the same test.
+        discoPorts.put(cls, portRet);
+
+        return portRet;
+    }
+
+    /**
+     * Every invocation of this method will never return a
+     * repeating multicast group for a different test case.
+     *
+     * @param cls Class.
+     * @return Next multicast group.
+     */
+    public static synchronized String getNextMulticastGroup(Class<? extends 
Test> cls) {
+        String addrStr = addrs.get(cls);
+
+        if (addrStr != null)
+            return addrStr;
+
+        // Increment address.
+        if (addr[3] == 255) {
+            if (addr[2] == 255)
+                assert false;
+            else {
+                addr[2] += 1;
+
+                addr[3] = 1;
+            }
+        }
+        else
+            addr[3] += 1;
+
+        // Convert address to string.
+        StringBuilder b = new StringBuilder(15);
+
+        for (int i = 0; i < addr.length; i++) {
+            b.append(addr[i]);
+
+            if (i < addr.length - 1)
+                b.append('.');
+        }
+
+        addrStr = b.toString();
+
+        // Cache address to be reused by the same test.
+        addrs.put(cls, addrStr);
+
+        return addrStr;
+    }
+
+    /**
+     * Runs runnable object in specified number of threads.
+     *
+     * @param run Target runnable.
+     * @param threadNum Number of threads.
+     * @param threadName Thread name.
+     * @return Execution time in milliseconds.
+     * @throws Exception Thrown if at least one runnable execution failed.
+     */
+    public static long runMultiThreaded(Runnable run, int threadNum, String 
threadName) throws Exception {
+        return runMultiThreaded(makeCallable(run, null), threadNum, 
threadName);
+    }
+
+    /**
+     * Runs runnable object in specified number of threads.
+     *
+     * @param run Target runnable.
+     * @param threadNum Number of threads.
+     * @param threadName Thread name.
+     * @return Future for the run. Future returns execution time in 
milliseconds.
+     */
+    public static IgniteFuture<Long> runMultiThreadedAsync(Runnable run, int 
threadNum, String threadName) {
+        return runMultiThreadedAsync(makeCallable(run, null), threadNum, 
threadName);
+    }
+
+    /**
+     * Runs callable object in specified number of threads.
+     *
+     * @param call Callable.
+     * @param threadNum Number of threads.
+     * @param threadName Thread names.
+     * @return Execution time in milliseconds.
+     * @throws Exception If failed.
+     */
+    public static long runMultiThreaded(Callable<?> call, int threadNum, 
String threadName) throws Exception {
+        List<Callable<?>> calls = Collections.<Callable<?>>nCopies(threadNum, 
call);
+
+        return runMultiThreaded(calls, threadName);
+    }
+
+    /**
+     * Runs callable object in specified number of threads.
+     *
+     * @param call Callable.
+     * @param threadNum Number of threads.
+     * @param threadName Thread names.
+     * @return Future for the run. Future returns execution time in 
milliseconds.
+     */
+    @SuppressWarnings("ExternalizableWithoutPublicNoArgConstructor")
+    public static IgniteFuture<Long> runMultiThreadedAsync(Callable<?> call, 
int threadNum, final String threadName) {
+        final List<Callable<?>> calls = 
Collections.<Callable<?>>nCopies(threadNum, call);
+        final GridTestSafeThreadFactory threadFactory = new 
GridTestSafeThreadFactory(threadName);
+
+        // Future that supports cancel() operation.
+        GridFutureAdapter<Long> cancelFut = new GridFutureAdapter<Long>() {
+            @Override public boolean cancel() {
+                if (onCancelled()) {
+                    threadFactory.interruptAllThreads();
+
+                    onCancelled();
+
+                    return true;
+                }
+
+                return false;
+            }
+        };
+
+        // Async execution future (doesn't support cancel()).
+        IgniteFuture<Long> runFut = runAsync(new Callable<Long>() {
+            @Override public Long call() throws Exception {
+                return runMultiThreaded(calls, threadFactory);
+            }
+        });
+
+        // Compound future, that adds cancel() support to execution future.
+        GridCompoundFuture<Long, Long> compFut = new GridCompoundFuture<>();
+
+        compFut.addAll(cancelFut, runFut);
+        compFut.reducer(F.sumLongReducer());
+        compFut.markInitialized();
+
+        cancelFut.onDone();
+
+        return compFut;
+    }
+
+    /**
+     * Runs callable tasks each in separate threads.
+     *
+     * @param calls Callable tasks.
+     * @param threadName Thread name.
+     * @return Execution time in milliseconds.
+     * @throws Exception If failed.
+     */
+    public static long runMultiThreaded(Iterable<Callable<?>> calls, String 
threadName) throws Exception {
+        return runMultiThreaded(calls, new 
GridTestSafeThreadFactory(threadName));
+    }
+
+    /**
+     * Runs callable tasks each in separate threads.
+     *
+     * @param calls Callable tasks.
+     * @param threadFactory Thread factory.
+     * @return Execution time in milliseconds.
+     * @throws Exception If failed.
+     */
+    public static long runMultiThreaded(Iterable<Callable<?>> calls, 
GridTestSafeThreadFactory threadFactory)
+        throws Exception {
+        if (!busyLock.enterBusy())
+            throw new IllegalStateException("Failed to start new threads (test 
is being stopped).");
+
+        Collection<Thread> threads = new ArrayList<>();
+        long time;
+
+        try {
+            for (Callable<?> call : calls)
+                threads.add(threadFactory.newThread(call));
+
+            time = System.currentTimeMillis();
+
+            for (Thread t : threads)
+                t.start();
+        }
+        finally {
+            busyLock.leaveBusy();
+        }
+
+        // Wait threads finish their job.
+        for (Thread t : threads)
+            t.join();
+
+        time = System.currentTimeMillis() - time;
+
+        // Validate errors happens
+        threadFactory.checkError();
+
+        return time;
+    }
+
+    /**
+     * Runs callable task asyncronously.
+     *
+     * @param task Callable.
+     * @return Future with task result.
+     */
+    @SuppressWarnings("ExternalizableWithoutPublicNoArgConstructor")
+    public static <T> IgniteFuture<T> runAsync(final Callable<T> task) {
+        if (!busyLock.enterBusy())
+            throw new IllegalStateException("Failed to start new threads (test 
is being stopped).");
+
+        try {
+            final GridTestSafeThreadFactory thrFactory = new 
GridTestSafeThreadFactory("async-runner");
+
+            final GridFutureAdapter<T> fut = new GridFutureAdapter<T>() {
+                @Override public boolean cancel() throws 
IgniteCheckedException {
+                    super.cancel();
+
+                    thrFactory.interruptAllThreads();
+
+                    onCancelled();
+
+                    return true;
+                }
+            };
+
+            thrFactory.newThread(new Runnable() {
+                @Override public void run() {
+                    try {
+                        // Execute task.
+                        T res = task.call();
+
+                        fut.onDone(res);
+                    }
+                    catch (Throwable e) {
+                        fut.onDone(e);
+                    }
+                }
+            }).start();
+
+            return fut;
+        }
+        finally {
+            busyLock.leaveBusy();
+        }
+    }
+
+    /**
+     * Interrupts and waits for termination of all the threads started
+     * so far by current test.
+     *
+     * @param log Logger.
+     */
+    public static void stopThreads(IgniteLogger log) {
+        busyLock.block();
+
+        try {
+            GridTestSafeThreadFactory.stopAllThreads(log);
+        }
+        finally {
+            busyLock.unblock();
+        }
+    }
+
+    /**
+     * @return GridGain home.
+     * @throws Exception If failed.
+     */
+    @SuppressWarnings({"ProhibitedExceptionThrown"})
+    public static String getGridGainHome() throws Exception {
+        String ggHome = System.getProperty("GRIDGAIN_HOME");
+
+        if (ggHome == null)
+            ggHome = System.getenv("GRIDGAIN_HOME");
+
+        if (ggHome == null)
+            throw new Exception("GRIDGAIN_HOME parameter must be set either as 
system or environment variable.");
+
+        File dir = new File(ggHome);
+
+        if (!dir.exists())
+            throw new Exception("Gridgain home does not exist [girdgain-home=" 
+ dir.getAbsolutePath() + ']');
+
+        if (!dir.isDirectory())
+            throw new Exception("Gridgain home is not a directory 
[gridgain-home=" + dir.getAbsolutePath() + ']');
+
+        return ggHome;
+    }
+
+    /**
+     * @param <T> Type.
+     * @param cls Class.
+     * @param annCls Annotation class.
+     * @return Annotation.
+     */
+    @Nullable public static <T extends Annotation> T getAnnotation(Class<?> 
cls, Class<T> annCls) {
+        for (Class<?> cls0 = cls; cls0 != null; cls0 = cls0.getSuperclass()) {
+            T ann = cls0.getAnnotation(annCls);
+
+            if (ann != null)
+                return ann;
+        }
+
+        return null;
+    }
+
+    /**
+     * Initializes address.
+     */
+    static {
+        InetAddress locHost = null;
+
+        try {
+            locHost = U.getLocalHost();
+        }
+        catch (IOException e) {
+            assert false : "Unable to get local address. This leads to the 
same multicast addresses " +
+                "in the local network.";
+        }
+
+        if (locHost != null) {
+            int thirdByte = locHost.getAddress()[3];
+
+            if (thirdByte < 0)
+                thirdByte += 256;
+
+            // To get different addresses for different machines.
+            addr = new int[] {229, thirdByte, 1, 1};
+        }
+        else
+            addr = new int[] {229, 1, 1, 1};
+    }
+
+    /**
+     * @param path Path.
+     * @param startFilter Start filter.
+     * @param endFilter End filter.
+     * @return List of JARs that corresponds to the filters.
+     * @throws IOException If failed.
+     */
+    private static Collection<String> getFiles(String path, @Nullable final 
String startFilter,
+        @Nullable final String endFilter) throws IOException {
+        Collection<String> res = new ArrayList<>();
+
+        File file = new File(path);
+
+        assert file.isDirectory();
+
+        File[] jars = file.listFiles(new FilenameFilter() {
+            /**
+             * @see FilenameFilter#accept(File, String)
+             */
+            @SuppressWarnings({"UnnecessaryJavaDocLink"})
+            @Override public boolean accept(File dir, String name) {
+                // Exclude spring.jar because it tries to load 
META-INF/spring-handlers.xml from
+                // all available JARs and create instances of classes from 
there for example.
+                // Exclude logging as it is used by spring and casted to Log 
interface.
+                // Exclude log4j because of the design - 1 per VM.
+                if (name.startsWith("spring") || name.startsWith("log4j") ||
+                    name.startsWith("commons-logging") || 
name.startsWith("junit") ||
+                    name.startsWith("gridgain-tests"))
+                    return false;
+
+                boolean ret = true;
+
+                if (startFilter != null)
+                    ret = name.startsWith(startFilter);
+
+                if (ret && endFilter != null)
+                    ret = name.endsWith(endFilter);
+
+                return ret;
+            }
+        });
+
+        for (File jar : jars)
+            res.add(jar.getCanonicalPath());
+
+        return res;
+    }
+
+    /**
+     * Silent stop grid.
+     * Method doesn't throw any exception.
+     *
+     * @param ignite Grid to stop.
+     * @param log Logger.
+     */
+    @SuppressWarnings({"CatchGenericClass"})
+    public static void close(Ignite ignite, IgniteLogger log) {
+        if (ignite != null)
+            try {
+                G.stop(ignite.name(), false);
+            }
+            catch (Throwable e) {
+                U.error(log, "Failed to stop grid: " + ignite.name(), e);
+            }
+    }
+
+    /**
+     * Silent stop grid.
+     * Method doesn't throw any exception.
+     *
+     * @param gridName Grid name.
+     * @param log Logger.
+     */
+    @SuppressWarnings({"CatchGenericClass"})
+    public static void stopGrid(String gridName, IgniteLogger log) {
+        try {
+            G.stop(gridName, false);
+        }
+        catch (Throwable e) {
+            U.error(log, "Failed to stop grid: " + gridName, e);
+        }
+    }
+
+    /**
+     * Gets file representing the path passed in. First the check is made if 
path is absolute.
+     * If not, then the check is made if path is relative to ${GRIDGAIN_HOME}. 
If both checks fail,
+     * then {@code null} is returned, otherwise file representing path is 
returned.
+     * <p>
+     * See {@link #getGridGainHome()} for information on how {@code 
GRIDGAIN_HOME} is retrieved.
+     *
+     * @param path Path to resolve.
+     * @return Resolved path, or {@code null} if file cannot be resolved.
+     * @see #getGridGainHome()
+     */
+    @Nullable public static File resolveGridGainPath(String path) {
+        return resolveGridGainPath(null, path);
+    }
+
+    /**
+     * @param ggHome Optional gridgain home path.
+     * @param path Path to resolve.
+     * @return Resolved path, or {@code null} if file cannot be resolved.
+     */
+    @Nullable public static File resolveGridGainPath(@Nullable String ggHome, 
String path) {
+        File file = resolvePath(ggHome, path);
+
+        return file != null ? file : resolvePath(ggHome, "os/" + path);
+    }
+
+    /**
+     * @param ggHome Optional gridgain home path.
+     * @param path Path to resolve.
+     * @return Resolved path, or {@code null} if file cannot be resolved.
+     */
+    @Nullable private static File resolvePath(@Nullable String ggHome, String 
path) {
+        File file = new File(path).getAbsoluteFile();
+
+        if (!file.exists()) {
+            String home = ggHome != null ? ggHome : U.getGridGainHome();
+
+            if (home == null)
+                return null;
+
+            file = new File(home, path);
+
+            return file.exists() ? file : null;
+        }
+
+        return file;
+    }
+
+    /**
+     * @param cache Cache.
+     * @return Cache context.
+     */
+    public static <K, V> GridCacheContext<K, V> 
cacheContext(GridCacheProjection<K, V> cache) {
+        return ((GridKernal)cache.gridProjection().ignite()).<K, 
V>internalCache().context();
+    }
+
+    /**
+     * @param cache Cache.
+     * @return Near cache.
+     */
+    public static <K, V> GridNearCacheAdapter<K, V> 
near(GridCacheProjection<K, V> cache) {
+        return cacheContext(cache).near();
+    }
+
+    /**
+     * @param cache Cache.
+     * @return DHT cache.
+     */
+    public static <K, V> GridDhtCacheAdapter<K, V> dht(GridCacheProjection<K, 
V> cache) {
+        return near(cache).dht();
+    }
+
+    /**
+     * @param cache Cache.
+     * @return Affinity.
+     */
+    static <K, V> GridCacheConsistentHashAffinityFunction 
affinity(GridCacheProjection<K, V> cache) {
+        return 
(GridCacheConsistentHashAffinityFunction)cache.cache().configuration().getAffinity();
+    }
+
+    /**
+     * @param cacheName Cache name.
+     * @param backups Number of backups.
+     * @param log Logger.
+     * @throws Exception If failed.
+     */
+    @SuppressWarnings("BusyWait")
+    public static <K, V> void waitTopologyUpdate(@Nullable String cacheName, 
int backups, IgniteLogger log)
+        throws Exception {
+        for (Ignite g : Ignition.allGrids()) {
+            GridCache<K, V> cache = ((GridEx)g).cachex(cacheName);
+
+            GridDhtPartitionTopology<?, ?> top = dht(cache).topology();
+
+            while (true) {
+                boolean wait = false;
+
+                for (int p = 0; p < affinity(cache).partitions(); p++) {
+                    Collection<ClusterNode> nodes = top.nodes(p, -1);
+
+                    if (nodes.size() > backups + 1) {
+                        LT.warn(log, null, "Partition map was not updated yet 
(will wait) [grid=" + g.name() +
+                            ", p=" + p + ", nodes=" + F.nodeIds(nodes) + ']');
+
+                        wait = true;
+
+                        break;
+                    }
+                }
+
+                if (wait)
+                    Thread.sleep(20);
+                else
+                    break; // While.
+            }
+        }
+    }
+
+    /**
+     * Convert runnable tasks with callable.
+     *
+     * @param run Runnable task to convert into callable one.
+     * @param res Callable result.
+     * @param <T> The result type of method <tt>call</tt>, always {@code null}.
+     * @return Callable task around the specified runnable one.
+     */
+    public static <T> Callable<T> makeCallable(final Runnable run, @Nullable 
final T res) {
+        return new Callable<T>() {
+            @Override public T call() throws Exception {
+                run.run();
+                return res;
+            }
+        };
+    }
+
+    /**
+     * Get object field value via reflection.
+     *
+     * @param obj Object or class to get field value from.
+     * @param cls Class.
+     * @param fieldName Field names to get value for.
+     * @param <T> Expected field class.
+     * @return Field value.
+     * @throws IgniteException In case of error.
+     */
+    @SuppressWarnings("SynchronizationOnLocalVariableOrMethodParameter")
+    public static <T> T getFieldValue(Object obj, Class cls, String fieldName) 
throws IgniteException {
+        assert obj != null;
+        assert fieldName != null;
+
+        try {
+            // Resolve inner field.
+            Field field = cls.getDeclaredField(fieldName);
+
+            synchronized (field) {
+                // Backup accessible field state.
+                boolean accessible = field.isAccessible();
+
+                try {
+                    if (!accessible)
+                        field.setAccessible(true);
+
+                    obj = field.get(obj);
+                }
+                finally {
+                    // Recover accessible field state.
+                    if (!accessible)
+                        field.setAccessible(false);
+                }
+            }
+
+            return (T)obj;
+        }
+        catch (NoSuchFieldException | IllegalAccessException e) {
+            throw new IgniteException("Failed to get object field [obj=" + obj 
+
+                ", fieldName=" + fieldName + ']', e);
+        }
+    }
+
+    /**
+     * Get object field value via reflection.
+     *
+     * @param obj Object or class to get field value from.
+     * @param fieldNames Field names to get value for: 
obj->field1->field2->...->fieldN.
+     * @param <T> Expected field class.
+     * @return Field value.
+     * @throws IgniteException In case of error.
+     */
+    @SuppressWarnings("SynchronizationOnLocalVariableOrMethodParameter")
+    public static <T> T getFieldValue(Object obj, String... fieldNames) throws 
IgniteException {
+        assert obj != null;
+        assert fieldNames != null;
+        assert fieldNames.length >= 1;
+
+        try {
+            for (String fieldName : fieldNames) {
+                Class<?> cls = obj instanceof Class ? (Class)obj : 
obj.getClass();
+
+                try {
+                    // Resolve inner field.
+                    Field field = cls.getDeclaredField(fieldName);
+
+                    synchronized (field) {
+                        // Backup accessible field state.
+                        boolean accessible = field.isAccessible();
+
+                        try {
+                            if (!accessible)
+                                field.setAccessible(true);
+
+                            obj = field.get(obj);
+                        }
+                        finally {
+                            // Recover accessible field state.
+                            if (!accessible)
+                                field.setAccessible(false);
+                        }
+                    }
+                }
+                catch (NoSuchFieldException e) {
+                    // Resolve inner class, if not an inner field.
+                    Class<?> innerCls = getInnerClass(cls, fieldName);
+
+                    if (innerCls == null)
+                        throw new IgniteException("Failed to get object field 
[obj=" + obj +
+                            ", fieldNames=" + Arrays.toString(fieldNames) + 
']', e);
+
+                    obj = innerCls;
+                }
+            }
+
+            return (T)obj;
+        }
+        catch (IllegalAccessException e) {
+            throw new IgniteException("Failed to get object field [obj=" + obj 
+
+                ", fieldNames=" + Arrays.toString(fieldNames) + ']', e);
+        }
+    }
+
+    /**
+     * Get inner class by its name from the enclosing class.
+     *
+     * @param parentCls Parent class to resolve inner class for.
+     * @param innerClsName Name of the inner class.
+     * @return Inner class.
+     */
+    @Nullable public static <T> Class<T> getInnerClass(Class<?> parentCls, 
String innerClsName) {
+        for (Class<?> cls : parentCls.getDeclaredClasses())
+            if (innerClsName.equals(cls.getSimpleName()))
+                return (Class<T>)cls;
+
+        return null;
+    }
+
+    /**
+     * Set object field value via reflection.
+     *
+     * @param obj Object to set field value to.
+     * @param fieldName Field name to set value for.
+     * @param val New field value.
+     * @throws IgniteException In case of error.
+     */
+    @SuppressWarnings("SynchronizationOnLocalVariableOrMethodParameter")
+    public static void setFieldValue(Object obj, String fieldName, Object val) 
throws IgniteException {
+        assert obj != null;
+        assert fieldName != null;
+
+        try {
+            Class<?> cls = obj instanceof Class ? (Class)obj : obj.getClass();
+
+            Field field = cls.getDeclaredField(fieldName);
+
+            synchronized (field) {
+                // Backup accessible field state.
+                boolean accessible = field.isAccessible();
+
+                try {
+                    if (!accessible)
+                        field.setAccessible(true);
+
+                    field.set(obj, val);
+                }
+                finally {
+                    // Recover accessible field state.
+                    if (!accessible)
+                        field.setAccessible(false);
+                }
+            }
+        }
+        catch (NoSuchFieldException | IllegalAccessException e) {
+            throw new IgniteException("Failed to set object field [obj=" + obj 
+ ", field=" + fieldName + ']', e);
+        }
+    }
+
+    /**
+     * Set object field value via reflection.
+     *
+     * @param obj Object to set field value to.
+     * @param cls Class to get field from.
+     * @param fieldName Field name to set value for.
+     * @param val New field value.
+     * @throws IgniteException In case of error.
+     */
+    @SuppressWarnings("SynchronizationOnLocalVariableOrMethodParameter")
+    public static void setFieldValue(Object obj, Class cls, String fieldName, 
Object val) throws IgniteException {
+        assert obj != null;
+        assert fieldName != null;
+
+        try {
+            Field field = cls.getDeclaredField(fieldName);
+
+            synchronized (field) {
+                // Backup accessible field state.
+                boolean accessible = field.isAccessible();
+
+                try {
+                    if (!accessible)
+                        field.setAccessible(true);
+
+                    field.set(obj, val);
+                }
+                finally {
+                    // Recover accessible field state.
+                    if (!accessible)
+                        field.setAccessible(false);
+                }
+            }
+        }
+        catch (NoSuchFieldException | IllegalAccessException e) {
+            throw new IgniteException("Failed to set object field [obj=" + obj 
+ ", field=" + fieldName + ']', e);
+        }
+    }
+
+    /**
+     * Invoke method on an object.
+     *
+     * @param obj Object to call method on.
+     * @param mtd Method to invoke.
+     * @param params Parameters of the method.
+     * @return Method invocation result.
+     * @throws Exception If failed.
+     */
+    @SuppressWarnings("SynchronizationOnLocalVariableOrMethodParameter")
+    @Nullable public static <T> T invoke(Object obj, String mtd, Object... 
params) throws Exception {
+        // We cannot resolve method by parameter classes due to some of 
parameters can be null.
+        // Search correct method among all methods collection.
+        for (Method m : obj.getClass().getDeclaredMethods()) {
+            // Filter methods by name.
+            if (!m.getName().equals(mtd))
+                continue;
+
+            if (!areCompatible(params, m.getParameterTypes()))
+                continue;
+
+            try {
+                synchronized (m) {
+                    // Backup accessible field state.
+                    boolean accessible = m.isAccessible();
+
+                    try {
+                        if (!accessible)
+                            m.setAccessible(true);
+
+                        return (T)m.invoke(obj, params);
+                    }
+                    finally {
+                        // Recover accessible field state.
+                        if (!accessible)
+                            m.setAccessible(false);
+                    }
+                }
+            }
+            catch (IllegalAccessException e) {
+                throw new RuntimeException("Failed to access method" +
+                    " [obj=" + obj + ", mtd=" + mtd + ", params=" + 
Arrays.toString(params) + ']', e);
+            }
+            catch (InvocationTargetException e) {
+                Throwable cause = e.getCause();
+
+                if (cause instanceof Error)
+                    throw (Error) cause;
+
+                if (cause instanceof Exception)
+                    throw (Exception) cause;
+
+                throw new RuntimeException("Failed to invoke method)" +
+                    " [obj=" + obj + ", mtd=" + mtd + ", params=" + 
Arrays.toString(params) + ']', e);
+            }
+        }
+
+        throw new RuntimeException("Failed to find method" +
+            " [obj=" + obj + ", mtd=" + mtd + ", params=" + 
Arrays.toString(params) + ']');
+    }
+
+    /**
+     * Check objects and corresponding types are compatible.
+     *
+     * @param objs Objects array.
+     * @param types Classes array.
+     * @return Objects in array can be casted to corresponding types.
+     */
+    private static boolean areCompatible(Object[] objs, Class[] types) {
+        if (objs.length != types.length)
+            return false;
+
+        for (int i = 0, size = objs.length; i < size; i++) {
+            Object o = objs[i];
+
+            if (o != null && !types[i].isInstance(o))
+                return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Tries few times to perform some assertion. In the worst case
+     * {@code assertion} closure will be executed {@code retries} + 1 times and
+     * thread will spend approximately {@code retries} * {@code retryInterval} 
sleeping.
+     *
+     * @param log Log.
+     * @param retries Number of retries.
+     * @param retryInterval Interval between retries in milliseconds.
+     * @param c Closure with assertion. All {@link AssertionError}s thrown
+     *      from this closure will be ignored {@code retries} times.
+     * @throws org.apache.ignite.IgniteInterruptedException If interrupted.
+     */
+    @SuppressWarnings("ErrorNotRethrown")
+    public static void retryAssert(@Nullable IgniteLogger log, int retries, 
long retryInterval, GridAbsClosure c)
+        throws IgniteInterruptedException {
+        for (int i = 0; i < retries; i++) {
+            try {
+                c.apply();
+
+                return;
+            }
+            catch (AssertionError e) {
+                U.warn(log, "Check failed (will retry in " + retryInterval + 
"ms).", e);
+
+                U.sleep(retryInterval);
+            }
+        }
+
+        // Apply the last time without guarding try.
+        c.apply();
+    }
+
+    /**
+     * Reads entire file into byte array.
+     *
+     * @param file File to read.
+     * @return Content of file in byte array.
+     * @throws IOException If failed.
+     */
+    public static byte[] readFile(File file) throws IOException {
+        assert file.exists();
+        assert file.length() < Integer.MAX_VALUE;
+
+        byte[] bytes = new byte[(int) file.length()];
+
+        try (FileInputStream fis = new FileInputStream(file)) {
+            int readBytesCnt = fis.read(bytes);
+            assert readBytesCnt == bytes.length;
+        }
+
+        return bytes;
+    }
+
+    /**
+     * Sleeps and increments an integer.
+     * <p>
+     * Allows for loops like the following:
+     * <pre>{@code
+     *     for (int i = 0; i < 20 && !condition; i = sleepAndIncrement(200, 
i)) {
+     *         ...
+     *     }
+     * }</pre>
+     * for busy-waiting limited number of iterations.
+     *
+     * @param sleepDur Sleep duration in milliseconds.
+     * @param i Integer to increment.
+     * @return Incremented value.
+     * @throws org.apache.ignite.IgniteInterruptedException If sleep was 
interrupted.
+     */
+    public static int sleepAndIncrement(int sleepDur, int i) throws 
IgniteInterruptedException {
+        U.sleep(sleepDur);
+
+        return i + 1;
+    }
+
+    /**
+     * Waits for condition, polling in busy wait loop.
+     *
+     * @param cond Condition to wait for.
+     * @param timeout Max time to wait in milliseconds.
+     * @return {@code true} if condition was achieved, {@code false} otherwise.
+     * @throws org.apache.ignite.IgniteInterruptedException If interrupted.
+     */
+    public static boolean waitForCondition(GridAbsPredicate cond, long 
timeout) throws IgniteInterruptedException {
+        long curTime = U.currentTimeMillis();
+        long endTime = curTime + timeout;
+
+        if (endTime < 0)
+            endTime = Long.MAX_VALUE;
+
+        while (curTime < endTime) {
+            if (cond.apply())
+                return true;
+
+            U.sleep(DFLT_BUSYWAIT_SLEEP_INTERVAL);
+
+            curTime = U.currentTimeMillis();
+        }
+
+        return false;
+    }
+
+    /**
+     * Creates an SSL context from test key store with disabled trust manager.
+     *
+     * @return Initialized context.
+     * @throws GeneralSecurityException In case if context could not be 
initialized.
+     * @throws IOException If keystore cannot be accessed.
+     */
+    public static SSLContext sslContext() throws GeneralSecurityException, 
IOException {
+        SSLContext ctx = SSLContext.getInstance("TLS");
+
+        char[] storePass = 
GridTestProperties.getProperty("ssl.keystore.password").toCharArray();
+
+        KeyManagerFactory keyMgrFactory = 
KeyManagerFactory.getInstance("SunX509");
+
+        KeyStore keyStore = KeyStore.getInstance("JKS");
+
+        keyStore.load(new 
FileInputStream(U.resolveGridGainPath(GridTestProperties.getProperty("ssl.keystore.path"))),
+            storePass);
+
+        keyMgrFactory.init(keyStore, storePass);
+
+        ctx.init(keyMgrFactory.getKeyManagers(),
+            new 
TrustManager[]{GridSslBasicContextFactory.getDisabledTrustManager()}, null);
+
+        return ctx;
+    }
+
+    /**
+     * Creates test-purposed SSL context factory from test key store with 
disabled trust manager.
+     *
+     * @return SSL context factory used in test.
+     */
+    public static GridSslContextFactory sslContextFactory() {
+        GridSslBasicContextFactory factory = new GridSslBasicContextFactory();
+
+        factory.setKeyStoreFilePath(
+            
U.resolveGridGainPath(GridTestProperties.getProperty("ssl.keystore.path")).getAbsolutePath());
+        
factory.setKeyStorePassword(GridTestProperties.getProperty("ssl.keystore.password").toCharArray());
+
+        
factory.setTrustManagers(GridSslBasicContextFactory.getDisabledTrustManager());
+
+        return factory;
+    }
+
+    /**
+     * @param o1 Object 1.
+     * @param o2 Object 2.
+     * @return Equals or not.
+     */
+    public static boolean deepEquals(@Nullable Object o1, @Nullable Object o2) 
{
+        if (o1 == o2)
+            return true;
+        else if (o1 == null || o2 == null)
+            return false;
+        else if (o1.getClass() != o2.getClass())
+            return false;
+        else {
+            Class<?> cls = o1.getClass();
+
+            assert o2.getClass() == cls;
+
+            for (Field f : cls.getDeclaredFields()) {
+                f.setAccessible(true);
+
+                Object v1;
+                Object v2;
+
+                try {
+                    v1 = f.get(o1);
+                    v2 = f.get(o2);
+                }
+                catch (IllegalAccessException e) {
+                    throw new AssertionError(e);
+                }
+
+                if (!Objects.deepEquals(v1, v2))
+                    return false;
+            }
+
+            return true;
+        }
+    }
+
+    /**
+     * Converts integer permission mode into set of {@link 
PosixFilePermission}.
+     *
+     * @param mode File mode.
+     * @return Set of {@link PosixFilePermission}.
+     */
+    public static Set<PosixFilePermission> modeToPermissionSet(int mode) {
+        Set<PosixFilePermission> res = 
EnumSet.noneOf(PosixFilePermission.class);
+
+        if ((mode & 0400) > 0)
+            res.add(PosixFilePermission.OWNER_READ);
+
+        if ((mode & 0200) > 0)
+            res.add(PosixFilePermission.OWNER_WRITE);
+
+        if ((mode & 0100) > 0)
+            res.add(PosixFilePermission.OWNER_EXECUTE);
+
+        if ((mode & 040) > 0)
+            res.add(PosixFilePermission.GROUP_READ);
+
+        if ((mode & 020) > 0)
+            res.add(PosixFilePermission.GROUP_WRITE);
+
+        if ((mode & 010) > 0)
+            res.add(PosixFilePermission.GROUP_EXECUTE);
+
+        if ((mode & 04) > 0)
+            res.add(PosixFilePermission.OTHERS_READ);
+
+        if ((mode & 02) > 0)
+            res.add(PosixFilePermission.OTHERS_WRITE);
+
+        if ((mode & 01) > 0)
+            res.add(PosixFilePermission.OTHERS_EXECUTE);
+
+        return res;
+    }
+
+    /**
+     * @return Path to apache ignite.
+     */
+    public static String apacheIgniteTestPath() {
+        return System.getProperty("IGNITE_TEST_PATH", U.getGridGainHome() + 
"/target/ignite");
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/bd28003b/modules/core/src/test/java/org/apache/ignite/testframework/config/GridTestProperties.java
----------------------------------------------------------------------
diff --git 
a/modules/core/src/test/java/org/apache/ignite/testframework/config/GridTestProperties.java
 
b/modules/core/src/test/java/org/apache/ignite/testframework/config/GridTestProperties.java
new file mode 100644
index 0000000..67d8a7b
--- /dev/null
+++ 
b/modules/core/src/test/java/org/apache/ignite/testframework/config/GridTestProperties.java
@@ -0,0 +1,304 @@
+/*
+ * 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.config;
+
+import org.apache.log4j.xml.*;
+import org.apache.ignite.testframework.*;
+import org.jetbrains.annotations.*;
+
+import java.io.*;
+import java.util.*;
+import java.util.Map.*;
+import java.util.regex.*;
+
+/**
+ * Loads test properties from {@code config} folder under tests.
+ * The property structure is as follows:
+ * <ul>
+ * <li>
+ *     Default properties and log4j.xml configuration is loaded directly from
+ *     {@code ${GRIDGAIN_HOME}/modules/tests/config} folder. Default 
properties can be
+ *     accessed via {@link #getDefaultProperties()} and {@link 
#getDefaultProperty(String)} methods.
+ *   </li>
+ * <li>
+ *     User is able to override any default property and log4j configuration in
+ *     {@code ${GRIDGAIN_HOME}/modules/tests/config/${username}} folder, where 
{@code username}
+ *     is the system user name. User properties can be accessed via {@link 
#getProperties()} and
+ *     {@link #getProperties(String)} methods.
+ *   </li>
+ * <li>
+ *     Any test may utilize its own sub-folder. To access configuration 
specific to some sub-folder
+ *     use {@link #getProperties(String)} and {@link #getProperty(String, 
String)} methods.
+ *   </li>
+ * </ul>
+ */
+public final class GridTestProperties {
+    /** */
+    public static final String TESTS_PROP_FILE = "tests.properties";
+
+    /** */
+    public static final String TESTS_CFG_PATH = "modules/core/src/test/config";
+
+    /** */
+    private static final Pattern PROP_REGEX = 
Pattern.compile("[@$]\\{[^@${}]+\\}");
+
+    /** */
+    private static final Map<String, String> dfltProps;
+
+    /** */
+    private static final Map<String, Map<String, String>> pathProps = new 
HashMap<>();
+
+    /** */
+    static {
+        // Initialize GRIDGAIN_HOME system property.
+        String ggHome = System.getProperty("GRIDGAIN_HOME");
+
+        if (ggHome == null || ggHome.isEmpty()) {
+            ggHome = System.getenv("GRIDGAIN_HOME");
+
+            if (ggHome != null && !ggHome.isEmpty())
+                System.setProperty("GRIDGAIN_HOME", ggHome);
+        }
+
+        // Load default properties.
+        File cfgFile = getTestConfigurationFile(null, TESTS_PROP_FILE);
+
+        assert cfgFile.exists();
+        assert !cfgFile.isDirectory();
+
+        dfltProps = Collections.unmodifiableMap(loadFromFile(new 
HashMap<String, String>(), cfgFile));
+
+        if 
("false".equals(System.getProperty("GRIDGAIN_TEST_PROP_DISABLE_LOG4J", 
"false"))) {
+            String user = System.getProperty("user.name");
+
+            assert user != null;
+
+            // Configure log4j logger.
+            configureLog4j(user);
+        }
+    }
+
+    /**
+     * Ensure singleton.
+     */
+    private GridTestProperties() {
+        // No-op.
+    }
+
+    /**
+     * @param user User name.
+     */
+    private static void configureLog4j(String user) {
+        String cfgFile = System.getProperty("GG_TEST_PROP_LOG4J_FILE");
+
+        if (cfgFile == null)
+            cfgFile = "log4j-test.xml";
+
+        File log4jFile = getTestConfigurationFile(user, cfgFile);
+
+        if (log4jFile == null)
+            log4jFile = getTestConfigurationFile(null, cfgFile);
+
+        DOMConfigurator.configure(log4jFile.getAbsolutePath());
+
+        System.out.println("Configured log4j from: " + log4jFile);
+    }
+
+    /** */
+    public static void init() {
+        // No-op.
+    }
+
+    /**
+     * @return Default properties.
+     */
+    public static synchronized Map<String, String> getDefaultProperties() {
+        return dfltProps;
+    }
+
+    /**
+     * @param name Default property name.
+     * @return Default property value.
+     */
+    public static synchronized String getDefaultProperty(String name) {
+        return dfltProps.get(name);
+    }
+
+    /**
+     * @return Properties.
+     */
+    public static synchronized Map<String, String> getProperties() {
+        String user = System.getProperty("user.name");
+
+        assert user != null;
+
+        return getProperties(user);
+    }
+
+    /**
+     * @param name Property name.
+     * @return Property value.
+     */
+    public static synchronized String getProperty(String name) {
+        return getProperties().get(name);
+    }
+
+    /**
+     * @param dir Directory path.
+     * @return Properties.
+     */
+    public static synchronized Map<String, String> getProperties(String dir) {
+        Map<String, String> props = pathProps.get(dir);
+
+        if (props == null) {
+            props = new HashMap<>();
+
+            // Load default properties.
+            props.putAll(dfltProps);
+
+            // Load properties from specified folder
+            // potentially overriding defaults.
+            loadProperties(props, dir);
+
+            // Seal it.
+            props = Collections.unmodifiableMap(props);
+
+            pathProps.put(dir, props);
+        }
+
+        return props;
+    }
+
+    /**
+     * @param name Property name.
+     * @param dir Directory path.
+     * @return Property value.
+     */
+    public static synchronized String getProperty(String name, String dir) {
+        return getProperties(dir).get(name);
+    }
+
+    /**
+     * Substitutes environmental or system properties in the given string.
+     *
+     * @param str String to make substitution in.
+     * @return Substituted string.
+     */
+    private static String substituteProperties(String str) {
+        str = str.trim();
+
+        Matcher matcher = PROP_REGEX.matcher(str);
+
+        StringBuffer buf = new StringBuffer();
+
+        while (matcher.find()) {
+            String match = matcher.group();
+
+            if (match.length() >= 4) {
+                String key = match.substring(2, match.length() - 1);
+
+                String val = System.getenv(key);
+
+                if (val == null)
+                    val = System.getProperty(key);
+
+                if (val != null) {
+                    // Take care of back slashes.
+                    match = val.replaceAll("\\\\", "\\\\\\\\");
+                }
+                else if (match.startsWith("$"))
+                    match = match.replace("$", "\\$");
+            }
+
+            matcher.appendReplacement(buf, match);
+        }
+
+        matcher.appendTail(buf);
+
+        return buf.toString();
+    }
+
+    /**
+     * @param props Initial properties.
+     * @param dir Directory path.
+     * @return Loaded properties.
+     */
+    private static Map<String, String> loadProperties(Map<String, String> 
props, String dir) {
+        File cfg = getTestConfigurationFile(dir, TESTS_PROP_FILE);
+
+        if (cfg != null)
+            loadFromFile(props, cfg);
+
+        return props;
+    }
+
+    /**
+     * @param user User name.
+     * @param fileName File name.
+     * @return Configuration file for given user.
+     */
+    @Nullable private static File getTestConfigurationFile(@Nullable String 
user, String fileName) {
+        String path = TESTS_CFG_PATH;
+
+        if (user != null)
+            path += File.separatorChar + user;
+
+        path += File.separatorChar + fileName;
+
+        File file = GridTestUtils.resolveGridGainPath(path);
+
+        if (file != null && file.exists()) {
+            assert !file.isDirectory();
+
+            return file;
+        }
+
+        return null;
+    }
+
+    /**
+     * @param props Initial properties.
+     * @param file Property file.
+     * @return Loaded properties.
+     */
+    private static Map<String, String> loadFromFile(Map<String, String> props, 
File file) {
+        try {
+
+            try (InputStream in = new FileInputStream(file)) {
+                Properties fileProps = new Properties();
+
+                fileProps.load(in);
+
+                for (Entry<Object, Object> prop : fileProps.entrySet()) {
+                    props.put((String) prop.getKey(), (String) 
prop.getValue());
+                }
+
+                for (Entry<String, String> prop : props.entrySet()) {
+                    prop.setValue(substituteProperties(prop.getValue()));
+                }
+            }
+        }
+        catch (IOException e) {
+            e.printStackTrace();
+
+            assert false : "Failed to load test configuration properties: " + 
file;
+        }
+
+        return props;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/bd28003b/modules/core/src/test/java/org/apache/ignite/testframework/config/package.html
----------------------------------------------------------------------
diff --git 
a/modules/core/src/test/java/org/apache/ignite/testframework/config/package.html
 
b/modules/core/src/test/java/org/apache/ignite/testframework/config/package.html
new file mode 100644
index 0000000..1f85ff2
--- /dev/null
+++ 
b/modules/core/src/test/java/org/apache/ignite/testframework/config/package.html
@@ -0,0 +1,23 @@
+<!--
+  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.
+  -->
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" 
"http://www.w3.org/TR/html4/loose.dtd";>
+<html>
+<body>
+    <!-- Package description. -->
+    Contains internal tests or test related classes and interfaces.
+</body>
+</html>

http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/bd28003b/modules/core/src/test/java/org/apache/ignite/testframework/http/GridEmbeddedHttpServer.java
----------------------------------------------------------------------
diff --git 
a/modules/core/src/test/java/org/apache/ignite/testframework/http/GridEmbeddedHttpServer.java
 
b/modules/core/src/test/java/org/apache/ignite/testframework/http/GridEmbeddedHttpServer.java
new file mode 100644
index 0000000..d39282e
--- /dev/null
+++ 
b/modules/core/src/test/java/org/apache/ignite/testframework/http/GridEmbeddedHttpServer.java
@@ -0,0 +1,189 @@
+/*
+ * 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.http;
+
+import com.sun.net.httpserver.*;
+import org.apache.ignite.testframework.*;
+import org.jetbrains.annotations.*;
+
+import java.io.*;
+import java.net.*;
+
+/**
+ * Embedded HTTP/HTTPS server implementation aimed to simplify tests 
development
+ * that need to make HTTP(s) interactions.
+ * <p>
+ * NOTE: this implementation is NOT thread-safe.
+ */
+public class GridEmbeddedHttpServer {
+    /** Default hostname to bind the server to. */
+    private static final String HOSTNAME_TO_BIND_SRV = "localhost";
+
+    /** Simple Oracle HTTP server used as main workhorse. */
+    private HttpServer httpSrv;
+
+    /** Store exact protocol (HTTP or HTTPS) which we are running at. */
+    private String proto;
+
+    /**
+     * Private constructor to promote server creation and initialization in 
<i>Builder pattern</i> style.
+     */
+    private GridEmbeddedHttpServer() {
+        // No-op
+    }
+
+    /**
+     * The class represents a server handler triggered on incoming request.
+     * <p>The handler checks that a request is a HTTP GET and that url path is 
the expected one.
+     * If all checks are passed it writes pre-configured file content to the 
HTTP response body.
+     * </p>
+     */
+    private static class FileDownloadingHandler implements HttpHandler {
+        /** URL path. */
+        private final String urlPath;
+
+        /** File to be downloaded. */
+        private final File downloadFile;
+
+        /**
+         * Creates and configures FileDownloadingHandler.
+         *
+         * @param urlPath Url path on which a future GET request is going to 
be executed.
+         * @param downloadFile File to be written into the HTTP response.
+         */
+        FileDownloadingHandler(String urlPath, File downloadFile) {
+            this.urlPath = urlPath;
+            this.downloadFile = downloadFile;
+        }
+
+        /**
+         * Handles HTTP requests: checks that a request is a HTTP GET and that 
url path is the expected one.
+         * If all checks are passed it writes pre-configured file content to 
the HTTP response body.
+         *
+         * @param exchange Wrapper above the HTTP request and response.
+         */
+        @Override public void handle(HttpExchange exchange) throws IOException 
{
+            assert "GET".equalsIgnoreCase(exchange.getRequestMethod());
+            assert urlPath == null || 
urlPath.equals(exchange.getRequestURI().toString());
+
+            exchange.getResponseHeaders().set("Content-Type", 
"application/octet-stream");
+            exchange.sendResponseHeaders(200, 0);
+
+            try (OutputStream resBody = exchange.getResponseBody()) {
+                resBody.write(GridTestUtils.readFile(downloadFile));
+            }
+        }
+    }
+
+    /**
+     * Creates and starts embedded HTTP server.
+     *
+     * @return Started HTTP server instance.
+     */
+    public static GridEmbeddedHttpServer startHttpServer() throws Exception {
+        return createAndStart(false);
+    }
+
+    /**
+     * Creates and starts embedded HTTPS server.
+     *
+     * @return Started HTTPS server instance.
+     */
+    public static GridEmbeddedHttpServer startHttpsServer() throws Exception {
+        return createAndStart(true);
+    }
+
+    /**
+     * Configures server with suitable for testing parameters.
+     *
+     * @param urlPath Url path on which a future GET request is going to be 
executed.
+     *                If urlPath is null then no assertions against the 
requesting url will be done.
+     * @param fileToBeDownloaded File to be written into the HTTP response.
+     * @return Configured HTTP(s) server.
+     */
+    public GridEmbeddedHttpServer withFileDownloadingHandler(@Nullable String 
urlPath, File fileToBeDownloaded) {
+        assert fileToBeDownloaded.exists();
+
+        httpSrv.createContext("/", new FileDownloadingHandler(urlPath, 
fileToBeDownloaded));
+
+        return this;
+    }
+
+    /**
+     * Stops server by closing the listening socket and disallowing any new 
exchanges
+     * from being processed.
+     *
+     * @param delay - the maximum time in seconds to wait until exchanges have 
finished.
+     */
+    public void stop(int delay) {
+        httpSrv.stop(delay);
+    }
+
+    /**
+     * Returns base server url in the form 
<i>protocol://serverHostName:serverPort</i>.
+     *
+     * @return Base server url.
+     */
+    public String getBaseUrl() {
+        return proto + "://" + httpSrv.getAddress().getHostName() + ":" + 
httpSrv.getAddress().getPort();
+    }
+
+    /**
+     * Internal method which creates and starts the server.
+     *
+     * @param httpsMode True if the server to be started is HTTPS, false 
otherwise.
+     * @return Started server.
+     */
+    private static GridEmbeddedHttpServer createAndStart(boolean httpsMode) 
throws Exception {
+        HttpServer httpSrv;
+        InetSocketAddress addrToBind = new 
InetSocketAddress(HOSTNAME_TO_BIND_SRV, getAvailablePort());
+
+        if (httpsMode) {
+            HttpsServer httpsSrv = HttpsServer.create(addrToBind, 0);
+
+            httpsSrv.setHttpsConfigurator(new 
HttpsConfigurator(GridTestUtils.sslContext()));
+
+            httpSrv = httpsSrv;
+        }
+        else
+            httpSrv = HttpServer.create(addrToBind, 0);
+
+        GridEmbeddedHttpServer embeddedHttpSrv = new GridEmbeddedHttpServer();
+
+        embeddedHttpSrv.proto = httpsMode ? "https" : "http";
+        embeddedHttpSrv.httpSrv = httpSrv;
+        embeddedHttpSrv.httpSrv.start();
+
+        return embeddedHttpSrv;
+    }
+
+    /**
+     * Returns a port number which was available for the moment of the method 
call.
+     *
+     * @return Available port number.
+     */
+    private static int getAvailablePort() throws IOException {
+        int httpSrvPort;
+
+        try (ServerSocket s = new ServerSocket(0)) {
+            httpSrvPort = s.getLocalPort();
+        }
+
+        return httpSrvPort;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/bd28003b/modules/core/src/test/java/org/apache/ignite/testframework/http/package.html
----------------------------------------------------------------------
diff --git 
a/modules/core/src/test/java/org/apache/ignite/testframework/http/package.html 
b/modules/core/src/test/java/org/apache/ignite/testframework/http/package.html
new file mode 100644
index 0000000..b75dcc2
--- /dev/null
+++ 
b/modules/core/src/test/java/org/apache/ignite/testframework/http/package.html
@@ -0,0 +1,23 @@
+<!--
+  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.
+  -->
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" 
"http://www.w3.org/TR/html4/loose.dtd";>
+<html>
+<body>
+    <!-- Package description. -->
+    Contains implementation of embedded HTTP/HTTPS server to use in internal 
tests only.
+</body>
+</html>

Reply via email to