http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/bd28003b/modules/core/src/test/java/org/apache/ignite/testframework/junits/common/GridAbstractLifecycleAwareSelfTest.java
----------------------------------------------------------------------
diff --git 
a/modules/core/src/test/java/org/apache/ignite/testframework/junits/common/GridAbstractLifecycleAwareSelfTest.java
 
b/modules/core/src/test/java/org/apache/ignite/testframework/junits/common/GridAbstractLifecycleAwareSelfTest.java
new file mode 100644
index 0000000..a7c327d
--- /dev/null
+++ 
b/modules/core/src/test/java/org/apache/ignite/testframework/junits/common/GridAbstractLifecycleAwareSelfTest.java
@@ -0,0 +1,127 @@
+/*
+ * 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.common;
+
+import org.apache.ignite.*;
+import org.apache.ignite.lifecycle.*;
+import org.apache.ignite.resources.*;
+
+import java.util.*;
+import java.util.concurrent.atomic.*;
+
+/**
+ * Base class for tests against {@link 
org.apache.ignite.lifecycle.LifecycleAware} support.
+ */
+public abstract class GridAbstractLifecycleAwareSelfTest extends 
GridCommonAbstractTest {
+    /** */
+    protected Collection<TestLifecycleAware> lifecycleAwares = new 
ArrayList<>();
+
+    /**
+     */
+    @SuppressWarnings("PublicInnerClass")
+    public static class TestLifecycleAware implements LifecycleAware {
+        /** */
+        private AtomicInteger startCnt = new AtomicInteger();
+
+        /** */
+        private AtomicInteger stopCnt = new AtomicInteger();
+
+        /** */
+        @IgniteCacheNameResource
+        private String cacheName;
+
+        /** */
+        private final String expCacheName;
+
+        /**
+         * @param expCacheName Expected injected cache name.
+         */
+        public TestLifecycleAware(String expCacheName) {
+            this.expCacheName = expCacheName;
+        }
+
+        /** {@inheritDoc} */
+        @Override public void start() {
+            startCnt.incrementAndGet();
+
+            assertEquals("Unexpected cache name for " + this, expCacheName, 
cacheName);
+        }
+
+        /** {@inheritDoc} */
+        @Override public void stop() {
+            stopCnt.incrementAndGet();
+        }
+
+        /**
+         * @return Number of times {@link 
org.apache.ignite.lifecycle.LifecycleAware#start} was called.
+         */
+        public int startCount() {
+            return startCnt.get();
+        }
+
+        /**
+         * @return Number of times {@link 
org.apache.ignite.lifecycle.LifecycleAware#stop} was called.
+         */
+        public int stopCount() {
+            return stopCnt.get();
+        }
+
+        /**
+         * @param cacheName Cache name.
+         */
+        public void cacheName(String cacheName) {
+            this.cacheName = cacheName;
+        }
+    }
+
+    /**
+     * After grid start callback.
+     * @param ignite Grid.
+     */
+    protected void afterGridStart(Ignite ignite) {
+        // No-op.
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    public void testLifecycleAware() throws Exception {
+        Ignite ignite = startGrid();
+
+        afterGridStart(ignite);
+
+        assertFalse(lifecycleAwares.isEmpty());
+
+        for (TestLifecycleAware lifecycleAware : lifecycleAwares) {
+            assertEquals("Unexpected start count for " + lifecycleAware, 1, 
lifecycleAware.startCount());
+            assertEquals("Unexpected stop count for " + lifecycleAware, 0, 
lifecycleAware.stopCount());
+        }
+
+        try {
+            stopGrid();
+
+            for (TestLifecycleAware lifecycleAware : lifecycleAwares) {
+                assertEquals("Unexpected start count for " + lifecycleAware, 
1, lifecycleAware.startCount());
+                assertEquals("Unexpected stop count for " + lifecycleAware, 1, 
lifecycleAware.stopCount());
+            }
+        }
+        finally {
+            lifecycleAwares.clear();
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/bd28003b/modules/core/src/test/java/org/apache/ignite/testframework/junits/common/GridCommonAbstractTest.java
----------------------------------------------------------------------
diff --git 
a/modules/core/src/test/java/org/apache/ignite/testframework/junits/common/GridCommonAbstractTest.java
 
b/modules/core/src/test/java/org/apache/ignite/testframework/junits/common/GridCommonAbstractTest.java
new file mode 100644
index 0000000..9c0f6c9
--- /dev/null
+++ 
b/modules/core/src/test/java/org/apache/ignite/testframework/junits/common/GridCommonAbstractTest.java
@@ -0,0 +1,676 @@
+/*
+ * 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.common;
+
+import org.apache.ignite.*;
+import org.apache.ignite.cache.*;
+import org.apache.ignite.cache.affinity.*;
+import org.apache.ignite.cluster.*;
+import org.apache.ignite.compute.*;
+import org.apache.ignite.configuration.*;
+import org.apache.ignite.events.*;
+import org.apache.ignite.internal.*;
+import org.apache.ignite.lang.*;
+import org.apache.ignite.internal.processors.cache.distributed.dht.*;
+import org.apache.ignite.internal.processors.cache.distributed.dht.colocated.*;
+import org.apache.ignite.internal.processors.cache.distributed.near.*;
+import org.apache.ignite.internal.processors.cache.local.*;
+import org.apache.ignite.internal.util.typedef.*;
+import org.apache.ignite.internal.util.typedef.internal.*;
+import org.apache.ignite.testframework.*;
+import org.apache.ignite.testframework.junits.*;
+import org.jetbrains.annotations.*;
+
+import javax.net.ssl.*;
+import java.util.*;
+
+import static org.apache.ignite.internal.processors.cache.GridCacheUtils.*;
+import static org.apache.ignite.cache.GridCacheMode.*;
+import static org.apache.ignite.cache.GridCachePreloadMode.*;
+
+/**
+ * Super class for all common tests.
+ */
+public abstract class GridCommonAbstractTest extends GridAbstractTest {
+    /**
+     * @param startGrid If {@code true}, then grid node will be auto-started.
+     */
+    protected GridCommonAbstractTest(boolean startGrid) {
+        super(startGrid);
+    }
+
+    /** */
+    protected GridCommonAbstractTest() {
+        super(false);
+    }
+
+    /**
+     * @param idx Grid index.
+     * @return Cache.
+     */
+    protected <K, V> GridCache<K, V> cache(int idx) {
+        return grid(idx).cachex();
+    }
+
+    /**
+     * @param idx Grid index.
+     * @return Cache.
+     */
+    protected <K, V> IgniteCache<K, V> jcache(int idx) {
+        return grid(idx).jcache(null);
+    }
+
+    /**
+     * @param idx Grid index.
+     * @param name Cache name.
+     * @return Cache.
+     */
+    protected <K, V> GridCache<K, V> cache(int idx, String name) {
+        return grid(idx).cachex(name);
+    }
+
+    /**
+     * @return Cache.
+     */
+    protected <K, V> GridCache<K, V> cache() {
+        return grid().cachex();
+    }
+
+    /**
+     * @return Cache.
+     */
+    protected <K, V> IgniteCache<K, V> jcache() {
+        return grid().jcache(null);
+    }
+
+    /**
+     * @return Cache.
+     */
+    protected <K, V> GridLocalCache<K, V> local() {
+        return (GridLocalCache<K, V>)((GridKernal)grid()).<K, 
V>internalCache();
+    }
+
+    /**
+     * @param cache Cache.
+     * @return DHT cache.
+     */
+    protected static <K, V> GridDhtCacheAdapter<K, V> 
dht(GridCacheProjection<K,V> cache) {
+        return nearEnabled(cache) ? near(cache).dht() :
+            ((GridKernal)cache.gridProjection().ignite()).<K, 
V>internalCache(cache.name()).context().dht();
+    }
+
+    /**
+     * @return DHT cache.
+     */
+    protected <K, V> GridDhtCacheAdapter<K, V> dht() {
+        return this.<K, V>near().dht();
+    }
+
+    /**
+     * @param idx Grid index.
+     * @return DHT cache.
+     */
+    protected <K, V> GridDhtCacheAdapter<K, V> dht(int idx) {
+        return this.<K, V>near(idx).dht();
+    }
+
+    /**
+     * @param idx Grid index.
+     * @param cache Cache name.
+     * @return DHT cache.
+     */
+    protected <K, V> GridDhtCacheAdapter<K, V> dht(int idx, String cache) {
+        return this.<K, V>near(idx, cache).dht();
+    }
+
+    /**
+     * @param idx Grid index.
+     * @param cache Cache name.
+     * @return Colocated cache.
+     */
+    protected <K, V> GridDhtColocatedCache<K, V> colocated(int idx, String 
cache) {
+        return (GridDhtColocatedCache<K, 
V>)((GridKernal)grid(idx)).internalCache(cache);
+    }
+
+    /**
+     * @param cache Cache.
+     * @return {@code True} if near cache is enabled.
+     */
+    protected static <K, V> boolean nearEnabled(GridCacheProjection<K,V> 
cache) {
+        CacheConfiguration cfg = ((GridKernal)cache.gridProjection().ignite()).
+            <K, V>internalCache(cache.name()).context().config();
+
+        return isNearEnabled(cfg);
+    }
+
+    /**
+     * @param cache Cache.
+     * @return Near cache.
+     */
+    protected static <K, V> GridNearCacheAdapter<K, V> 
near(GridCacheProjection<K,V> cache) {
+        return ((GridKernal)cache.gridProjection().ignite()).<K, 
V>internalCache(cache.name()).context().near();
+    }
+
+    /**
+     * @param cache Cache.
+     * @return Colocated cache.
+     */
+    protected static <K, V> GridDhtColocatedCache<K, V> 
colocated(GridCacheProjection<K,V> cache) {
+        return ((GridKernal)cache.gridProjection().ignite()).<K, 
V>internalCache(cache.name()).context().colocated();
+    }
+
+    /**
+     * @return Near cache.
+     */
+    protected <K, V> GridNearCacheAdapter<K, V> near() {
+        return ((GridKernal)grid()).<K, V>internalCache().context().near();
+    }
+
+    /**
+     * @param idx Grid index.
+     * @return Near cache.
+     */
+    protected <K, V> GridNearCacheAdapter<K, V> near(int idx) {
+        return ((GridKernal)grid(idx)).<K, V>internalCache().context().near();
+    }
+
+    /**
+     * @param idx Grid index.
+     * @return Colocated cache.
+     */
+    protected <K, V> GridDhtColocatedCache<K, V> colocated(int idx) {
+        return (GridDhtColocatedCache<K, V>)((GridKernal)grid(idx)).<K, 
V>internalCache();
+    }
+
+    /**
+     * @param idx Grid index.
+     * @param cache Cache name.
+     * @return Near cache.
+     */
+    protected <K, V> GridNearCacheAdapter<K, V> near(int idx, String cache) {
+        return ((GridKernal)grid(idx)).<K, 
V>internalCache(cache).context().near();
+    }
+
+    /** {@inheritDoc} */
+    @Override protected final boolean isJunitFrameworkClass() {
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override protected final void setUp() throws Exception {
+        // Disable SSL hostname verifier.
+        HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
+            @Override public boolean verify(String s, SSLSession sslSes) {
+                return true;
+            }
+        });
+
+        getTestCounters().incrementStarted();
+
+        super.setUp();
+    }
+
+    /** {@inheritDoc} */
+    @Override protected final void tearDown() throws Exception {
+        getTestCounters().incrementStopped();
+
+        super.tearDown();
+    }
+
+    /** {@inheritDoc} */
+    @Override protected final Ignite startGridsMultiThreaded(int cnt) throws 
Exception {
+        Ignite g = super.startGridsMultiThreaded(cnt);
+
+        awaitPartitionMapExchange();
+
+        return g;
+    }
+
+    /**
+     * @throws InterruptedException If interrupted.
+     */
+    @SuppressWarnings("BusyWait")
+    protected void awaitPartitionMapExchange() throws InterruptedException {
+        for (Ignite g : G.allGrids()) {
+            for (GridCache<?, ?> c : ((GridEx)g).cachesx()) {
+                CacheConfiguration cfg = c.configuration();
+
+                if (cfg.getCacheMode() == PARTITIONED && cfg.getPreloadMode() 
!= NONE && g.cluster().nodes().size() > 1) {
+                    GridCacheAffinityFunction aff = cfg.getAffinity();
+
+                    GridDhtCacheAdapter<?, ?> dht = dht(c);
+
+                    GridDhtPartitionTopology<?, ?> top = dht.topology();
+
+                    for (int p = 0; p < aff.partitions(); p++) {
+                        long start = 0;
+
+                        for (int i = 0; ; i++) {
+                            // Must map on updated version of topology.
+                            Collection<ClusterNode> affNodes = 
c.affinity().mapPartitionToPrimaryAndBackups(p);
+
+                            int exp = affNodes.size();
+
+                            Collection<ClusterNode> owners = top.nodes(p, -1);
+
+                            int actual = owners.size();
+
+                            if (affNodes.size() != owners.size() || 
!affNodes.containsAll(owners)) {
+                                LT.warn(log(), null, "Waiting for topology map 
update [grid=" + g.name() +
+                                    ", p=" + p + ", nodes=" + exp + ", 
owners=" + actual +
+                                    ", affNodes=" + affNodes + ", owners=" + 
owners +
+                                    ", locNode=" + 
g.cluster().localNode().id() + ']');
+
+                                if (i == 0)
+                                    start = System.currentTimeMillis();
+
+                                Thread.sleep(200); // Busy wait.
+
+                                continue;
+                            }
+
+                            if (i > 0)
+                                log().warning("Finished waiting for topology 
map update [grid=" + g.name() +
+                                    ", p=" + p + ", duration=" + 
(System.currentTimeMillis() - start) + "ms]");
+
+                            break;
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * @param cache Cache.
+     * @return Key for which given cache is primary.
+     * @throws IgniteCheckedException If failed.
+     */
+    protected Integer primaryKey(GridCacheProjection<?, ?> cache)
+        throws IgniteCheckedException {
+        return primaryKeys(cache, 1, 1).get(0);
+    }
+
+    /**
+     * @param cache Cache.
+     * @param cnt Keys count.
+     * @return Collection of keys for which given cache is primary.
+     * @throws IgniteCheckedException If failed.
+     */
+    protected List<Integer> primaryKeys(GridCacheProjection<?, ?> cache, int 
cnt)
+        throws IgniteCheckedException {
+        return primaryKeys(cache, cnt, 1);
+    }
+
+    /**
+     * @param cache Cache.
+     * @param cnt Keys count.
+     * @param startFrom Start value for keys search.
+     * @return Collection of keys for which given cache is primary.
+     * @throws IgniteCheckedException If failed.
+     */
+    protected List<Integer> primaryKeys(GridCacheProjection<?, ?> cache, int 
cnt, int startFrom)
+        throws IgniteCheckedException {
+        assert cnt > 0 : cnt;
+
+        List<Integer> found = new ArrayList<>(cnt);
+
+        ClusterNode locNode = 
cache.gridProjection().ignite().cluster().localNode();
+
+        GridCacheAffinity<Integer> aff = cache.<Integer, 
Object>cache().affinity();
+
+        for (int i = startFrom; i < startFrom + 100_000; i++) {
+            Integer key = i;
+
+            if (aff.isPrimary(locNode, key)) {
+                found.add(key);
+
+                if (found.size() == cnt)
+                    return found;
+            }
+        }
+
+        throw new IgniteCheckedException("Unable to find " + cnt + " keys as 
primary for cache.");
+    }
+
+    /**
+     * @param cache Cache.
+     * @return Key for which given cache is backup.
+     * @throws IgniteCheckedException If failed.
+     */
+    protected Integer backupKey(GridCacheProjection<?, ?> cache)
+        throws IgniteCheckedException {
+        return backupKeys(cache, 1, 1).get(0);
+    }
+
+    /**
+     * @param cache Cache.
+     * @param cnt Keys count.
+     * @return Collection of keys for which given cache is backup.
+     * @throws IgniteCheckedException If failed.
+     */
+    protected List<Integer> backupKeys(GridCacheProjection<?, ?> cache, int 
cnt)
+        throws IgniteCheckedException {
+        return backupKeys(cache, cnt, 1);
+    }
+
+    /**
+     * @param cache Cache.
+     * @param cnt Keys count.
+     * @param startFrom Start value for keys search.
+     * @return Collection of keys for which given cache is backup.
+     * @throws IgniteCheckedException If failed.
+     */
+    protected List<Integer> backupKeys(GridCacheProjection<?, ?> cache, int 
cnt, int startFrom)
+        throws IgniteCheckedException {
+        assert cnt > 0 : cnt;
+
+        List<Integer> found = new ArrayList<>(cnt);
+
+        ClusterNode locNode = 
cache.gridProjection().ignite().cluster().localNode();
+
+        GridCacheAffinity<Integer> aff = cache.<Integer, 
Object>cache().affinity();
+
+        for (int i = startFrom; i < startFrom + 100_000; i++) {
+            Integer key = i;
+
+            if (aff.isBackup(locNode, key)) {
+                found.add(key);
+
+                if (found.size() == cnt)
+                    return found;
+            }
+        }
+
+        throw new IgniteCheckedException("Unable to find " + cnt + " keys as 
backup for cache.");
+    }
+
+    /**
+     * @param cache Cache.
+     * @return Keys for which given cache is neither primary nor backup.
+     * @throws IgniteCheckedException If failed.
+     */
+    protected Integer nearKey(GridCacheProjection<?, ?> cache)
+        throws IgniteCheckedException {
+        return nearKeys(cache, 1, 1).get(0);
+    }
+
+    /**
+     * @param cache Cache.
+     * @param cnt Keys count.
+     * @return Collection of keys for which given cache is neither primary nor 
backup.
+     * @throws IgniteCheckedException If failed.
+     */
+    protected List<Integer> nearKeys(GridCacheProjection<?, ?> cache, int cnt)
+        throws IgniteCheckedException {
+        return nearKeys(cache, cnt, 1);
+    }
+
+    /**
+     * @param cache Cache.
+     * @param cnt Keys count.
+     * @param startFrom Start value for keys search.
+     * @return Collection of keys for which given cache is neither primary nor 
backup.
+     * @throws IgniteCheckedException If failed.
+     */
+    protected List<Integer> nearKeys(GridCacheProjection<?, ?> cache, int cnt, 
int startFrom)
+        throws IgniteCheckedException {
+        assert cnt > 0 : cnt;
+
+        List<Integer> found = new ArrayList<>(cnt);
+
+        ClusterNode locNode = 
cache.gridProjection().ignite().cluster().localNode();
+
+        GridCacheAffinity<Integer> aff = cache.<Integer, 
Object>cache().affinity();
+
+        for (int i = startFrom; i < startFrom + 100_000; i++) {
+            Integer key = i;
+
+            if (!aff.isPrimaryOrBackup(locNode, key)) {
+                found.add(key);
+
+                if (found.size() == cnt)
+                    return found;
+            }
+        }
+
+        throw new IgniteCheckedException("Unable to find " + cnt + " keys as 
backup for cache.");
+    }
+
+    /**
+     * @param cache Cache.
+     * @param cnt Keys count.
+     * @param startFrom Start value for keys search.
+     * @return Collection of keys for which given cache is primary.
+     * @throws IgniteCheckedException If failed.
+     */
+    protected List<Integer> primaryKeys(IgniteCache<?, ?> cache, int cnt, int 
startFrom)
+        throws IgniteCheckedException {
+        GridCacheProjection<?, ?> prj = GridTestUtils.getFieldValue(cache, 
"delegate");
+
+        return primaryKeys(prj, cnt, startFrom);
+    }
+
+    /**
+     * @param cache Cache.
+     * @param cnt Keys count.
+     * @param startFrom Start value for keys search.
+     * @return Collection of keys for which given cache is backup.
+     * @throws IgniteCheckedException If failed.
+     */
+    protected List<Integer> backupKeys(IgniteCache<?, ?> cache, int cnt, int 
startFrom)
+        throws IgniteCheckedException {
+        GridCacheProjection<?, ?> prj = GridTestUtils.getFieldValue(cache, 
"delegate");
+
+        return backupKeys(prj, cnt, startFrom);
+    }
+
+    /**
+     * @param cache Cache.
+     * @param cnt Keys count.
+     * @param startFrom Start value for keys search.
+     * @return Collection of keys for which given cache is neither primary nor 
backup.
+     * @throws IgniteCheckedException If failed.
+     */
+    protected List<Integer> nearKeys(IgniteCache<?, ?> cache, int cnt, int 
startFrom)
+        throws IgniteCheckedException {
+        GridCacheProjection<?, ?> prj = GridTestUtils.getFieldValue(cache, 
"delegate");
+
+        return nearKeys(prj, cnt, startFrom);
+    }
+
+    /**
+     * @param key Key.
+     * @param cacheName Cache name.
+     * @return Cache.
+     */
+    protected <K, V> IgniteCache<K, V> primaryCache(Object key, @Nullable 
String cacheName) {
+        ClusterNode node = 
grid(0).cache(cacheName).affinity().mapKeyToNode(key);
+
+        assertNotNull(node);
+
+        return 
grid((String)node.attribute(GridNodeAttributes.ATTR_GRID_NAME)).jcache(cacheName);
+    }
+
+    /**
+     * @param cache Cache.
+     * @return Collection of keys for which given cache is primary.
+     * @throws IgniteCheckedException If failed.
+     */
+    protected Integer primaryKey(IgniteCache<?, ?> cache)
+        throws IgniteCheckedException {
+        GridCacheProjection<?, ?> prj = GridTestUtils.getFieldValue(cache, 
"delegate");
+
+        return primaryKey(prj);
+    }
+
+    /**
+     * @param cache Cache.
+     * @return Keys for which given cache is backup.
+     * @throws IgniteCheckedException If failed.
+     */
+    protected Integer backupKey(IgniteCache<?, ?> cache)
+        throws IgniteCheckedException {
+        GridCacheProjection<?, ?> prj = GridTestUtils.getFieldValue(cache, 
"delegate");
+
+        return backupKey(prj);
+    }
+
+    /**
+     * @param cache Cache.
+     * @return Key for which given cache is neither primary nor backup.
+     * @throws IgniteCheckedException If failed.
+     */
+    protected Integer nearKey(IgniteCache<?, ?> cache)
+        throws IgniteCheckedException {
+        GridCacheProjection<?, ?> prj = GridTestUtils.getFieldValue(cache, 
"delegate");
+
+        return nearKey(prj);
+    }
+
+    /**
+     * @param comp Compute.
+     * @param task Task.
+     * @param arg Task argument.
+     * @return Task future.
+     * @throws IgniteCheckedException If failed.
+     */
+    protected <R> ComputeTaskFuture<R> executeAsync(IgniteCompute comp, 
ComputeTask task, @Nullable Object arg)
+        throws IgniteCheckedException {
+        comp = comp.enableAsync();
+
+        assertNull(comp.execute(task, arg));
+
+        ComputeTaskFuture<R> fut = comp.future();
+
+        assertNotNull(fut);
+
+        return fut;
+    }
+
+    /**
+     * @param comp Compute.
+     * @param taskName Task name.
+     * @param arg Task argument.
+     * @return Task future.
+     * @throws IgniteCheckedException If failed.
+     */
+    protected <R> ComputeTaskFuture<R> executeAsync(IgniteCompute comp, String 
taskName, @Nullable Object arg)
+        throws IgniteCheckedException {
+        comp = comp.enableAsync();
+
+        assertNull(comp.execute(taskName, arg));
+
+        ComputeTaskFuture<R> fut = comp.future();
+
+        assertNotNull(fut);
+
+        return fut;
+    }
+
+    /**
+     * @param comp Compute.
+     * @param taskCls Task class.
+     * @param arg Task argument.
+     * @return Task future.
+     * @throws IgniteCheckedException If failed.
+     */
+    @SuppressWarnings("unchecked")
+    protected <R> ComputeTaskFuture<R> executeAsync(IgniteCompute comp, Class 
taskCls, @Nullable Object arg)
+        throws IgniteCheckedException {
+        comp = comp.enableAsync();
+
+        assertNull(comp.execute(taskCls, arg));
+
+        ComputeTaskFuture<R> fut = comp.future();
+
+        assertNotNull(fut);
+
+        return fut;
+    }
+
+    /**
+     * @param evts Events.
+     * @param filter Filter.
+     * @param types Events types.
+     * @return Future.
+     * @throws IgniteCheckedException If failed.
+     */
+    protected <T extends IgniteEvent> IgniteFuture<T> 
waitForLocalEvent(IgniteEvents evts,
+        @Nullable IgnitePredicate<T> filter, @Nullable int... types) throws 
IgniteCheckedException {
+        evts = evts.enableAsync();
+
+        assertTrue(evts.isAsync());
+
+        assertNull(evts.waitForLocal(filter, types));
+
+        IgniteFuture<T> fut = evts.future();
+
+        assertNotNull(fut);
+
+        return fut;
+    }
+
+    /**
+     * @param ignite Grid.
+     * @return {@link org.apache.ignite.IgniteCompute} for given grid's local 
node.
+     */
+    protected IgniteCompute forLocal(Ignite ignite) {
+        return ignite.compute(ignite.cluster().forLocal());
+    }
+
+    /**
+     * @param prj Projection.
+     * @return {@link org.apache.ignite.IgniteCompute} for given projection.
+     */
+    protected IgniteCompute compute(ClusterGroup prj) {
+        return prj.ignite().compute(prj);
+    }
+
+    /**
+     * @param prj Projection.
+     * @return {@link org.apache.ignite.IgniteMessaging} for given projection.
+     */
+    protected IgniteMessaging message(ClusterGroup prj) {
+        return prj.ignite().message(prj);
+    }
+
+    /**
+     * @param prj Projection.
+     * @return {@link org.apache.ignite.IgniteMessaging} for given projection.
+     */
+    protected IgniteEvents events(ClusterGroup prj) {
+        return prj.ignite().events(prj);
+    }
+
+    /**
+     * @param cfg Configuration.
+     * @param cacheName Cache name.
+     * @return Cache configuration.
+     */
+    protected CacheConfiguration cacheConfiguration(IgniteConfiguration cfg, 
String cacheName) {
+        for (CacheConfiguration ccfg : cfg.getCacheConfiguration()) {
+            if (F.eq(cacheName, ccfg.getName()))
+                return ccfg;
+        }
+
+        fail("Failed to find cache configuration for cache: " + cacheName);
+
+        return null;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/bd28003b/modules/core/src/test/java/org/apache/ignite/testframework/junits/common/GridCommonTest.java
----------------------------------------------------------------------
diff --git 
a/modules/core/src/test/java/org/apache/ignite/testframework/junits/common/GridCommonTest.java
 
b/modules/core/src/test/java/org/apache/ignite/testframework/junits/common/GridCommonTest.java
new file mode 100644
index 0000000..6553ba2
--- /dev/null
+++ 
b/modules/core/src/test/java/org/apache/ignite/testframework/junits/common/GridCommonTest.java
@@ -0,0 +1,36 @@
+/*
+ * 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.common;
+
+import java.lang.annotation.*;
+
+/**
+ * Base class for all tests.
+ */
+@Documented
+@Inherited
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE})
+public @interface GridCommonTest {
+    /**
+     * Optional group this test belongs to.
+     *
+     * @return Test group.
+     */
+    public String group() default "";
+}

http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/bd28003b/modules/core/src/test/java/org/apache/ignite/testframework/junits/common/package.html
----------------------------------------------------------------------
diff --git 
a/modules/core/src/test/java/org/apache/ignite/testframework/junits/common/package.html
 
b/modules/core/src/test/java/org/apache/ignite/testframework/junits/common/package.html
new file mode 100644
index 0000000..1f85ff2
--- /dev/null
+++ 
b/modules/core/src/test/java/org/apache/ignite/testframework/junits/common/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/junits/logger/GridLog4jRollingFileAppender.java
----------------------------------------------------------------------
diff --git 
a/modules/core/src/test/java/org/apache/ignite/testframework/junits/logger/GridLog4jRollingFileAppender.java
 
b/modules/core/src/test/java/org/apache/ignite/testframework/junits/logger/GridLog4jRollingFileAppender.java
new file mode 100644
index 0000000..7be2574
--- /dev/null
+++ 
b/modules/core/src/test/java/org/apache/ignite/testframework/junits/logger/GridLog4jRollingFileAppender.java
@@ -0,0 +1,114 @@
+/*
+ * 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.logger;
+
+import org.apache.ignite.*;
+import org.apache.ignite.logger.*;
+import org.apache.log4j.*;
+import org.apache.ignite.internal.util.typedef.internal.*;
+
+import java.io.*;
+import java.util.*;
+
+/**
+ * Log4J {@link org.apache.log4j.RollingFileAppender} with added support for 
grid node IDs.
+ */
+public class GridLog4jRollingFileAppender extends RollingFileAppender 
implements IgniteLoggerNodeIdAware {
+    /** Node ID. */
+    private UUID nodeId;
+
+    /** Basic log file name. */
+    private String baseFileName;
+
+    /**
+     * Default constructor (does not do anything).
+     */
+    public GridLog4jRollingFileAppender() {
+        init();
+    }
+
+    /**
+     * Instantiate a FileAppender with given parameters.
+     *
+     * @param layout Layout.
+     * @param filename File name.
+     * @throws java.io.IOException If failed.
+     */
+    public GridLog4jRollingFileAppender(Layout layout, String filename) throws 
IOException {
+        super(layout, filename);
+
+        init();
+    }
+
+    /**
+     * Instantiate a FileAppender with given parameters.
+     *
+     * @param layout Layout.
+     * @param filename File name.
+     * @param append Append flag.
+     * @throws java.io.IOException If failed.
+     */
+    public GridLog4jRollingFileAppender(Layout layout, String filename, 
boolean append) throws IOException {
+        super(layout, filename, append);
+
+        init();
+    }
+
+    /**
+     * Initializes appender.
+     */
+    private void init() {
+        GridTestLog4jLogger.addAppender(this);
+    }
+
+    /** {@inheritDoc} */
+    @SuppressWarnings("NonPrivateFieldAccessedInSynchronizedContext")
+    @Override public synchronized void setNodeId(UUID nodeId) {
+        A.notNull(nodeId, "nodeId");
+
+        this.nodeId = nodeId;
+
+        if (fileName != null) { // fileName could be null if GRIDGAIN_HOME is 
not defined.
+            if (baseFileName == null)
+                baseFileName = fileName;
+
+            fileName = U.nodeIdLogFileName(nodeId, baseFileName);
+        }
+        else {
+            String tmpDir = IgniteSystemProperties.getString("java.io.tmpdir");
+
+            if (tmpDir != null) {
+                baseFileName = new File(tmpDir, 
"gridgain.log").getAbsolutePath();
+
+                fileName = U.nodeIdLogFileName(nodeId, baseFileName);
+            }
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override public synchronized UUID getNodeId() {
+        return nodeId;
+    }
+
+    /** {@inheritDoc} */
+    @Override public synchronized void setFile(String fileName, boolean 
fileAppend, boolean bufIO, int bufSize)
+        throws IOException {
+        if (nodeId != null)
+            super.setFile(fileName, fileAppend, bufIO, bufSize);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/bd28003b/modules/core/src/test/java/org/apache/ignite/testframework/junits/logger/GridTestLog4jLogger.java
----------------------------------------------------------------------
diff --git 
a/modules/core/src/test/java/org/apache/ignite/testframework/junits/logger/GridTestLog4jLogger.java
 
b/modules/core/src/test/java/org/apache/ignite/testframework/junits/logger/GridTestLog4jLogger.java
new file mode 100644
index 0000000..4c27791
--- /dev/null
+++ 
b/modules/core/src/test/java/org/apache/ignite/testframework/junits/logger/GridTestLog4jLogger.java
@@ -0,0 +1,514 @@
+/*
+ * 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.logger;
+
+import org.apache.ignite.*;
+import org.apache.ignite.internal.util.*;
+import org.apache.ignite.lang.*;
+import org.apache.ignite.logger.*;
+import org.apache.log4j.*;
+import org.apache.log4j.varia.*;
+import org.apache.log4j.xml.*;
+import org.apache.ignite.internal.util.tostring.*;
+import org.apache.ignite.internal.util.typedef.*;
+import org.apache.ignite.internal.util.typedef.internal.*;
+import org.jetbrains.annotations.*;
+
+import java.io.*;
+import java.net.*;
+import java.util.*;
+
+import static org.apache.ignite.IgniteSystemProperties.*;
+
+/**
+ * Log4j-based implementation for logging. This logger should be used
+ * by loaders that have prefer <a target=_new 
href="http://logging.apache.org/log4j/docs/";>log4j</a>-based logging.
+ * <p>
+ * Here is a typical example of configuring log4j logger in GridGain 
configuration file:
+ * <pre name="code" class="xml">
+ *      &lt;property name="gridLogger"&gt;
+ *          &lt;bean class="org.gridgain.grid.logger.log4j.GridLog4jLogger"&gt;
+ *              &lt;constructor-arg type="java.lang.String" 
value="config/gridgain-log4j.xml"/&gt;
+ *          &lt;/bean>
+ *      &lt;/property&gt;
+ * </pre>
+ * and from your code:
+ * <pre name="code" class="java">
+ *      GridConfiguration cfg = new GridConfiguration();
+ *      ...
+ *      URL xml = U.resolveGridGainUrl("config/custom-log4j.xml");
+ *      GridLogger log = new GridLog4jLogger(xml);
+ *      ...
+ *      cfg.setGridLogger(log);
+ * </pre>
+ *
+ * Please take a look at <a target=_new 
href="http://logging.apache.org/log4j/1.2/index.html>Apache Log4j 1.2</a>
+ * for additional information.
+ * <p>
+ * It's recommended to use GridGain logger injection instead of 
using/instantiating
+ * logger in your task/job code. See {@link 
org.apache.ignite.resources.IgniteLoggerResource} annotation about logger
+ * injection.
+ */
+public class GridTestLog4jLogger implements IgniteLogger, 
IgniteLoggerNodeIdAware {
+    /** */
+    private static final long serialVersionUID = 0L;
+
+    /** Appenders. */
+    private static Collection<FileAppender> fileAppenders = new 
GridConcurrentHashSet<>();
+
+    /** */
+    private static volatile boolean inited;
+
+    /** */
+    private static volatile boolean quiet0;
+
+    /** */
+    private static final Object mux = new Object();
+
+    /** Logger implementation. */
+    @GridToStringExclude
+    @SuppressWarnings("FieldAccessedSynchronizedAndUnsynchronized")
+    private Logger impl;
+
+    /** Path to configuration file. */
+    private final String path;
+
+    /** Quiet flag. */
+    private final boolean quiet;
+
+    /** Node ID. */
+    private UUID nodeId;
+
+    /**
+     * Creates new logger and automatically detects if root logger already
+     * has appenders configured. If it does not, the root logger will be
+     * configured with default appender (analogous to calling
+     * {@link #GridTestLog4jLogger(boolean) GridLog4jLogger(boolean)}
+     * with parameter {@code true}, otherwise, existing appenders will be used 
(analogous
+     * to calling {@link #GridTestLog4jLogger(boolean) 
GridLog4jLogger(boolean)}
+     * with parameter {@code false}).
+     */
+    public GridTestLog4jLogger() {
+        this(!isConfigured());
+    }
+
+    /**
+     * Creates new logger. If initialize parameter is {@code true} the Log4j
+     * logger will be initialized with default console appender and {@code 
INFO}
+     * log level.
+     *
+     * @param init If {@code true}, then a default console appender with
+     *      following pattern layout will be created: {@code %d{ABSOLUTE} %-5p 
[%c{1}] %m%n}.
+     *      If {@code false}, then no implicit initialization will take place,
+     *      and {@code Log4j} should be configured prior to calling this
+     *      constructor.
+     */
+    public GridTestLog4jLogger(boolean init) {
+        impl = Logger.getRootLogger();
+
+        if (init) {
+            // Implementation has already been inited, passing NULL.
+            addConsoleAppenderIfNeeded(Level.INFO, null);
+
+            quiet = quiet0;
+        }
+        else
+            quiet = true;
+
+        path = null;
+    }
+
+    /**
+     * Creates new logger with given implementation.
+     *
+     * @param impl Log4j implementation to use.
+     */
+    public GridTestLog4jLogger(final Logger impl) {
+        assert impl != null;
+
+        path = null;
+
+        addConsoleAppenderIfNeeded(null, new C1<Boolean, Logger>() {
+            @Override public Logger apply(Boolean init) {
+                return impl;
+            }
+        });
+
+        quiet = quiet0;
+    }
+
+    /**
+     * Creates new logger with given configuration {@code path}.
+     *
+     * @param path Path to log4j configuration XML file.
+     * @throws IgniteCheckedException Thrown in case logger can't be created.
+     */
+    public GridTestLog4jLogger(String path) throws IgniteCheckedException {
+        if (path == null)
+            throw new IgniteCheckedException("Configuration XML file for Log4j 
must be specified.");
+
+        this.path = path;
+
+        final URL cfgUrl = U.resolveGridGainUrl(path);
+
+        if (cfgUrl == null)
+            throw new IgniteCheckedException("Log4j configuration path was not 
found: " + path);
+
+        addConsoleAppenderIfNeeded(null, new C1<Boolean, Logger>() {
+            @Override public Logger apply(Boolean init) {
+                if (init)
+                    DOMConfigurator.configure(cfgUrl);
+
+                return Logger.getRootLogger();
+            }
+        });
+
+        quiet = quiet0;
+    }
+
+    /**
+     * Creates new logger with given configuration {@code cfgFile}.
+     *
+     * @param cfgFile Log4j configuration XML file.
+     * @throws IgniteCheckedException Thrown in case logger can't be created.
+     */
+    public GridTestLog4jLogger(File cfgFile) throws IgniteCheckedException {
+        if (cfgFile == null)
+            throw new IgniteCheckedException("Configuration XML file for Log4j 
must be specified.");
+
+        if (!cfgFile.exists() || cfgFile.isDirectory())
+            throw new IgniteCheckedException("Log4j configuration path was not 
found or is a directory: " + cfgFile);
+
+        path = cfgFile.getAbsolutePath();
+
+        addConsoleAppenderIfNeeded(null, new C1<Boolean, Logger>() {
+            @Override public Logger apply(Boolean init) {
+                if (init)
+                    DOMConfigurator.configure(path);
+
+                return Logger.getRootLogger();
+            }
+        });
+
+        quiet = quiet0;
+    }
+
+    /**
+     * Creates new logger with given configuration {@code cfgUrl}.
+     *
+     * @param cfgUrl URL for Log4j configuration XML file.
+     * @throws IgniteCheckedException Thrown in case logger can't be created.
+     */
+    public GridTestLog4jLogger(final URL cfgUrl) throws IgniteCheckedException 
{
+        if (cfgUrl == null)
+            throw new IgniteCheckedException("Configuration XML file for Log4j 
must be specified.");
+
+        path = null;
+
+        addConsoleAppenderIfNeeded(null, new C1<Boolean, Logger>() {
+            @Override public Logger apply(Boolean init) {
+                if (init)
+                    DOMConfigurator.configure(cfgUrl);
+
+                return Logger.getRootLogger();
+            }
+        });
+
+        quiet = quiet0;
+    }
+
+    /**
+     * Checks if Log4j is already configured within this VM or not.
+     *
+     * @return {@code True} if log4j was already configured, {@code false} 
otherwise.
+     */
+    public static boolean isConfigured() {
+        return Logger.getRootLogger().getAllAppenders().hasMoreElements();
+    }
+
+    /**
+     * Sets level for internal log4j implementation.
+     *
+     * @param level Log level to set.
+     */
+    public void setLevel(Level level) {
+        impl.setLevel(level);
+    }
+
+    /** {@inheritDoc} */
+    @Nullable @Override public String fileName() {
+        FileAppender fapp = F.first(fileAppenders);
+
+        return fapp != null ? fapp.getFile() : null;
+    }
+
+    /**
+     * Adds console appender when needed with some default logging settings.
+     *
+     * @param logLevel Optional log level.
+     * @param implInitC Optional log implementation init closure.
+     */
+    private void addConsoleAppenderIfNeeded(@Nullable Level logLevel,
+        @Nullable IgniteClosure<Boolean, Logger> implInitC) {
+        if (inited) {
+            if (implInitC != null)
+                // Do not init.
+                impl = implInitC.apply(false);
+
+            return;
+        }
+
+        synchronized (mux) {
+            if (inited) {
+                if (implInitC != null)
+                    // Do not init.
+                    impl = implInitC.apply(false);
+
+                return;
+            }
+
+            if (implInitC != null)
+                // Init logger impl.
+                impl = implInitC.apply(true);
+
+            boolean quiet = Boolean.valueOf(System.getProperty(GG_QUIET, 
"true"));
+
+            boolean consoleAppenderFound = false;
+            Category rootCategory = null;
+            ConsoleAppender errAppender = null;
+
+            for (Category l = impl; l != null; ) {
+                if (!consoleAppenderFound) {
+                    for (Enumeration appenders = l.getAllAppenders(); 
appenders.hasMoreElements(); ) {
+                        Appender appender = (Appender)appenders.nextElement();
+
+                        if (appender instanceof ConsoleAppender) {
+                            if ("CONSOLE_ERR".equals(appender.getName())) {
+                                // Treat CONSOLE_ERR appender as a system one 
and don't count it.
+                                errAppender = (ConsoleAppender)appender;
+
+                                continue;
+                            }
+
+                            consoleAppenderFound = true;
+
+                            break;
+                        }
+                    }
+                }
+
+                if (l.getParent() == null) {
+                    rootCategory = l;
+
+                    break;
+                }
+                else
+                    l = l.getParent();
+            }
+
+            if (consoleAppenderFound && quiet)
+                // User configured console appender, but log is quiet.
+                quiet = false;
+
+            if (!consoleAppenderFound && !quiet && 
Boolean.valueOf(System.getProperty(GG_CONSOLE_APPENDER, "true"))) {
+                // Console appender not found => we've looked through all 
categories up to root.
+                assert rootCategory != null;
+
+                // User launched gridgain in verbose mode and did not add 
console appender with INFO level
+                // to configuration and did not set GG_CONSOLE_APPENDER to 
false.
+                if (errAppender != null) {
+                    
rootCategory.addAppender(createConsoleAppender(Level.INFO));
+
+                    if (errAppender.getThreshold() == Level.ERROR)
+                        errAppender.setThreshold(Level.WARN);
+                }
+                else
+                    // No error console appender => create console appender 
with no level limit.
+                    rootCategory.addAppender(createConsoleAppender(Level.OFF));
+
+                if (logLevel != null)
+                    impl.setLevel(logLevel);
+            }
+
+            quiet0 = quiet;
+            inited = true;
+        }
+    }
+
+    /**
+     * Creates console appender with some reasonable default logging settings.
+     *
+     * @param maxLevel Max logging level.
+     * @return New console appender.
+     */
+    private Appender createConsoleAppender(Level maxLevel) {
+        String fmt = "[%d{ABSOLUTE}][%-5p][%t][%c{1}] %m%n";
+
+        // Configure output that should go to System.out
+        Appender app = new ConsoleAppender(new PatternLayout(fmt), 
ConsoleAppender.SYSTEM_OUT);
+
+        LevelRangeFilter lvlFilter = new LevelRangeFilter();
+
+        lvlFilter.setLevelMin(Level.TRACE);
+        lvlFilter.setLevelMax(maxLevel);
+
+        app.addFilter(lvlFilter);
+
+        return app;
+    }
+
+    /**
+     * Adds file appender.
+     *
+     * @param a Appender.
+     */
+    public static void addAppender(FileAppender a) {
+        A.notNull(a, "a");
+
+        fileAppenders.add(a);
+    }
+
+    /**
+     * Removes file appender.
+     *
+     * @param a Appender.
+     */
+    public static void removeAppender(FileAppender a) {
+        A.notNull(a, "a");
+
+        fileAppenders.remove(a);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void setNodeId(UUID nodeId) {
+        A.notNull(nodeId, "nodeId");
+
+        this.nodeId = nodeId;
+
+        for (FileAppender a : fileAppenders) {
+            if (a instanceof IgniteLoggerNodeIdAware) {
+                ((IgniteLoggerNodeIdAware)a).setNodeId(nodeId);
+
+                a.activateOptions();
+            }
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override public UUID getNodeId() {
+        return nodeId;
+    }
+
+    /**
+     * Gets files for all registered file appenders.
+     *
+     * @return List of files.
+     */
+    public static Collection<String> logFiles() {
+        Collection<String> res = new ArrayList<>(fileAppenders.size());
+
+        for (FileAppender a : fileAppenders)
+            res.add(a.getFile());
+
+        return res;
+    }
+
+    /**
+     * Gets {@link org.apache.ignite.IgniteLogger} wrapper around log4j logger 
for the given
+     * category. If category is {@code null}, then root logger is returned. If
+     * category is an instance of {@link Class} then {@code 
(Class)ctgr).getName()}
+     * is used as category name.
+     *
+     * @param ctgr {@inheritDoc}
+     * @return {@link org.apache.ignite.IgniteLogger} wrapper around log4j 
logger.
+     */
+    @Override public GridTestLog4jLogger getLogger(Object ctgr) {
+        return new GridTestLog4jLogger(ctgr == null ? Logger.getRootLogger() :
+            ctgr instanceof Class ? 
Logger.getLogger(((Class<?>)ctgr).getName()) :
+                Logger.getLogger(ctgr.toString()));
+    }
+
+    /** {@inheritDoc} */
+    @Override public void trace(String msg) {
+        if (!impl.isTraceEnabled())
+            warning("Logging at TRACE level without checking if TRACE level is 
enabled: " + msg);
+
+        impl.trace(msg);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void debug(String msg) {
+        if (!impl.isDebugEnabled())
+            warning("Logging at DEBUG level without checking if DEBUG level is 
enabled: " + msg);
+
+        impl.debug(msg);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void info(String msg) {
+        if (!impl.isInfoEnabled())
+            warning("Logging at INFO level without checking if INFO level is 
enabled: " + msg);
+
+        impl.info(msg);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void warning(String msg) {
+        impl.warn(msg);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void warning(String msg, @Nullable Throwable e) {
+        impl.warn(msg, e);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void error(String msg) {
+        impl.error(msg);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void error(String msg, @Nullable Throwable e) {
+        impl.error(msg, e);
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean isTraceEnabled() {
+        return impl.isTraceEnabled();
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean isDebugEnabled() {
+        return impl.isDebugEnabled();
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean isInfoEnabled() {
+        return impl.isInfoEnabled();
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean isQuiet() {
+        return quiet;
+    }
+
+    /** {@inheritDoc} */
+    @Override public String toString() {
+        return S.toString(GridTestLog4jLogger.class, this);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/bd28003b/modules/core/src/test/java/org/apache/ignite/testframework/junits/package.html
----------------------------------------------------------------------
diff --git 
a/modules/core/src/test/java/org/apache/ignite/testframework/junits/package.html
 
b/modules/core/src/test/java/org/apache/ignite/testframework/junits/package.html
new file mode 100644
index 0000000..135eb1a
--- /dev/null
+++ 
b/modules/core/src/test/java/org/apache/ignite/testframework/junits/package.html
@@ -0,0 +1,24 @@
+<!--
+  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/junits/spi/GridSpiAbstractConfigTest.java
----------------------------------------------------------------------
diff --git 
a/modules/core/src/test/java/org/apache/ignite/testframework/junits/spi/GridSpiAbstractConfigTest.java
 
b/modules/core/src/test/java/org/apache/ignite/testframework/junits/spi/GridSpiAbstractConfigTest.java
new file mode 100644
index 0000000..91087bc
--- /dev/null
+++ 
b/modules/core/src/test/java/org/apache/ignite/testframework/junits/spi/GridSpiAbstractConfigTest.java
@@ -0,0 +1,110 @@
+/*
+ * 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.spi;
+
+import org.apache.ignite.spi.*;
+import org.apache.ignite.spi.discovery.*;
+import org.jetbrains.annotations.*;
+
+import java.lang.reflect.*;
+
+/**
+ * Base class for SPI configuration tests.
+ * @param <T> Type of tested SPI.
+ */
+public abstract class GridSpiAbstractConfigTest<T extends IgniteSpi> extends 
GridSpiAbstractTest<T> {
+    /** Default constructor. */
+    protected GridSpiAbstractConfigTest() {
+        super(false);
+    }
+
+    /**
+     * Checks that unacceptable property value prevents SPI from being started.
+     *
+     * @param spi Spi to test property on.
+     * @param propName name of property to check.
+     * @param val An illegal value.
+     * @throws Exception If check failed.
+     */
+    protected void checkNegativeSpiProperty(IgniteSpi spi, String propName, 
@Nullable Object val) throws Exception {
+        checkNegativeSpiProperty(spi, propName, val, true);
+    }
+
+    /**
+     * Checks that unacceptable property value prevents SPI from being started.
+     *
+     * @param spi Spi to test property on.
+     * @param propName name of property to check.
+     * @param val An illegal value.
+     * @param checkExMsg If {@code true} then additional info will be added to 
failure.
+     * @throws Exception If check failed.
+     */
+    protected void checkNegativeSpiProperty(IgniteSpi spi, String propName, 
Object val, boolean checkExMsg)
+        throws Exception {
+        assert spi != null;
+        assert propName != null;
+
+        getTestData().getTestResources().inject(spi);
+
+        String mtdName = "set" + propName.substring(0, 1).toUpperCase() + 
propName.substring(1);
+
+        Method mtd = null;
+
+        for (Method m : spi.getClass().getMethods())
+            if (m.getName().equals(mtdName)) {
+                mtd = m;
+
+                break;
+            }
+
+        assert mtd != null : "The setter is not found for property: " + 
propName;
+
+        boolean err = false;
+
+        try {
+            mtd.invoke(spi, val);
+        }
+        catch (InvocationTargetException e) {
+            info("SPI property setter thrown exception: " + e);
+
+            if (e.getCause() instanceof IllegalArgumentException)
+                err = true;
+            else
+                throw e;
+        }
+
+        if (!err)
+            try {
+                if (!(spi instanceof DiscoverySpi))
+                    spi.getNodeAttributes();
+
+                spi.spiStart(getTestGridName());
+            }
+            catch (IgniteSpiException e) {
+                info("SPI start thrown exception: " + e);
+
+                if (checkExMsg)
+                    assert e.getMessage().contains("SPI parameter failed 
condition check: ") :
+                        "SPI has returned wrong exception message [propName=" 
+ propName + ", msg=" + e + ']';
+
+                err = true;
+            }
+
+        assert err : "No check for property [property=" + propName +", value=" 
+ val + ']';
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/bd28003b/modules/core/src/test/java/org/apache/ignite/testframework/junits/spi/GridSpiAbstractTest.java
----------------------------------------------------------------------
diff --git 
a/modules/core/src/test/java/org/apache/ignite/testframework/junits/spi/GridSpiAbstractTest.java
 
b/modules/core/src/test/java/org/apache/ignite/testframework/junits/spi/GridSpiAbstractTest.java
new file mode 100644
index 0000000..2d5108f
--- /dev/null
+++ 
b/modules/core/src/test/java/org/apache/ignite/testframework/junits/spi/GridSpiAbstractTest.java
@@ -0,0 +1,692 @@
+/*
+ * 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.spi;
+
+import org.apache.ignite.cluster.*;
+import org.apache.ignite.internal.*;
+import org.apache.ignite.product.*;
+import org.apache.ignite.spi.*;
+import org.apache.ignite.internal.managers.security.*;
+import org.apache.ignite.plugin.security.*;
+import org.apache.ignite.spi.communication.*;
+import org.apache.ignite.spi.communication.tcp.*;
+import org.apache.ignite.spi.discovery.*;
+import org.apache.ignite.spi.discovery.tcp.*;
+import org.apache.ignite.spi.discovery.tcp.ipfinder.*;
+import org.apache.ignite.spi.discovery.tcp.ipfinder.multicast.*;
+import org.apache.ignite.spi.discovery.tcp.ipfinder.vm.*;
+import org.apache.ignite.internal.util.typedef.internal.*;
+import org.apache.ignite.testframework.*;
+import org.apache.ignite.testframework.junits.*;
+import org.apache.ignite.testframework.junits.spi.GridSpiTestConfig.*;
+import org.jetbrains.annotations.*;
+
+import java.io.*;
+import java.lang.reflect.*;
+import java.util.*;
+import java.util.concurrent.*;
+
+import static org.apache.ignite.product.IgniteProductVersion.*;
+
+/**
+ * Base SPI test class.
+ * @param <T> SPI implementation class.
+ */
+@SuppressWarnings({"JUnitTestCaseWithNonTrivialConstructors"})
+public abstract class GridSpiAbstractTest<T extends IgniteSpi> extends 
GridAbstractTest {
+    /** */
+    private static final IgniteProductVersion VERSION = fromString("99.99.99");
+
+    /** */
+    private static final Map<Class<?>, TestData<?>> tests = new 
ConcurrentHashMap<>();
+
+    /** */
+    private final boolean autoStart;
+
+    /** Original context classloader. */
+    private ClassLoader cl;
+
+    /** */
+    protected GridSpiAbstractTest() {
+        super(false);
+
+        autoStart = true;
+    }
+
+    /**
+     * @param autoStart Start automatically.
+     */
+    protected GridSpiAbstractTest(boolean autoStart) {
+        super(false);
+
+        this.autoStart = autoStart;
+    }
+
+    /** {@inheritDoc} */
+    @Override protected final boolean isJunitFrameworkClass() {
+        return true;
+    }
+
+    /**
+     * @return Test data.
+     */
+    @SuppressWarnings({"unchecked"})
+    protected TestData<T> getTestData() {
+        TestData<T> data = (TestData<T>)tests.get(getClass());
+
+        if (data == null)
+            tests.put(getClass(), data = new TestData<>());
+
+        return data;
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    private void resetTestData() throws Exception {
+        tests.put(getClass(), new TestData<T>());
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    @Override protected final void setUp() throws Exception {
+        // Need to change classloader here, although it also handled in the 
parent class
+        // the current test initialisation procedure doesn't allow us to setUp 
the parent first.
+        cl = Thread.currentThread().getContextClassLoader();
+
+        
Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
+
+        TestCounters cntrs = getTestCounters();
+
+        if (cntrs.isReset())
+            cntrs.reset();
+
+        cntrs.incrementStarted();
+
+        if (autoStart && isFirstTest()) {
+            GridSpiTest spiTest = GridTestUtils.getAnnotation(getClass(), 
GridSpiTest.class);
+
+            assert spiTest != null;
+
+            beforeSpiStarted();
+
+            if (spiTest.trigger())
+                spiStart();
+
+            info("==== Started spi test [test=" + getClass().getSimpleName() + 
"] ====");
+        }
+
+        super.setUp();
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    protected void beforeSpiStarted() throws Exception {
+        // No-op.
+    }
+
+    /** {@inheritDoc} */
+    @Override protected final GridTestResources getTestResources() {
+        return getTestData().getTestResources();
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    @SuppressWarnings({"unchecked", "CastToIncompatibleInterface", 
"InstanceofIncompatibleInterface"})
+    protected final void spiStart() throws Exception {
+        GridSpiTest spiTest = GridTestUtils.getAnnotation(getClass(), 
GridSpiTest.class);
+
+        assert spiTest != null;
+
+        T spi = (T)spiTest.spi().newInstance();
+
+        // Set spi into test data.
+        getTestData().setSpi(spi);
+
+        if (!(spi instanceof DiscoverySpi)) {
+            if (spiTest.triggerDiscovery())
+                configureDiscovery(spiTest);
+        }
+        else
+            getTestData().setDiscoverySpi((DiscoverySpi)spi);
+
+        getTestResources().inject(spi);
+
+        spiConfigure(spi);
+
+        Map<String, Object> attrs = spi.getNodeAttributes();
+
+        // Set up SPI class name and SPI version.
+        Map<String, Serializable> spiAttrs = 
initSpiClassAndVersionAttributes(spi);
+
+        if (attrs != null)
+            getTestData().getAttributes().putAll(attrs);
+
+        if (spiAttrs != null)
+            getTestData().getAttributes().putAll(spiAttrs);
+
+        Map<String, Serializable> nodeAttrs = getNodeAttributes();
+
+        if (nodeAttrs != null)
+            getTestData().getAttributes().putAll(nodeAttrs);
+
+        DiscoverySpi discoSpi = getTestData().getDiscoverySpi();
+
+        if (spiTest.triggerDiscovery() && !getTestData().isDiscoveryTest()) {
+            getTestData().getAttributes().putAll(
+                initSpiClassAndVersionAttributes(discoSpi));
+
+            // Set all local node attributes into discovery SPI.
+            discoSpi.setNodeAttributes(getTestData().getAttributes(), VERSION);
+
+            discoSpi.setMetricsProvider(createMetricsProvider());
+
+            discoSpi.setDataExchange(new DiscoverySpiDataExchange() {
+                @Override public List<Object> collect(UUID nodeId) {
+                    return new ArrayList<>();
+                }
+
+                @Override public void onExchange(List<Object> data) {
+                }
+            });
+
+            try {
+                spiStart(discoSpi);
+            }
+            catch (Exception e) {
+                spiStop(discoSpi);
+
+                throw e;
+            }
+        }
+
+        if (spi instanceof DiscoverySpi) {
+            
getTestData().getAttributes().putAll(initSpiClassAndVersionAttributes(spi));
+
+            
((DiscoverySpi)spi).setNodeAttributes(getTestData().getAttributes(), VERSION);
+
+            ((DiscoverySpi)spi).setMetricsProvider(createMetricsProvider());
+        }
+
+        try {
+            spiStart(spi);
+        }
+        catch (Exception e) {
+            spiStop(spi);
+
+            if (discoSpi != null && !discoSpi.equals(spi))
+                spiStop(discoSpi);
+
+            afterSpiStopped();
+
+            throw e;
+        }
+
+        // Initialize SPI context.
+        getTestData().setSpiContext(initSpiContext());
+
+        // Initialize discovery SPI only once.
+        if (discoSpi != null && !discoSpi.equals(spi))
+            discoSpi.onContextInitialized(getSpiContext());
+
+        spi.onContextInitialized(getTestData().getSpiContext());
+    }
+
+    /**
+     * @return SPI context.
+     * @throws Exception If anything failed.
+     */
+    protected GridSpiTestContext initSpiContext() throws Exception {
+        GridSpiTestContext spiCtx = new GridSpiTestContext();
+
+        if (getTestData().getDiscoverySpi() != null) {
+            
spiCtx.setLocalNode(getTestData().getDiscoverySpi().getLocalNode());
+
+            for (ClusterNode node : 
getTestData().getDiscoverySpi().getRemoteNodes())
+                spiCtx.addNode(node);
+        }
+        else {
+            GridTestNode node = new GridTestNode(UUID.randomUUID());
+
+            spiCtx.setLocalNode(node);
+
+            if (getSpi() != null) {
+                // Set up SPI class name and SPI version.
+                Map<String, Serializable> attrs = 
initSpiClassAndVersionAttributes(getSpi());
+
+                for (Map.Entry<String, Serializable> entry: attrs.entrySet())
+                    node.addAttribute(entry.getKey(), entry.getValue());
+            }
+        }
+
+        return spiCtx;
+    }
+
+    /**
+     * @param spi SPI to create attributes for.
+     * @return Map of attributes.
+     */
+    private Map<String, Serializable> 
initSpiClassAndVersionAttributes(IgniteSpi spi) {
+        Map<String, Serializable> attrs = new HashMap<>();
+
+        attrs.put(U.spiAttribute(spi, GridNodeAttributes.ATTR_SPI_CLASS), 
spi.getClass().getName());
+
+        return attrs;
+    }
+
+    /**
+     * @return SPI context.
+     * @throws Exception If anything failed.
+     */
+    protected final GridSpiTestContext getSpiContext() throws Exception {
+        return getTestData().getSpiContext();
+    }
+
+    /**
+     * @param spiTest SPI test annotation.
+     * @throws Exception If anything failed.
+     */
+    private void configureDiscovery(GridSpiTest spiTest) throws Exception {
+        DiscoverySpi discoSpi = spiTest.discoverySpi().newInstance();
+
+        if (discoSpi instanceof TcpDiscoverySpi) {
+            TcpDiscoverySpi tcpDisco = (TcpDiscoverySpi)discoSpi;
+
+            tcpDisco.setIpFinder(new TcpDiscoveryVmIpFinder(true));
+        }
+
+        getTestData().setDiscoverySpi(discoSpi);
+
+        getTestResources().inject(discoSpi);
+
+        discoSpi.setAuthenticator(new DiscoverySpiNodeAuthenticator() {
+            @Override public GridSecurityContext authenticateNode(ClusterNode 
n, GridSecurityCredentials cred) {
+                GridSecuritySubjectAdapter subj = new 
GridSecuritySubjectAdapter(
+                    GridSecuritySubjectType.REMOTE_NODE, n.id());
+
+                subj.permissions(new GridAllowAllPermissionSet());
+
+                return new GridSecurityContext(subj);
+            }
+
+            @Override public boolean isGlobalNodeAuthentication() {
+                return false;
+            }
+        });
+
+        configure(discoSpi);
+
+        if (discoSpi.getNodeAttributes() != null)
+            getTestData().getAttributes().putAll(discoSpi.getNodeAttributes());
+    }
+
+    /**
+     * Creates metrics provider just for testing purposes. The provider
+     * will not return valid node metrics.
+     *
+     * @return Dummy metrics provider.
+     */
+    protected DiscoveryMetricsProvider createMetricsProvider() {
+        return new DiscoveryMetricsProvider() {
+            /** {@inheritDoc} */
+            @Override public ClusterNodeMetrics getMetrics() { return new 
DiscoveryNodeMetricsAdapter(); }
+        };
+    }
+
+    /**
+     * @param spi SPI.
+     * @throws Exception If failed.
+     */
+    protected void spiConfigure(T spi) throws Exception {
+        configure(spi);
+    }
+
+    /**
+     * @param spi SPI.
+     * @throws Exception If failed.
+     * @throws IllegalAccessException If failed.
+     * @throws InvocationTargetException If failed.
+     */
+    private void configure(IgniteSpi spi) throws Exception {
+        // Inject Configuration.
+        for (Method m : getClass().getMethods()) {
+            GridSpiTestConfig cfg = m.getAnnotation(GridSpiTestConfig.class);
+
+            if (cfg != null) {
+                if (getTestData().isDiscoveryTest() ||
+                    (cfg.type() != ConfigType.DISCOVERY && !(spi instanceof 
DiscoverySpi)) ||
+                    (cfg.type() != ConfigType.SELF && spi instanceof 
DiscoverySpi)) {
+                    assert m.getName().startsWith("get") : "Test configuration 
must be a getter [method=" +
+                        m.getName() + ']';
+
+                    // Determine getter name.
+                    String name = cfg.setterName();
+
+                    if (name == null || name.isEmpty())
+                        name = 's' + m.getName().substring(1);
+
+                    Method setter = getMethod(spi.getClass(), name);
+
+                    assert setter != null : "Spi does not have setter for 
configuration property [spi=" +
+                        spi.getClass().getName() + ", config-prop=" + name + 
']';
+
+                    // Inject configuration parameter into spi.
+                    setter.invoke(spi, m.invoke(this));
+                }
+            }
+        }
+
+        // Our SPI tests should not have the same parameters otherwise they
+        // will find each other.
+        if (spi instanceof TcpCommunicationSpi)
+            
((TcpCommunicationSpi)spi).setLocalPort(GridTestUtils.getNextCommPort(getClass()));
+
+        if (spi instanceof TcpDiscoverySpi) {
+            TcpDiscoveryIpFinder ipFinder = 
((TcpDiscoverySpi)spi).getIpFinder();
+
+            if (ipFinder instanceof TcpDiscoveryMulticastIpFinder) {
+                String mcastAddr = 
GridTestUtils.getNextMulticastGroup(getClass());
+
+                if (mcastAddr != null && !mcastAddr.isEmpty()) {
+                    
((TcpDiscoveryMulticastIpFinder)ipFinder).setMulticastGroup(mcastAddr);
+                    ((TcpDiscoveryMulticastIpFinder)ipFinder).setMulticastPort(
+                        GridTestUtils.getNextMulticastPort(getClass()));
+                }
+            }
+        }
+    }
+
+    /**
+     * @param spi SPI.
+     * @throws Exception If failed.
+     */
+    protected void spiStart(IgniteSpi spi) throws Exception {
+        U.setWorkDirectory(null, U.getGridGainHome());
+
+        // Start SPI with unique grid name.
+        spi.spiStart(getTestGridName());
+
+        info("SPI started [spi=" + spi.getClass() + ']');
+    }
+
+    /**
+     * @return  Fully initialized and started SPI implementation.
+     */
+    protected final T getSpi() {
+        return getTestData().getSpi();
+    }
+
+    /**
+     * Gets class of the SPI implementation.
+     *
+     * @return Class of the SPI implementation.
+     */
+    @SuppressWarnings({"unchecked"})
+    protected final Class<? extends T> getSpiClass() {
+        GridSpiTest spiTest = GridTestUtils.getAnnotation(getClass(), 
GridSpiTest.class);
+
+        assert spiTest != null;
+
+        return (Class<? extends T>)spiTest.spi();
+    }
+
+    /**
+     * @return Node UUID.
+     * @throws Exception If failed.
+     */
+    protected UUID getNodeId() throws Exception {
+        return getTestResources().getNodeId();
+    }
+
+    /**
+     * @return Discovery SPI.
+     * @throws Exception If failed.
+     */
+    @Nullable protected final DiscoverySpi getDiscoverySpi() throws Exception {
+        if (getTestData() != null)
+            return getTestData().getDiscoverySpi();
+
+        return null;
+    }
+
+    /**
+     * Override this method for put local node attributes to discovery SPI.
+     *
+     * @return Always {@code null}.
+     */
+    @Nullable protected Map<String, Serializable> getNodeAttributes() {
+        return null;
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    @Override protected final void tearDown() throws Exception {
+        getTestCounters().incrementStopped();
+
+        boolean wasLast = isLastTest();
+
+        super.tearDown();
+
+        if (autoStart && wasLast) {
+            GridSpiTest spiTest = GridTestUtils.getAnnotation(getClass(), 
GridSpiTest.class);
+
+            assert spiTest != null;
+
+            if (spiTest.trigger()) {
+                spiStop();
+
+                afterSpiStopped();
+            }
+
+            info("==== Stopped spi test [test=" + getClass().getSimpleName() + 
"] ====");
+        }
+
+        Thread.currentThread().setContextClassLoader(cl);
+    }
+
+    /**
+     * @throws Exception If failed..
+     */
+    protected void afterSpiStopped() throws Exception {
+        // No-op.
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    protected final void spiStop() throws Exception {
+        TestData<T> testData = getTestData();
+
+        if (testData.getSpi() == null)
+            return;
+
+        testData.getSpi().onContextDestroyed();
+
+        spiStop(testData.getSpi());
+
+        GridSpiTest spiTest = GridTestUtils.getAnnotation(getClass(), 
GridSpiTest.class);
+
+        assert spiTest != null;
+
+        if (!testData.isDiscoveryTest() && spiTest.triggerDiscovery()) {
+            testData.getDiscoverySpi().onContextDestroyed();
+
+            spiStop(testData.getDiscoverySpi());
+        }
+
+        getTestResources().stopThreads();
+
+        resetTestData();
+    }
+
+    /**
+     * @param spi SPI.
+     * @throws Exception If failed.
+     */
+    protected void spiStop(IgniteSpi spi) throws Exception {
+        spi.spiStop();
+
+        info("SPI stopped [spi=" + spi.getClass().getName() + ']');
+    }
+
+    /**
+     * @param cls Class.
+     * @param name Method name.
+     * @return Method.
+     */
+    @Nullable private Method getMethod(Class<?> cls, String name) {
+        for (; !cls.equals(Object.class); cls = cls.getSuperclass()) {
+            for (Method m : cls.getMethods()) {
+                if (m.getName().equals(name))
+                    return m;
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     *
+     * @param <T> SPI implementation class.
+     */
+    protected static class TestData<T> {
+        /** */
+        private T spi;
+
+        /** */
+        private DiscoverySpi discoSpi;
+
+        /** */
+        private CommunicationSpi commSpi;
+
+        /** */
+        private GridSpiTestContext spiCtx;
+
+        /** */
+        private Map<String, Object> allAttrs = new HashMap<>();
+
+        /** */
+        private GridTestResources rsrcs = new GridTestResources();
+
+        /**
+         *
+         */
+        TestData() {
+            // No-op.
+        }
+
+        /**
+         * @return Test resources.
+         *
+         */
+        public GridTestResources getTestResources() {
+            return rsrcs;
+        }
+
+        /**
+         * @return {@code true} in case it is a discovery test.
+         */
+        @SuppressWarnings({"ObjectEquality"})
+        public boolean isDiscoveryTest() {
+            return spi == discoSpi;
+        }
+
+        /**
+         * @return {@code true} in case it is a communication test.
+         */
+        @SuppressWarnings({"ObjectEquality"})
+        public boolean isCommunicationTest() {
+            return spi == commSpi;
+        }
+
+        /**
+         * @return SPI.
+         */
+        public T getSpi() {
+            return spi;
+        }
+
+        /**
+         * @param spi SPI.
+         */
+        public void setSpi(T spi) {
+            this.spi = spi;
+        }
+
+        /**
+         * @param commSpi Communication SPI.
+         */
+        public void setCommSpi(CommunicationSpi commSpi) {
+            this.commSpi = commSpi;
+        }
+
+        /**
+         * @return Attributes.
+         */
+        public Map<String, Object> getAttributes() {
+            return allAttrs;
+        }
+
+        /**
+         * @param allAttrs Attributes.
+         */
+        public void setAllAttrs(Map<String, Object> allAttrs) {
+            this.allAttrs = allAttrs;
+        }
+
+        /**
+         * @return Discovery SPI.
+         */
+        public DiscoverySpi getDiscoverySpi() {
+            return discoSpi;
+        }
+
+        /**
+         * @param discoSpi Discovery SPI.
+         */
+        public void setDiscoverySpi(DiscoverySpi discoSpi) {
+            this.discoSpi = discoSpi;
+        }
+
+        /**
+         * @return SPI context.
+         */
+        public GridSpiTestContext getSpiContext() {
+            return spiCtx;
+        }
+
+        /**
+         * @param spiCtx SPI context.
+         */
+        public void setSpiContext(GridSpiTestContext spiCtx) {
+            this.spiCtx = spiCtx;
+        }
+
+        /** {@inheritDoc} */
+        @Override public String toString() {
+            return getClass().getSimpleName() +
+                " [spi=" + spi +
+                ", discoSpi=" + discoSpi +
+                ", allAttrs=" + allAttrs + ']';
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/bd28003b/modules/core/src/test/java/org/apache/ignite/testframework/junits/spi/GridSpiTest.java
----------------------------------------------------------------------
diff --git 
a/modules/core/src/test/java/org/apache/ignite/testframework/junits/spi/GridSpiTest.java
 
b/modules/core/src/test/java/org/apache/ignite/testframework/junits/spi/GridSpiTest.java
new file mode 100644
index 0000000..162855d
--- /dev/null
+++ 
b/modules/core/src/test/java/org/apache/ignite/testframework/junits/spi/GridSpiTest.java
@@ -0,0 +1,62 @@
+/*
+ * 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.spi;
+
+import org.apache.ignite.spi.*;
+import org.apache.ignite.spi.discovery.*;
+import org.apache.ignite.spi.discovery.tcp.*;
+
+import java.lang.annotation.*;
+
+/**
+ * Annotates all tests in SPI test framework. Provides implementation class of 
the SPI and
+ * optional dependencies.
+ */
+@SuppressWarnings({"JavaDoc"})
+@Documented
+@Inherited
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE})
+public @interface GridSpiTest {
+    /**
+     * Mandatory implementation class for SPI.
+     */
+    public Class<? extends IgniteSpi> spi();
+
+    /**
+     * Flag indicating whether SPI should be automatically started.
+     */
+    public boolean trigger() default true;
+
+    /**
+     * Flag indicating whether discovery SPI should be automatically started.
+     */
+    public boolean triggerDiscovery() default false;
+
+    /**
+     * Optional discovery SPI property to specify which SPI to use for 
discovering other nodes.
+     * This property is ignored if the spi being tested is an implementation 
of {@link org.apache.ignite.spi.discovery.DiscoverySpi} or
+     * {@link #triggerDiscovery()} is set to {@code false}.
+     */
+    public Class<? extends DiscoverySpi> discoverySpi() default 
TcpDiscoverySpi.class;
+
+    /**
+     * Optional group this test belongs to.
+     */
+    public String group() default "";
+}

http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/bd28003b/modules/core/src/test/java/org/apache/ignite/testframework/junits/spi/GridSpiTestConfig.java
----------------------------------------------------------------------
diff --git 
a/modules/core/src/test/java/org/apache/ignite/testframework/junits/spi/GridSpiTestConfig.java
 
b/modules/core/src/test/java/org/apache/ignite/testframework/junits/spi/GridSpiTestConfig.java
new file mode 100644
index 0000000..d60cb48
--- /dev/null
+++ 
b/modules/core/src/test/java/org/apache/ignite/testframework/junits/spi/GridSpiTestConfig.java
@@ -0,0 +1,48 @@
+/*
+ * 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.spi;
+
+import java.lang.annotation.*;
+
+/**
+ * Annotates a getter method value of which is used to configure 
implementation SPI.
+ */
+@SuppressWarnings({"JavaDoc"})
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD})
+public @interface GridSpiTestConfig {
+    /** */
+    @SuppressWarnings({"PublicInnerClass"})
+    public enum ConfigType {
+        /** */
+        SELF,
+
+        /** */
+        DISCOVERY,
+
+        /** */
+        BOTH
+    }
+
+    /** */
+    @SuppressWarnings({"JavaDoc"}) ConfigType type() default ConfigType.SELF;
+
+    /** */
+    String setterName() default "";
+}

http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/bd28003b/modules/core/src/test/java/org/apache/ignite/testframework/junits/spi/package.html
----------------------------------------------------------------------
diff --git 
a/modules/core/src/test/java/org/apache/ignite/testframework/junits/spi/package.html
 
b/modules/core/src/test/java/org/apache/ignite/testframework/junits/spi/package.html
new file mode 100644
index 0000000..1f85ff2
--- /dev/null
+++ 
b/modules/core/src/test/java/org/apache/ignite/testframework/junits/spi/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/package.html
----------------------------------------------------------------------
diff --git 
a/modules/core/src/test/java/org/apache/ignite/testframework/package.html 
b/modules/core/src/test/java/org/apache/ignite/testframework/package.html
new file mode 100644
index 0000000..1f85ff2
--- /dev/null
+++ b/modules/core/src/test/java/org/apache/ignite/testframework/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>

Reply via email to