/* * Carrot2 project. * * Copyright (C) 2002-2016, Dawid Weiss, Stanisław Osiński. * All rights reserved. * * Refer to the full license file "carrot2.LICENSE" * in the root folder of the repository checkout or at: * http://www.carrot2.org/carrot2.LICENSE */ package org.carrot2.core; import org.carrot2.core.attribute.Init; import org.carrot2.core.attribute.Processing; import org.carrot2.util.annotations.ThreadSafe; import org.carrot2.util.attribute.Attribute; import org.carrot2.util.attribute.Bindable; import org.carrot2.util.attribute.Input; import org.carrot2.util.attribute.constraint.ImplementingClasses; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; import static org.junit.Assert.*; /** * Tests pooling functionality of a {@link Controller}. */ public abstract class ControllerTestsPooling extends ControllerTestsBase { /** * Returns a controller that implements basic processing and component instance * pooling functionality. */ public abstract Controller getPoolingController(); @BeforeClass public static void enableAssertions() { PoolingProcessingComponentManager.NonPrimitiveInputAttributesCheck.makeAssertion = true; } @AfterClass public static void disableAssertions() { PoolingProcessingComponentManager.NonPrimitiveInputAttributesCheck.makeAssertion = false; } @Override public Controller prepareController() { return getPoolingController(); } /** * Verifies that components are not disposed of right after processing. */ @Test public void testRepeatedExecution3Components() { invokeInit(component1Mock); invokeProcessing(component1Mock); invokeInit(component2Mock); invokeProcessing(component2Mock); invokeInit(component3Mock); invokeProcessing(component3Mock); invokeDisposal(component1Mock, component2Mock, component3Mock); mocksControl.replay(); processingAttributes.put("instanceAttribute", "i"); processingAttributes.put("runtimeAttribute", "r"); processingAttributes.put("data", "d"); performProcessing(Component1.class); assertEquals("dir", resultAttributes.get("data")); processingAttributes.put("data", "d"); performProcessingDisposeAndVerifyMocks(Component2.class, Component3.class); assertEquals("dirir", resultAttributes.get("data")); } @Test public void testResettingPrimitiveAttribute() { invokeInit(component1Mock); invokeProcessing(component1Mock); invokeProcessing(component1Mock); invokeDisposal(component1Mock); mocksControl.replay(); processingAttributes.put("instanceAttribute", "i"); processingAttributes.put("runtimeAttribute", "r"); processingAttributes.put("data", "d"); performProcessing(Component1.class); assertEquals("dir", resultAttributes.get("data")); // Clear attributes and check if they've been restored processingAttributes.clear(); processingAttributes.put("data", "d"); performProcessingDisposeAndVerifyMocks(Component1.class); assertEquals("di", resultAttributes.get("data")); } @Test public void testResettingReferenceAttribute() { // Capture the initial attribute value performProcessing(ComponentWithBindableReference.class); final BindableInstanceCounter initial = result.getAttribute("bindable"); // Override with a different instance final BindableInstanceCounter overridden = new BindableInstanceCounter(); processingAttributes.put("bindable", overridden); performProcessing(ComponentWithBindableReference.class); assertThat((Object) result.getAttribute("bindable")).isSameAs(overridden); // Perform processing with no attributes. The initial value should be restored. processingAttributes.clear(); performProcessingAndDispose(ComponentWithBindableReference.class); assertThat((Object) result.getAttribute("bindable")).isSameAs(initial); } @Test(expected = ProcessingException.class) public void testResettingRequiredProcessingAttributeToNull() { invokeInit(component1Mock); invokeProcessing(component1Mock); // beforeProcessing will fail because of missing required attributes // afterProcessing() still will be performed component1Mock.afterProcessing(); invokeDisposal(component1Mock); mocksControl.replay(); processingAttributes.put("instanceAttribute", "i"); processingAttributes.put("runtimeAttribute", "r"); processingAttributes.put("data", "d"); performProcessing(Component1.class); assertEquals("dir", resultAttributes.get("data")); // Clear attributes and check if they've been restored processingAttributes.clear(); // This processing will throw an exception -- required attribute not provided. // This means the attribute has been restored to null. performProcessingDisposeAndVerifyMocks(Component1.class); } /** * Verifies that the controller does not needlessly create instances of non-primitive * attributes. This can happen if an init-time non-primitive attribute is to be set by * its class (which is instantiated by the binder). If the attribute class is provided * at init-time, no new instances should be created at processing time. */ @Test public void testComponentConfigurationInitProcessingAttributeCreation() { BindableInstanceCounter.reset(); initAttributes.put("initProcessing", BindableInstanceCounter.class); assertThat(BindableInstanceCounter.createdInstances).isEqualTo(0); performProcessing(ComponentWithInitProcessingInputReferenceAttribute.class); performProcessing(ComponentWithInitProcessingInputReferenceAttribute.class); assertThat(BindableInstanceCounter.createdInstances).isEqualTo( isCaching() ? 2 : eagerlyInitializedInstances()); processingAttributes.put("initProcessing", BindableInstanceCounter.class); performProcessing(ComponentWithInitProcessingInputReferenceAttribute.class); performProcessingAndDispose(ComponentWithInitProcessingInputReferenceAttribute.class); assertThat(BindableInstanceCounter.createdInstances).isEqualTo( isCaching() ? 2 : eagerlyInitializedInstances() + 3 - 1); } @Test public void testComponentInstanceReused() { ComponentWithInstanceCounter.reset(); performProcessing(ComponentWithInstanceCounter.class); assertThat(ComponentWithInstanceCounter.instanceCount).isEqualTo(eagerlyInitializedInstances()); performProcessing(ComponentWithInstanceCounter.class); performProcessing(ComponentWithInstanceCounter.class); performProcessing(ComponentWithInstanceCounter.class); assertThat(ComponentWithInstanceCounter.instanceCount).isEqualTo(eagerlyInitializedInstances()); performProcessingAndDispose(ComponentWithInstanceCounter.class); assertThat(ComponentWithInstanceCounter.instanceCount).isEqualTo(eagerlyInitializedInstances()); } @Bindable public static class ComponentWithInstanceCounter extends ProcessingComponentBase { static int instanceCount = 0; public ComponentWithInstanceCounter() { synchronized (ComponentWithInstanceCounter.class) { instanceCount++; } } static void reset() { instanceCount = 0; } } @Bindable @ThreadSafe public static class ComponentWithNonPrimitiveAttributes extends ProcessingComponentBase { @Init @Processing @Input @Attribute(key = "threadSafe") @ImplementingClasses(classes = {}, strict = false) public ThreadSafeClass threadSafe = new ThreadSafeClass(); @Init @Input @Attribute(key = "nonThreadSafeInit") @ImplementingClasses(classes = {}, strict = false) public NonThreadSafeClass nonThreadSafeInit = new NonThreadSafeClass(); @Processing @Input @Attribute(key = "nonThreadSafeProcessing") @ImplementingClasses(classes = {}, strict = false) public NonThreadSafeClass nonThreadSafeProcessing = new NonThreadSafeClass(); } @ThreadSafe public static class ThreadSafeClass { } public static class NonThreadSafeClass { } @Test(expected = AssertionError.class) public void warningOnNonThreadSafeInitInputInstanceAttributeProvidedOnInit() { initAttributes.put("threadSafe", new ThreadSafeClass()); initAttributes.put("nonThreadSafeInit", new NonThreadSafeClass()); performProcessingAndDispose(ComponentWithNonPrimitiveAttributes.class); } @Test(expected = AssertionError.class) public void warningOnNonThreadSafeProcessingInputInstanceAttributeProvidedOnInit() { initAttributes.put("threadSafe", new ThreadSafeClass()); initAttributes.put("nonThreadSafeProcessing", new NonThreadSafeClass()); performProcessingAndDispose(ComponentWithNonPrimitiveAttributes.class); } @Test public void noWarningOnNonThreadSafeInputClassAttributeProvidedOnInit() { initAttributes.put("threadSafe", ThreadSafeClass.class); initAttributes.put("nonThreadSafeInit", NonThreadSafeClass.class); initAttributes.put("nonThreadSafeProcessing", NonThreadSafeClass.class); performProcessingAndDispose(ComponentWithNonPrimitiveAttributes.class); } @Test public void noWarningOnNonThreadSafeInputInstanceFieldDefault() { performProcessingAndDispose(ComponentWithNonPrimitiveAttributes.class); } }