/* * 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.cache; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Map; import java.util.Random; import java.util.UUID; import java.util.concurrent.Callable; import java.util.concurrent.LinkedBlockingQueue; import javax.cache.Cache; import org.apache.ignite.IgniteCheckedException; import org.apache.ignite.IgniteException; import org.apache.ignite.cache.store.CacheStore; import org.apache.ignite.internal.processors.cache.CacheEntryImpl; import org.apache.ignite.internal.util.lang.GridMetadataAwareAdapter; import org.apache.ignite.internal.util.typedef.CI2; import org.apache.ignite.internal.util.typedef.F; import org.apache.ignite.internal.util.typedef.internal.U; import org.apache.ignite.lang.IgniteAsyncSupport; import org.apache.ignite.lang.IgniteFuture; import org.apache.ignite.lang.IgniteUuid; import org.apache.ignite.testframework.GridTestUtils; import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest; import org.apache.ignite.transactions.Transaction; import org.apache.ignite.transactions.TransactionConcurrency; import org.apache.ignite.transactions.TransactionIsolation; import org.apache.ignite.transactions.TransactionState; import org.jetbrains.annotations.Nullable; /** * Abstract cache store test. */ public abstract class GridAbstractCacheStoreSelfTest<T extends CacheStore<Object, Object>> extends GridCommonAbstractTest { /** */ protected final T store; /** */ protected TestThreadLocalCacheSession ses = new TestThreadLocalCacheSession(); /** * @throws Exception If failed. */ @SuppressWarnings({"AbstractMethodCallInConstructor", "OverriddenMethodCallDuringObjectConstruction"}) protected GridAbstractCacheStoreSelfTest() throws Exception { super(false); store = store(); inject(store); } /** * @throws Exception If failed. */ public void testStore() throws Exception { // Create dummy transaction Transaction tx = new DummyTx(); ses.newSession(tx); store.write(new CacheEntryImpl<>("k1", "v1")); store.write(new CacheEntryImpl<>("k2", "v2")); store.sessionEnd(true); ses.newSession(null); assertEquals("v1", store.load("k1")); assertEquals("v2", store.load("k2")); assertNull(store.load("k3")); ses.newSession(tx); store.delete("k1"); store.sessionEnd(true); ses.newSession(null); assertNull(store.load("k1")); assertEquals("v2", store.load("k2")); assertNull(store.load("k3")); } /** * @throws IgniteCheckedException if failed. */ public void testRollback() throws IgniteCheckedException { Transaction tx = new DummyTx(); ses.newSession(tx); // Put. store.write(new CacheEntryImpl<>("k1", "v1")); store.sessionEnd(false); // Rollback. tx = new DummyTx(); ses.newSession(tx); assertNull(store.load("k1")); // Put all. assertNull(store.load("k2")); Collection<Cache.Entry<? extends Object, ? extends Object>> col = new ArrayList<>(); col.add(new CacheEntryImpl<>("k2", "v2")); store.writeAll(col); store.sessionEnd(false); // Rollback. tx = new DummyTx(); ses.newSession(tx); assertNull(store.load("k2")); col = new ArrayList<>(); col.add(new CacheEntryImpl<>("k3", "v3")); store.writeAll(col); store.sessionEnd(true); // Commit. tx = new DummyTx(); ses.newSession(tx); assertEquals("v3", store.load("k3")); store.write(new CacheEntryImpl<>("k4", "v4")); store.sessionEnd(false); // Rollback. tx = new DummyTx(); ses.newSession(tx); assertNull(store.load("k4")); assertEquals("v3", store.load("k3")); // Remove. store.delete("k3"); store.sessionEnd(false); // Rollback. tx = new DummyTx(); ses.newSession(tx); assertEquals("v3", store.load("k3")); // Remove all. store.deleteAll(Arrays.asList("k3")); store.sessionEnd(false); // Rollback. tx = new DummyTx(); ses.newSession(tx); assertEquals("v3", store.load("k3")); } /** * @throws IgniteCheckedException if failed. */ public void testAllOpsWithTXNoCommit() throws IgniteCheckedException { doTestAllOps(new DummyTx(), false); } /** * @throws IgniteCheckedException if failed. */ public void testAllOpsWithTXCommit() throws IgniteCheckedException { doTestAllOps(new DummyTx(), true); } /** * @throws IgniteCheckedException if failed. */ public void testAllOpsWithoutTX() throws IgniteCheckedException { doTestAllOps(null, false); } /** * @param tx Transaction. * @param commit Commit. */ private void doTestAllOps(@Nullable Transaction tx, boolean commit) { try { ses.newSession(tx); store.write(new CacheEntryImpl<>("key1", "val1")); if (tx != null && commit) { store.sessionEnd(true); tx = new DummyTx(); ses.newSession(tx); } if (tx == null || commit) assertEquals("val1", store.load("key1")); Collection<Cache.Entry<? extends Object, ? extends Object>> col = new ArrayList<>(); col.add(new CacheEntryImpl<>("key2", "val2")); col.add(new CacheEntryImpl<>("key3", "val3")); store.writeAll(col); if (tx != null && commit) { store.sessionEnd(true); tx = new DummyTx(); } if (tx == null || commit) { Map<Object, Object> loaded = store.loadAll(Arrays.asList("key1", "key2", "key3", "no_such_key")); for (Map.Entry<Object, Object> e : loaded.entrySet()) { Object key = e.getKey(); Object val = e.getValue(); if ("key1".equals(key)) assertEquals("val1", val); if ("key2".equals(key)) assertEquals("val2", val); if ("key3".equals(key)) assertEquals("val3", val); if ("no_such_key".equals(key)) fail(); } assertEquals(3, loaded.size()); } store.deleteAll(Arrays.asList("key2", "key3")); if (tx != null && commit) { store.sessionEnd(true); tx = new DummyTx(); ses.newSession(tx); } if (tx == null || commit) { assertNull(store.load("key2")); assertNull(store.load("key3")); assertEquals("val1", store.load("key1")); } store.delete("key1"); if (tx != null && commit) { store.sessionEnd(true); tx = new DummyTx(); ses.newSession(tx); } if (tx == null || commit) assertNull(store.load("key1")); } finally { if (tx != null) { store.sessionEnd(false); ses.newSession(null); } } } /** * @throws Exception If failed. */ public void testSimpleMultithreading() throws Exception { final Random rnd = new Random(); final LinkedBlockingQueue<UUID> queue = new LinkedBlockingQueue<>(); multithreaded(new Callable<Object>() { @Override public Object call() throws Exception { for (int i = 0; i < 1000; i++) { Transaction tx = rnd.nextBoolean() ? new DummyTx() : null; ses.newSession(tx); int op = rnd.nextInt(10); boolean queueEmpty = false; if (op < 4) { // Load. UUID key = queue.poll(); if (key == null) queueEmpty = true; else { if (rnd.nextBoolean()) assertNotNull(store.load(key)); else { Map<Object, Object> loaded = store.loadAll(Collections.singleton(key)); assertEquals(1, loaded.size()); Map.Entry<Object, Object> e = loaded.entrySet().iterator().next(); UUID k = (UUID)e.getKey(); UUID v = (UUID)e.getValue(); assertTrue(k.equals(v) || (k.getMostSignificantBits() == v.getLeastSignificantBits() && k.getLeastSignificantBits() == v.getMostSignificantBits())); } if (tx != null) store.sessionEnd(true); queue.add(key); } } else if (op < 6) { // Remove. UUID key = queue.poll(); if (key == null) queueEmpty = true; else { if (rnd.nextBoolean()) store.delete(key); else store.deleteAll(Collections.singleton(key)); if (tx != null) store.sessionEnd(true); } } else { // Update. UUID key = queue.poll(); if (key == null) queueEmpty = true; else { UUID val = new UUID(key.getLeastSignificantBits(), key.getMostSignificantBits()); if (rnd.nextBoolean()) store.write(new CacheEntryImpl<>(key, val)); else { Collection<Cache.Entry<? extends Object, ? extends Object>> col = new ArrayList<>(); col.add(new CacheEntryImpl<>(key, val)); store.writeAll(col); } if (tx != null) store.sessionEnd(true); queue.add(key); } } if (queueEmpty) { // Add. UUID key = UUID.randomUUID(); if (rnd.nextBoolean()) store.write(new CacheEntryImpl<>(key, key)); else { Collection<Cache.Entry<? extends Object, ? extends Object>> col = new ArrayList<>(); col.add(new CacheEntryImpl<>(key, key)); store.writeAll(col); } if (tx != null) store.sessionEnd(true); queue.add(key); } } return null; } }, 37); } /** * @param store Store. * @throws Exception If failed. */ protected void inject(T store) throws Exception { getTestResources().inject(store); GridTestUtils.setFieldValue(store, "ses", ses); } /** {@inheritDoc} */ @Override protected void beforeTest() throws Exception { super.beforeTest(); U.startLifecycleAware(F.asList(store)); final Collection<Object> keys = new ArrayList<>(); CI2<Object, Object> c = new CI2<Object, Object>() { @Override public void apply(Object k, Object v) { keys.add(k); } }; store.loadCache(c); if (keys.isEmpty()) return; ses.newSession(null); store.deleteAll(keys); keys.clear(); store.loadCache(c); assertTrue(keys.isEmpty()); } /** * @throws Exception If failed. */ @Override protected void afterTest() throws Exception { super.afterTest(); U.stopLifecycleAware(log, F.asList(store)); } /** * @return Store. */ protected abstract T store(); /** * Dummy transaction for test purposes. */ public static class DummyTx extends GridMetadataAwareAdapter implements Transaction { /** */ private final IgniteUuid xid = IgniteUuid.randomUuid(); /** {@inheritDoc} */ @Nullable @Override public IgniteUuid xid() { return xid; } /** {@inheritDoc} */ @Nullable @Override public UUID nodeId() { return null; } /** {@inheritDoc} */ @Override public long threadId() { return 0; } /** {@inheritDoc} */ @Override public long startTime() { return 0; } /** {@inheritDoc} */ @Nullable @Override public TransactionIsolation isolation() { return null; } /** {@inheritDoc} */ @Nullable @Override public TransactionConcurrency concurrency() { return null; } /** {@inheritDoc} */ @Override public boolean implicit() { return false; } /** {@inheritDoc} */ @Override public boolean isInvalidate() { return false; } /** {@inheritDoc} */ @Nullable @Override public TransactionState state() { return null; } /** {@inheritDoc} */ @Override public long timeout() { return 0; } /** {@inheritDoc} */ @Override public long timeout(long timeout) { return 0; } /** {@inheritDoc} */ @Override public boolean setRollbackOnly() { return false; } /** {@inheritDoc} */ @Override public boolean isRollbackOnly() { return false; } /** {@inheritDoc} */ @Override public void commit() { // No-op. } /** {@inheritDoc} */ @Override public IgniteFuture<Void> commitAsync() throws IgniteException { return null; } /** {@inheritDoc} */ @Override public void close() { // No-op. } /** {@inheritDoc} */ @Override public IgniteAsyncSupport withAsync() { throw new UnsupportedOperationException(); } /** {@inheritDoc} */ @Override public boolean isAsync() { return false; } /** {@inheritDoc} */ @Override public <R> IgniteFuture<R> future() { return null; } /** {@inheritDoc} */ @Override public void rollback() { // No-op. } /** {@inheritDoc} */ @Override public IgniteFuture<Void> rollbackAsync() throws IgniteException { return null; } } }