diff --git a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/AsyncEntryTest.java b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/AsyncEntryTest.java index c09dfb29..c9336b8d 100644 --- a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/AsyncEntryTest.java +++ b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/AsyncEntryTest.java @@ -1,9 +1,11 @@ package com.alibaba.csp.sentinel; import com.alibaba.csp.sentinel.context.Context; +import com.alibaba.csp.sentinel.context.ContextTestUtil; import com.alibaba.csp.sentinel.context.ContextUtil; import com.alibaba.csp.sentinel.slotchain.StringResourceWrapper; +import org.junit.After; import org.junit.Test; import static org.junit.Assert.*; @@ -22,6 +24,8 @@ public class AsyncEntryTest { try { ContextUtil.enter(contextName); Context curContext = ContextUtil.getContext(); + Entry previousEntry = new CtEntry(new StringResourceWrapper("entry-sync", EntryType.IN), + null, curContext); AsyncEntry entry = new AsyncEntry(new StringResourceWrapper("testCleanCurrentEntryInLocal", EntryType.OUT), null, curContext); @@ -29,9 +33,26 @@ public class AsyncEntryTest { entry.cleanCurrentEntryInLocal(); assertNotSame(entry, curContext.getCurEntry()); + assertSame(previousEntry, curContext.getCurEntry()); + } finally { + ContextTestUtil.cleanUpContext(); + } + } + + @Test(expected = IllegalStateException.class) + public void testCleanCurrentEntryInLocalError() { + final String contextName = "abc"; + try { + ContextUtil.enter(contextName); + Context curContext = ContextUtil.getContext(); + AsyncEntry entry = new AsyncEntry(new StringResourceWrapper("testCleanCurrentEntryInLocal", EntryType.OUT), + null, curContext); + + entry.cleanCurrentEntryInLocal(); + + entry.cleanCurrentEntryInLocal(); } finally { - ContextUtil.getContext().setCurEntry(null); - ContextUtil.exit(); + ContextTestUtil.cleanUpContext(); } } @@ -47,8 +68,6 @@ public class AsyncEntryTest { assertNull(entry.getAsyncContext()); entry.initAsyncContext(); - System.out.println(curContext.getName()); - System.out.println(curContext.getOrigin()); Context asyncContext = entry.getAsyncContext(); assertNotNull(asyncContext); @@ -58,19 +77,25 @@ public class AsyncEntryTest { assertSame(entry, asyncContext.getCurEntry()); assertTrue(asyncContext.isAsync()); } finally { - ContextUtil.getContext().setCurEntry(null); - ContextUtil.exit(); + ContextTestUtil.cleanUpContext(); } } - @Test(expected = IllegalStateException.class) + @Test public void testDuplicateInitAsyncContext() { Context context = new Context(null, "abc"); AsyncEntry entry = new AsyncEntry(new StringResourceWrapper("testDuplicateInitAsyncContext", EntryType.OUT), null, context); entry.initAsyncContext(); + Context asyncContext = entry.getAsyncContext(); // Duplicate init. entry.initAsyncContext(); + assertSame(asyncContext, entry.getAsyncContext()); + } + + @After + public void tearDown() { + ContextTestUtil.cleanUpContext(); } } \ No newline at end of file diff --git a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/CtEntryTest.java b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/CtEntryTest.java new file mode 100644 index 00000000..47cad0ae --- /dev/null +++ b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/CtEntryTest.java @@ -0,0 +1,134 @@ +package com.alibaba.csp.sentinel; + +import com.alibaba.csp.sentinel.context.Context; +import com.alibaba.csp.sentinel.context.ContextTestUtil; +import com.alibaba.csp.sentinel.context.ContextUtil; +import com.alibaba.csp.sentinel.context.NullContext; +import com.alibaba.csp.sentinel.node.Node; +import com.alibaba.csp.sentinel.slotchain.StringResourceWrapper; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * @author Eric Zhao + */ +public class CtEntryTest { + + @Test + public void testExitNotMatchCurEntry() { + String contextName = "context-rpc"; + ContextUtil.enter(contextName); + Context context = ContextUtil.getContext(); + CtEntry entry1 = null; + CtEntry entry2 = null; + try { + entry1 = new CtEntry(new StringResourceWrapper("res1", EntryType.IN), + null, ContextUtil.getContext()); + assertSame(entry1, context.getCurEntry()); + entry2 = new CtEntry(new StringResourceWrapper("res2", EntryType.IN), + null, ContextUtil.getContext()); + assertSame(entry2, context.getCurEntry()); + + // Forget to exit for entry 2... + // Directly exit for entry 1, then boom... + entry1.exit(); + } catch (ErrorEntryFreeException ex) { + assertNotNull(entry1); + assertNotNull(entry2); + assertNull(entry1.context); + assertNull(entry2.context); + assertNull(context.getCurEntry()); + return; + } finally { + ContextUtil.exit(); + } + fail("Mismatch entry-exit should throw an ErrorEntryFreeException"); + } + + private Context getFakeDefaultContext() { + return new Context(null, Constants.CONTEXT_DEFAULT_NAME); + } + + @Test + public void testExitLastEntryWithDefaultContext() { + final Context defaultContext = getFakeDefaultContext(); + ContextUtil.runOnContext(defaultContext, new Runnable() { + @Override + public void run() { + CtEntry entry = new CtEntry(new StringResourceWrapper("res", EntryType.IN), + null, ContextUtil.getContext()); + assertSame(entry, defaultContext.getCurEntry()); + assertSame(defaultContext, ContextUtil.getContext()); + entry.exit(); + assertNull(defaultContext.getCurEntry()); + // Default context will be automatically exited. + assertNull(ContextUtil.getContext()); + } + }); + + } + + @Test + public void testExitTwoLastEntriesWithCustomContext() { + String contextName = "context-rpc"; + ContextUtil.enter(contextName); + Context context = ContextUtil.getContext(); + try { + CtEntry entry1 = new CtEntry(new StringResourceWrapper("resA", EntryType.IN), + null, context); + entry1.exit(); + assertEquals(context, ContextUtil.getContext()); + CtEntry entry2 = new CtEntry(new StringResourceWrapper("resB", EntryType.IN), + null, context); + entry2.exit(); + assertEquals(context, ContextUtil.getContext()); + } finally { + ContextUtil.exit(); + assertNull(ContextUtil.getContext()); + } + } + + @Test + public void testEntryAndExitWithNullContext() { + Context context = new NullContext(); + CtEntry entry = new CtEntry(new StringResourceWrapper("testEntryAndExitWithNullContext", EntryType.IN), + null, context); + assertNull(context.getCurEntry()); + entry.exit(); + assertNull(context.getCurEntry()); + // Won't true exit, so the context won't be cleared. + assertEquals(context, entry.context); + } + + @Test + public void testGetLastNode() { + Context context = new NullContext(); + CtEntry entry = new CtEntry(new StringResourceWrapper("testGetLastNode", EntryType.IN), + null, context); + assertNull(entry.parent); + assertNull(entry.getLastNode()); + Entry parentEntry = mock(Entry.class); + Node node = mock(Node.class); + when(parentEntry.getCurNode()).thenReturn(node); + entry.parent = parentEntry; + assertSame(node, entry.getLastNode()); + } + + @Before + public void setUp() throws Exception { + ContextTestUtil.cleanUpContext(); + ContextTestUtil.resetContextMap(); + } + + @After + public void tearDown() throws Exception { + ContextTestUtil.cleanUpContext(); + ContextTestUtil.resetContextMap(); + } +} \ No newline at end of file diff --git a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/CtSphTest.java b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/CtSphTest.java new file mode 100644 index 00000000..568bc62e --- /dev/null +++ b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/CtSphTest.java @@ -0,0 +1,365 @@ +package com.alibaba.csp.sentinel; + +import com.alibaba.csp.sentinel.context.Context; +import com.alibaba.csp.sentinel.context.ContextTestUtil; +import com.alibaba.csp.sentinel.context.ContextUtil; +import com.alibaba.csp.sentinel.node.DefaultNode; +import com.alibaba.csp.sentinel.slotchain.AbstractLinkedProcessorSlot; +import com.alibaba.csp.sentinel.slotchain.DefaultProcessorSlotChain; +import com.alibaba.csp.sentinel.slotchain.ProcessorSlot; +import com.alibaba.csp.sentinel.slotchain.ProcessorSlotChain; +import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; +import com.alibaba.csp.sentinel.slotchain.SlotChainProvider; +import com.alibaba.csp.sentinel.slotchain.StringResourceWrapper; +import com.alibaba.csp.sentinel.slots.block.BlockException; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +/** + * Test cases for Sentinel internal {@link CtSph}. + * + * @author Eric Zhao + */ +public class CtSphTest { + + private final CtSph ctSph = new CtSph(); + + private void testCustomContextEntryWithFullContextSize(String resourceName, boolean async) { + fillFullContext(); + ResourceWrapper resourceWrapper = new StringResourceWrapper(resourceName, EntryType.IN); + String contextName = "custom-context-" + System.currentTimeMillis(); + ContextUtil.enter(contextName, "9527"); + + // Prepare a slot that "should not pass". If entered the slot, exception will be thrown. + addShouldNotPassSlotFor(resourceWrapper); + + Entry entry = null; + try { + if (async) { + entry = ctSph.asyncEntry(resourceName, resourceWrapper.getType(), 1); + } else { + entry = ctSph.entry(resourceWrapper, 1); + } + } catch (BlockException ex) { + fail("Unexpected blocked: " + ex.getClass().getCanonicalName()); + } finally { + if (entry != null) { + entry.exit(); + } + ContextUtil.exit(); + } + } + + @Test + public void testCustomContextSyncEntryWithFullContextSize() { + String resourceName = "testCustomContextSyncEntryWithFullContextSize"; + testCustomContextEntryWithFullContextSize(resourceName, false); + } + + @Test + public void testCustomContextAsyncEntryWithFullContextSize() { + String resourceName = "testCustomContextAsyncEntryWithFullContextSize"; + testCustomContextEntryWithFullContextSize(resourceName, true); + } + + private void testDefaultContextEntryWithFullContextSize(String resourceName, boolean async) { + fillFullContext(); + ResourceWrapper resourceWrapper = new StringResourceWrapper(resourceName, EntryType.IN); + + // Prepare a slot that "should pass". + ShouldPassSlot slot = addShouldPassSlotFor(resourceWrapper); + assertFalse(slot.entered || slot.exited); + + Entry entry = null; + try { + if (!async) { + entry = ctSph.entry(resourceWrapper, 1); + } else { + entry = ctSph.asyncEntry(resourceName, resourceWrapper.getType(), 1); + Context asyncContext = ((AsyncEntry)entry).getAsyncContext(); + assertTrue(ContextUtil.isDefaultContext(asyncContext)); + assertTrue(asyncContext.isAsync()); + } + assertTrue(ContextUtil.isDefaultContext(ContextUtil.getContext())); + assertTrue(slot.entered); + } catch (BlockException ex) { + fail("Unexpected blocked: " + ex.getClass().getCanonicalName()); + } finally { + if (entry != null) { + entry.exit(); + assertTrue(slot.exited); + } + } + } + + @Test + public void testDefaultContextSyncEntryWithFullContextSize() { + String resourceName = "testDefaultContextSyncEntryWithFullContextSize"; + testDefaultContextEntryWithFullContextSize(resourceName, false); + } + + @Test + public void testDefaultContextAsyncEntryWithFullContextSize() { + String resourceName = "testDefaultContextAsyncEntryWithFullContextSize"; + testDefaultContextEntryWithFullContextSize(resourceName, true); + } + + @Test + public void testEntryAndAsyncEntryWhenSwitchOff() { + // Turn off the switch. + Constants.ON = false; + + String resourceNameA = "resSync"; + String resourceNameB = "resAsync"; + ResourceWrapper resourceWrapperA = new StringResourceWrapper(resourceNameA, EntryType.IN); + ResourceWrapper resourceWrapperB = new StringResourceWrapper(resourceNameB, EntryType.IN); + + // Prepare a slot that "should not pass". If entered the slot, exception will be thrown. + addShouldNotPassSlotFor(resourceWrapperA); + addShouldNotPassSlotFor(resourceWrapperB); + + Entry entry = null; + AsyncEntry asyncEntry = null; + try { + entry = ctSph.entry(resourceWrapperA, 1); + asyncEntry = ctSph.asyncEntry(resourceNameB, resourceWrapperB.getType(), 1); + } catch (BlockException ex) { + fail("Unexpected blocked: " + ex.getClass().getCanonicalName()); + } finally { + if (asyncEntry != null) { + asyncEntry.exit(); + } + if (entry != null) { + entry.exit(); + } + Constants.ON = true; + } + } + + @Test + public void testAsyncEntryNormalPass() { + String resourceName = "testAsyncEntryNormalPass"; + ResourceWrapper resourceWrapper = new StringResourceWrapper(resourceName, EntryType.IN); + AsyncEntry entry = null; + + // Prepare a slot that "should pass". + ShouldPassSlot slot = addShouldPassSlotFor(resourceWrapper); + assertFalse(slot.entered || slot.exited); + + ContextUtil.enter("abc"); + Entry previousEntry = ContextUtil.getContext().getCurEntry(); + try { + entry = ctSph.asyncEntry(resourceName, EntryType.IN, 1); + assertTrue(slot.entered); + assertFalse(slot.exited); + Context asyncContext = entry.getAsyncContext(); + assertNotNull(asyncContext); + assertSame(entry, asyncContext.getCurEntry()); + assertNotSame("The async entry should not be added to current context", + entry, ContextUtil.getContext().getCurEntry()); + assertSame(previousEntry, ContextUtil.getContext().getCurEntry()); + } catch (BlockException ex) { + fail("Unexpected blocked: " + ex.getClass().getCanonicalName()); + } finally { + if (entry != null) { + Context asyncContext = entry.getAsyncContext(); + entry.exit(); + assertTrue(slot.exited); + assertNull(entry.getAsyncContext()); + assertSame(previousEntry, asyncContext.getCurEntry()); + } + ContextUtil.exit(); + } + } + + @Test + public void testAsyncEntryNestedInSyncEntryNormalBlocked() { + String previousResourceName = "fff"; + String resourceName = "testAsyncEntryNestedInSyncEntryNormalBlocked"; + ResourceWrapper resourceWrapper = new StringResourceWrapper(resourceName, EntryType.IN); + + // Prepare a slot that "must block". + MustBlockSlot slot = addMustBlockSlot(resourceWrapper); + assertFalse(slot.exited); + // Previous entry should pass. + addShouldPassSlotFor(new StringResourceWrapper(previousResourceName, EntryType.IN)); + ContextUtil.enter("bcd-" + System.currentTimeMillis()); + + AsyncEntry entry = null; + Entry syncEntry = null; + Entry previousEntry = null; + try { + // First enter a sync resource. + syncEntry = ctSph.entry(previousResourceName, EntryType.IN, 1); + // Record current entry (previous for next). + previousEntry = ContextUtil.getContext().getCurEntry(); + // Then enter an async resource. + entry = ctSph.asyncEntry(resourceName, EntryType.IN, 1); + + // Should not pass here. + } catch (BlockException ex) { + assertNotNull(previousEntry); + assertNull(entry); + assertTrue(slot.exited); + assertSame(previousEntry, ContextUtil.getContext().getCurEntry()); + return; + } finally { + assertNull(entry); + assertNotNull(syncEntry); + + syncEntry.exit(); + ContextUtil.exit(); + } + fail("This async entry is expected to be blocked"); + } + + private void testEntryAmountExceeded(boolean async) { + fillFullResources(); + Entry entry = null; + try { + if (!async) { + entry = ctSph.entry("testSync", EntryType.IN, 1); + } else { + entry = ctSph.asyncEntry("testSync", EntryType.IN, 1); + } + assertNull(((CtEntry)entry).chain); + if (!async) { + assertSame(entry, ContextUtil.getContext().getCurEntry()); + } else { + Context asyncContext = ((AsyncEntry)entry).getAsyncContext(); + assertNotNull(asyncContext); + assertSame(entry, asyncContext.getCurEntry()); + } + } catch (BlockException ex) { + fail("Unexpected blocked: " + ex.getClass().getCanonicalName()); + } finally { + if (entry != null) { + entry.exit(); + } + } + } + + @Test + public void testEntryAmountExceededForSyncEntry() { + testEntryAmountExceeded(false); + } + + @Test + public void testEntryAmountExceededForAsyncEntry() { + testEntryAmountExceeded(true); + } + + @Test + public void testLookUpSlotChain() { + ResourceWrapper r1 = new StringResourceWrapper("firstRes", EntryType.IN); + assertFalse(CtSph.getChainMap().containsKey(r1)); + ProcessorSlot chainR1 = ctSph.lookProcessChain(r1); + assertNotNull("The slot chain for r1 should be created", chainR1); + assertSame("Should return the cached slot chain once it has been created", chainR1, ctSph.lookProcessChain(r1)); + + fillFullResources(); + ResourceWrapper r2 = new StringResourceWrapper("secondRes", EntryType.IN); + assertFalse(CtSph.getChainMap().containsKey(r2)); + assertNull("The slot chain for r2 should not be created because amount exceeded", ctSph.lookProcessChain(r2)); + assertNull(ctSph.lookProcessChain(r2)); + } + + private void fillFullContext() { + for (int i = 0; i < Constants.MAX_CONTEXT_NAME_SIZE; i++) { + ContextUtil.enter("test-context-" + i); + ContextUtil.exit(); + } + } + + private void fillFullResources() { + for (int i = 0; i < Constants.MAX_SLOT_CHAIN_SIZE; i++) { + ResourceWrapper resourceWrapper = new StringResourceWrapper("test-resource-" + i, EntryType.IN); + CtSph.getChainMap().put(resourceWrapper, SlotChainProvider.newSlotChain()); + } + } + + private void addShouldNotPassSlotFor(ResourceWrapper resourceWrapper) { + ProcessorSlotChain slotChain = new DefaultProcessorSlotChain(); + slotChain.addLast(new ShouldNotPassSlot()); + CtSph.getChainMap().put(resourceWrapper, slotChain); + } + + private ShouldPassSlot addShouldPassSlotFor(ResourceWrapper resourceWrapper) { + ProcessorSlotChain slotChain = new DefaultProcessorSlotChain(); + ShouldPassSlot shouldPassSlot = new ShouldPassSlot(); + slotChain.addLast(shouldPassSlot); + CtSph.getChainMap().put(resourceWrapper, slotChain); + return shouldPassSlot; + } + + private MustBlockSlot addMustBlockSlot(ResourceWrapper resourceWrapper) { + ProcessorSlotChain slotChain = new DefaultProcessorSlotChain(); + MustBlockSlot mustBlockSlot = new MustBlockSlot(); + slotChain.addLast(mustBlockSlot); + CtSph.getChainMap().put(resourceWrapper, slotChain); + return mustBlockSlot; + } + + private class ShouldNotPassSlot extends AbstractLinkedProcessorSlot { + @Override + public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode param, int count, + Object... args) { + throw new IllegalStateException("Should not enter this slot!"); + } + + @Override + public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) { + throw new IllegalStateException("Should not exit this slot!"); + } + } + + private class MustBlockSlot extends AbstractLinkedProcessorSlot { + boolean exited = false; + + @Override + public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode param, int count, + Object... args) throws Throwable { + throw new BlockException("custom") {}; + } + + @Override + public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) { + exited = true; + } + } + + private class ShouldPassSlot extends AbstractLinkedProcessorSlot { + boolean entered = false; + boolean exited = false; + + @Override + public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode param, int count, + Object... args) { + entered = true; + } + + @Override + public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) { + exited = true; + } + } + + @Before + public void setUp() throws Exception { + ContextTestUtil.cleanUpContext(); + ContextTestUtil.resetContextMap(); + CtSph.resetChainMap(); + } + + @After + public void tearDown() throws Exception { + ContextTestUtil.cleanUpContext(); + ContextTestUtil.resetContextMap(); + CtSph.resetChainMap(); + } +} \ No newline at end of file diff --git a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/EntryTest.java b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/EntryTest.java new file mode 100644 index 00000000..3cfb8c09 --- /dev/null +++ b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/EntryTest.java @@ -0,0 +1,68 @@ +package com.alibaba.csp.sentinel; + +import com.alibaba.csp.sentinel.node.Node; +import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; +import com.alibaba.csp.sentinel.slotchain.StringResourceWrapper; + +import org.junit.Test; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +/** + * Test cases for {@link Entry}. + * + * @author Eric Zhao + */ +public class EntryTest { + + @Test + public void testEntryExitCounts() { + ResourceWrapper resourceWrapper = new StringResourceWrapper("resA", EntryType.IN); + TestEntry entry = new TestEntry(resourceWrapper); + entry.exit(); + assertEquals(-1, entry.count); + entry.exit(9); + assertEquals(-10, entry.count); + } + + @Test + public void testEntryFieldsGetSet() { + ResourceWrapper resourceWrapper = new StringResourceWrapper("resA", EntryType.IN); + Entry entry = new TestEntry(resourceWrapper); + assertSame(resourceWrapper, entry.getResourceWrapper()); + Throwable error = new IllegalStateException(); + entry.setError(error); + assertSame(error, entry.getError()); + Node curNode = mock(Node.class); + entry.setCurNode(curNode); + assertSame(curNode, entry.getCurNode()); + Node originNode = mock(Node.class); + entry.setOriginNode(originNode); + assertSame(originNode, entry.getOriginNode()); + } + + private class TestEntry extends Entry { + + int count = 0; + + TestEntry(ResourceWrapper resourceWrapper) { + super(resourceWrapper); + } + + @Override + public void exit(int count, Object... args) throws ErrorEntryFreeException { + this.count -= count; + } + + @Override + protected Entry trueExit(int count, Object... args) throws ErrorEntryFreeException { + return null; + } + + @Override + public Node getLastNode() { + return null; + } + } +} \ No newline at end of file diff --git a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/context/ContextTest.java b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/context/ContextTest.java index 61dda04d..034668c9 100755 --- a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/context/ContextTest.java +++ b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/context/ContextTest.java @@ -16,7 +16,6 @@ package com.alibaba.csp.sentinel.context; import com.alibaba.csp.sentinel.Constants; -import com.alibaba.csp.sentinel.TestUtil; import org.junit.After; import org.junit.Before; @@ -39,7 +38,7 @@ public class ContextTest { @After public void cleanUp() { - TestUtil.cleanUpContext(); + ContextTestUtil.cleanUpContext(); } @Test @@ -80,8 +79,7 @@ public class ContextTest { } private void resetContextMap() { - ContextUtil.resetContextMap(); - Constants.ROOT.removeChildList(); + ContextTestUtil.resetContextMap(); } @Test