/* * Copyright (c) 2007 BUSINESS OBJECTS SOFTWARE LIMITED * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * * Neither the name of Business Objects nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ /* * Created: Feb 28, 2007 * By: Robin Salkeld * --!> */ package org.openquark.cal.services; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import junit.framework.TestCase; import org.openquark.cal.compiler.ModuleName; import org.openquark.cal.compiler.SourceModel.TypeExprDefn; import org.openquark.cal.compiler.io.EntryPointSpec; import org.openquark.cal.compiler.io.InputPolicy; import org.openquark.cal.compiler.io.OutputPolicy; import org.openquark.cal.machine.StatusListener; import org.openquark.cal.machine.StatusListener.StatusListenerAdapter; import org.openquark.cal.machine.StatusListener.Status.Module; import org.openquark.cal.module.Cal.Collections.CAL_List; import org.openquark.cal.module.Cal.Core.CAL_Prelude; import org.openquark.cal.runtime.CALExecutorException; import org.openquark.cal.runtime.ExecutionContext; import org.openquark.cal.runtime.MachineType; /** * This class contains test for the EntryPointCache. */ public class EntryPointCache_Test extends TestCase { private BasicCALServices calServices; private EntryPointCache entryPointCache; private int listCompilationCount; private int allowedListCompilations; private StatusListener listCompilationListener; private static final List<String> TEST_LIST = Arrays.asList(new String[] { "one", "two", "three "}); @Override protected void setUp() throws Exception { // TODO: We should probably test both machines with this code calServices = CALServicesTestUtilities.getCommonCALServices(MachineType.LECC, "cal.platform.test.cws"); entryPointCache = new EntryPointCache(calServices.getWorkspaceManager(), 10, 5); listCompilationListener = new StatusListenerAdapter() { @Override public void setModuleStatus(Module moduleStatus, ModuleName moduleName) { if (moduleStatus.equals(StatusListener.SM_LOADED) && moduleName.equals(CAL_List.MODULE_NAME)) { listCompilationCount++; if (listCompilationCount > allowedListCompilations) { fail("The List adjunct module was compiled too many times"); } } } }; listCompilationCount = 0; allowedListCompilations = 0; } @Override protected void tearDown() throws Exception { calServices.getWorkspaceManager().removeStatusListener(listCompilationListener); calServices = null; entryPointCache = null; } private static final TypeExprDefn STRING_TYPE = TypeExprDefn.TypeCons.make(CAL_Prelude.TypeConstructors.String); private static final TypeExprDefn STRING_LIST_TYPE = TypeExprDefn.List.make(STRING_TYPE); public void testTouchesThenRuns() throws CALExecutorException, GemCompilationException { // Touch the List.head() function final ExecutionContext executionContext = calServices.getWorkspaceManager().makeExecutionContextWithDefaultProperties(); EntryPointSpec headString = EntryPointSpec.make(CAL_List.Functions.head, OutputPolicy.makeTypedDefaultOutputPolicy(STRING_TYPE)); entryPointCache.cacheEntryPoints(Collections.singletonList(headString)); // Use a status listener to assert that the List adjunct module doesn't get recompiled calServices.getWorkspaceManager().addStatusListener(listCompilationListener); assertEquals("one", entryPointCache.runFunction(headString, new Object[] { TEST_LIST }, executionContext)); } public void testMultipleRunsOfSameFunction() throws InterruptedException { final EntryPointSpec headString = EntryPointSpec.make(CAL_List.Functions.head, OutputPolicy.makeTypedDefaultOutputPolicy(STRING_TYPE)); // Create many threads that will use the same entry point - the first should // safely compile the adjunct and the rest should use it without compiling again final ExecutionContext executionContext = calServices.getWorkspaceManager().makeExecutionContextWithDefaultProperties(); Runnable functionCallRunnable = new Runnable() { public void run() { try { assertEquals("one", entryPointCache.runFunction(headString, new Object[] { TEST_LIST }, executionContext)); } catch (CALExecutorException e) { fail("Unexpected exception: " + e); } catch (GemCompilationException e) { fail("Unexpected exception: " + e); } } }; // Use a status listener to assert that the List adjunct module is only compiled once calServices.getWorkspaceManager().addStatusListener(listCompilationListener); allowedListCompilations++; int numThreads = 5; Thread[] callThreads = new Thread[numThreads]; for (int i = 0; i < callThreads.length; i++) { callThreads[i] = new Thread(functionCallRunnable); callThreads[i].start(); } for (Thread element : callThreads) { element.join(); } } public void testMultipleOverlappingTouches() throws CALExecutorException, GemCompilationException { final EntryPointSpec headString = EntryPointSpec.make(CAL_List.Functions.head, OutputPolicy.makeTypedDefaultOutputPolicy(STRING_TYPE)); final EntryPointSpec tailString = EntryPointSpec.make(CAL_List.Functions.tail, OutputPolicy.makeTypedDefaultOutputPolicy(STRING_LIST_TYPE)); final EntryPointSpec reverseString = EntryPointSpec.make(CAL_List.Functions.reverse, OutputPolicy.makeTypedDefaultOutputPolicy(STRING_LIST_TYPE)); final EntryPointSpec lengthString = EntryPointSpec.make(CAL_List.Functions.length, new InputPolicy[] { InputPolicy.makeTypedDefaultInputPolicy(STRING_LIST_TYPE)}, OutputPolicy.DEFAULT_OUTPUT_POLICY); final ExecutionContext executionContext = calServices.getWorkspaceManager().makeExecutionContextWithDefaultProperties(); // Use a status listener to assert that the List adjunct module is only compiled twice calServices.getWorkspaceManager().addStatusListener(listCompilationListener); allowedListCompilations++; // Touch the first two functions entryPointCache.cacheEntryPoints(Arrays.asList(new EntryPointSpec[] { headString, tailString })); // Test the first two functions - no recompiling should occur assertEquals("one", entryPointCache.runFunction(headString, new Object[] { TEST_LIST }, executionContext)); assertEquals(TEST_LIST.subList(1, 3), entryPointCache.runFunction(tailString, new Object[] { TEST_LIST }, executionContext)); // Touch the middle two functions - the result should be having entry points for the first three allowedListCompilations++; entryPointCache.cacheEntryPoints(Arrays.asList(new EntryPointSpec[] { tailString, reverseString })); // Test the first three functions - no recompiling should occur assertEquals("one", entryPointCache.runFunction(headString, new Object[] { TEST_LIST }, executionContext)); assertEquals(TEST_LIST.subList(1, 3), entryPointCache.runFunction(tailString, new Object[] { TEST_LIST }, executionContext)); List<String> reversedList = new ArrayList<String>(TEST_LIST); Collections.reverse(reversedList); assertEquals(reversedList, entryPointCache.runFunction(reverseString, new Object[] { TEST_LIST }, executionContext)); // Call another unrelated function - this should automatically touch it and retain the first three allowedListCompilations++; assertEquals(new Integer(3), entryPointCache.runFunction(lengthString, new Object[] { TEST_LIST }, executionContext)); // Make sure the first three are still valid assertEquals("one", entryPointCache.runFunction(headString, new Object[] { TEST_LIST }, executionContext)); assertEquals(TEST_LIST.subList(1, 3), entryPointCache.runFunction(tailString, new Object[] { TEST_LIST }, executionContext)); assertEquals(reversedList, entryPointCache.runFunction(reverseString, new Object[] { TEST_LIST }, executionContext)); } }