/* * 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. */ /* * CALServicesTestUtilities.java * Creation date: Mar 2, 2005. * By: Joseph Wong */ package org.openquark.cal.services; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import junit.framework.Assert; import org.openquark.cal.compiler.MessageLogger; import org.openquark.cal.compiler.ModuleName; import org.openquark.cal.compiler.ModuleSourceDefinitionGroup; import org.openquark.cal.compiler.ModuleTypeInfo; import org.openquark.cal.compiler.QualifiedName; import org.openquark.cal.compiler.Scope; import org.openquark.cal.compiler.SourceModel; import org.openquark.cal.compiler.io.EntryPointSpec; import org.openquark.cal.machine.ProgramResourceLocator; import org.openquark.cal.machine.ProgramResourceRepository; import org.openquark.cal.machine.ProgramResourceLocator.Folder; import org.openquark.cal.module.Cal.Core.CAL_Prelude; import org.openquark.cal.runtime.CALExecutorException; import org.openquark.cal.runtime.MachineType; import org.openquark.util.FileSystemHelper; import org.openquark.util.Pair; /** * A set of helper methods for use in JUnit test cases that need to access CAL * services. The methods make use of JUnit functionality like * Assert.assertTrue() to indicate expected behavior. A unit test that can make * use of a shared copy of the workspace (i.e. it doesn't need to modify or * significantly change the workspace) should call the static method * getCommonCALServices() to retrieve a shared copy of BasicCALServices and use * it. * * @author Joseph Wong */ public class CALServicesTestUtilities { private static final String DO_NOT_CLEANUP_RUNTIME_PROP = "org.openquark.cal.unittest.doNotCleanupRuntimeBeforeRun"; private static final String DO_NOT_USE_TEMP_LECC_RUNTIME_DIR_PROP = "org.openquark.cal.unittest.doNotUseTemporaryLeccRuntimeDirectory"; /** * Indicate whether the cleanup of the files generated by the runtime is enabled. */ private static final boolean ENABLE_RUNTIME_CLEANUP = (System.getProperty(DO_NOT_CLEANUP_RUNTIME_PROP) == null); /** * Indicate whether cleanup should not be performed more than once per directory on the file system. */ private static final boolean INHIBIT_MULTIPLE_CLEANUPS = true; /** * Indicates whether the lecc_runtime directory should be created in the user's temporary directory. */ private static final boolean ENABLE_TEMP_LECC_RUNTIME_DIR = (System.getProperty(DO_NOT_USE_TEMP_LECC_RUNTIME_DIR_PROP) == null); /** * The default machine type to use for unit tests. */ private static final MachineType DEFAULT_UNIT_TEST_MACHINE_TYPE = MachineType.LECC; /** * The default target module to use for unit tests. */ public static final ModuleName DEFAULT_UNIT_TEST_MODULE = CAL_Prelude.MODULE_NAME; /** * If set to true, display debugging output when instances of BasicCALServices are created. */ private static final boolean SHOW_INSTANCE_CREATION = false; /** * Set this flag to true if debugging output is desired regardless of * whether a test fails or succeeds. */ private static final boolean SHOW_DEBUGGING_OUTPUT = false; /** * Creates a new BasicCalServices for test code. * @param machineType the machine type to be used be the BasicCALS * @param workspaceFileProperty * If non-null, the value of this system property represents the file system pathname of the workspace file * to be used to instantiate the workspace. * If null, defaultWorkspaceFileName will be used. * @param defaultWorkspaceFile This workspace file from the StandardVault will be used if the workspaceFileProperty * system property is not set. * @param defaultWorkspaceClientID defaultWorkspaceClientID a string identifying the default client for this workspace. * Client id's are used to refer to specific workspaces. This id will be used, unless the property * indicated by CALWorkspace.WORKSPACE_PROP_CLIENT_ID is set, in which case the property value will be used. * If the client id is null, the workspace is nullary. * @return the newly created BasicCALServices, or null on failure. */ public static BasicCALServices makeCALServices(MachineType machineType, String workspaceFileProperty, String defaultWorkspaceFile, String defaultWorkspaceClientID) { BasicCALServices.Config cfg = BasicCALServices.makeConfig(); cfg.setMachineType(machineType); cfg.setSourceRootProvider(new UnitTestWorkspaceRootProvider(null)); return BasicCALServices.make(workspaceFileProperty, defaultWorkspaceFile, defaultWorkspaceClientID, cfg); } /** * Creates a new BasicCalServices for test code. * @param machineType the machine type to be used be the BasicCALS * @param workspaceFileProperty * If non-null, the value of this system property represents the file system pathname of the workspace file * to be used to instantiate the workspace. * If null, defaultWorkspaceFileName will be used. * @param defaultWorkspaceFile This workspace file from the StandardVault will be used if the workspaceFileProperty * system property is not set. * @param defaultWorkspaceClientID defaultWorkspaceClientID a string identifying the default client for this workspace. * Client id's are used to refer to specific workspaces. This id will be used, unless the property * indicated by CALWorkspace.WORKSPACE_PROP_CLIENT_ID is set, in which case the property value will be used. * If the client id is null, the workspace is nullary. * @param sourceGenerationRootSuffix root suffix for output directory of compiled modules. * @return the newly created BasicCALServices, or null on failure. */ public static BasicCALServices makeCALServices(MachineType machineType, String workspaceFileProperty, String defaultWorkspaceFile, String defaultWorkspaceClientID, String sourceGenerationRootSuffix) { BasicCALServices.Config cfg = BasicCALServices.makeConfig(); cfg.setMachineType(machineType); cfg.setSourceRootProvider(new UnitTestWorkspaceRootProvider(sourceGenerationRootSuffix)); return BasicCALServices.make(workspaceFileProperty, defaultWorkspaceFile, defaultWorkspaceClientID, cfg); } /** * Used to find where the compiled files are output. * @param workspaceClientID a string identifying the default client for this workspace - must match the string used when creating the basicCALServices * @param sourceGenerationRootSuffix root suffix for output directory of compiled modules - must match the string used when creating the BasicCALServices * @return the workspace root directory for the workspace */ public static File getWorkspaceRoot(String workspaceClientID, String sourceGenerationRootSuffix) { String clientId = WorkspaceConfiguration.getDiscreteWorkspaceID(workspaceClientID); return new UnitTestWorkspaceRootProvider(sourceGenerationRootSuffix).getSourceGenerationRootOverride(clientId); } /** * An override provider that dictates where the workspace root directory should go. * * @author Joseph Wong */ private static final class UnitTestWorkspaceRootProvider implements WorkspaceManager.SourceGenerationRootOverrideProvider { /** The suffix to attach to the source generation root directory. Can be null. */ private final String sourceGenerationRootSuffix; /** * Private constructor (to be called by outer class only). * @param sourceGenerationRootSuffix */ private UnitTestWorkspaceRootProvider(String sourceGenerationRootSuffix) { this.sourceGenerationRootSuffix = sourceGenerationRootSuffix; } /** * {@inheritDoc} */ public File getSourceGenerationRootOverride(String clientID) { // if the lecc_runtime is to be redirected to a temporary location, // reassign the workspaceRoot to a temporary directory if (ENABLE_TEMP_LECC_RUNTIME_DIR) { String newDirName = "unittest_workspace_root"; if (clientID != null) { newDirName += "." + clientID; } if (sourceGenerationRootSuffix != null) { newDirName += "." + sourceGenerationRootSuffix; } File workspaceRoot = new File(System.getProperty("java.io.tmpdir"), newDirName); if (FileSystemHelper.ensureDirectoryExists(workspaceRoot)) { return workspaceRoot; } else { throw new RuntimeException("The unit test workspace root directory cannot be created: " + workspaceRoot); } } return null; } } /** * Common copies of BasicCALServices. These copies are not intended for use * in situations where modules or existing functions are added/renamed/removed. */ private static Map<String, BasicCALServices> commonCALServices = new HashMap<String, BasicCALServices>(); /** * Returns a common, shared copy of BasicCALServices parameterized by the * specified parameters. It defaults to the machine type defined by * <code>DEFAULT_MACHINE_TYPE</code>, and defaults to using 'null' as the default * workspace client ID. * * @param workspaceFile * the workspace file * * @return the common copy of BasicCALServices configured with the specified * parameters */ public static BasicCALServices getCommonCALServices(String workspaceFile) { return getCommonCALServices(DEFAULT_UNIT_TEST_MACHINE_TYPE, workspaceFile); } /** * Returns a common, shared copy of BasicCALServices parameterized by the * specified parameters. It defaults to using 'null' as the default * workspace client ID. * * @param machineType * the machine type * @param workspaceFile * the workspace file * * @return the common copy of BasicCALServices configured with the specified * parameters */ public static BasicCALServices getCommonCALServices(MachineType machineType, String workspaceFile) { return getCommonCALServices(machineType, "org.openquark.cal.test.workspace", workspaceFile, null); } /** * Returns a common, shared copy of BasicCALServices parameterized by the * specified parameters. It defaults to the machine type defined by * <code>DEFAULT_MACHINE_TYPE</code>. * * @param workspaceFileProperty * the workspace file property * @param defaultWorkspaceFile * the default workspace file * @param defaultWorkspaceClientID * the default workspace client ID * * @return the common copy of BasicCALServices configured with the specified * parameters */ public static BasicCALServices getCommonCALServices( String workspaceFileProperty, String defaultWorkspaceFile, String defaultWorkspaceClientID) { return getCommonCALServices(DEFAULT_UNIT_TEST_MACHINE_TYPE, workspaceFileProperty, defaultWorkspaceFile, defaultWorkspaceClientID); } /** * Returns a common, shared copy of BasicCALServices parameterized by the * specified parameters. * * @param machineType * the machine type * @param workspaceFileProperty * the workspace file property * @param defaultWorkspaceFile * the default workspace file * @param defaultWorkspaceClientID * the default workspace client ID * * @return the common copy of BasicCALServices configured with the specified * parameters */ public static BasicCALServices getCommonCALServices( MachineType machineType, String workspaceFileProperty, String defaultWorkspaceFile, String defaultWorkspaceClientID) { String key = machineType + "::" + workspaceFileProperty + "::" + ((defaultWorkspaceClientID == null) ? ":default:" : defaultWorkspaceClientID) + "::" + defaultWorkspaceFile; if (commonCALServices.containsKey(key)) { return commonCALServices.get(key); } else { BasicCALServices calServices = makeCALServices( machineType, workspaceFileProperty, defaultWorkspaceFile, defaultWorkspaceClientID); conditionallyCleanupRuntime(calServices, null); MessageLogger logger = new MessageLogger(); calServices.compileWorkspace(null, logger); commonCALServices.put(key, calServices); if (SHOW_INSTANCE_CREATION) { System.out.println("New common CAL services - " + key); try { throw new Exception(); } catch (Exception e) { StackTraceElement[] stackTrace = e.getStackTrace(); for (final StackTraceElement element : stackTrace) { System.out.println(" at " + element); } } } if (logger.getNErrors() > 0) { Assert.fail("Compilation of workspace failed: " + logger.getCompilerMessages()); } return calServices; } } /** * Returns an unshared copy of BasicCALServices parameterized by the * specified parameters. It defaults to the machine type defined by * <code>DEFAULT_MACHINE_TYPE</code>, and defaults to using 'null' as the default * workspace client ID. * * @param workspaceFile * the workspace file * @param shouldCompile * whether the workspace should be compiled as well. * @return the common copy of BasicCALServices configured with the specified * parameters */ public static BasicCALServices makeUnsharedUnitTestCALServices(String workspaceFile, boolean shouldCompile) { return makeUnsharedUnitTestCALServices(DEFAULT_UNIT_TEST_MACHINE_TYPE, workspaceFile, shouldCompile); } /** * Returns an unshared copy of BasicCALServices parameterized by the * specified parameters. It defaults to using 'null' as the default * workspace client ID. * * @param machineType * the machine type * @param workspaceFile * the workspace file * @param shouldCompile * whether the workspace should be compiled as well. * @return the common copy of BasicCALServices configured with the specified * parameters */ public static BasicCALServices makeUnsharedUnitTestCALServices(MachineType machineType, String workspaceFile, boolean shouldCompile) { return makeUnsharedUnitTestCALServices(machineType, "org.openquark.cal.test.workspace", workspaceFile, null, shouldCompile); } /** * Returns an unshared copy of BasicCALServices parameterized by the * specified parameters. It defaults to the machine type defined by * <code>DEFAULT_MACHINE_TYPE</code>. * * @param workspaceFileProperty * the workspace file property, or null to always use the default workspace file. * @param defaultWorkspaceFile * the default workspace file * @param defaultWorkspaceClientID * the default workspace client ID * @param shouldCompile * whether the workspace should be compiled as well. * @return the common copy of BasicCALServices configured with the specified * parameters */ public static BasicCALServices makeUnsharedUnitTestCALServices( String workspaceFileProperty, String defaultWorkspaceFile, String defaultWorkspaceClientID, boolean shouldCompile) { return makeUnsharedUnitTestCALServices(DEFAULT_UNIT_TEST_MACHINE_TYPE, workspaceFileProperty, defaultWorkspaceFile, defaultWorkspaceClientID, shouldCompile); } /** * Returns an unshared copy of BasicCALServices parameterized by the * specified parameters. * @param machineType * the machine type * @param workspaceFileProperty * the workspace file property, or null to always use the default workspace file. * @param defaultWorkspaceFile * the default workspace file * @param defaultWorkspaceClientID * the default workspace client ID * @param shouldCompile * whether the workspace should be compiled as well. * @return the common copy of BasicCALServices configured with the specified * parameters */ public static BasicCALServices makeUnsharedUnitTestCALServices( MachineType machineType, String workspaceFileProperty, String defaultWorkspaceFile, String defaultWorkspaceClientID, boolean shouldCompile) { return makeUnsharedUnitTestCALServices(machineType, workspaceFileProperty, defaultWorkspaceFile, defaultWorkspaceClientID, null, shouldCompile); } /** * Returns an unshared copy of BasicCALServices parameterized by the * specified parameters. * @param machineType * the machine type * @param workspaceFileProperty * the workspace file property, or null to always use the default workspace file. * @param defaultWorkspaceFile * the default workspace file * @param defaultWorkspaceClientID * the default workspace client ID * @param sourceGenerationRootSuffix * the suffix to attach to the source generation root directory * @param shouldCompile * whether the workspace should be compiled as well. * @return the common copy of BasicCALServices configured with the specified * parameters */ public static BasicCALServices makeUnsharedUnitTestCALServices( MachineType machineType, String workspaceFileProperty, String defaultWorkspaceFile, String defaultWorkspaceClientID, String sourceGenerationRootSuffix, boolean shouldCompile) { BasicCALServices calServices = makeCALServices( machineType, workspaceFileProperty, defaultWorkspaceFile, defaultWorkspaceClientID, sourceGenerationRootSuffix); conditionallyCleanupRuntime(calServices, sourceGenerationRootSuffix); if (shouldCompile) { MessageLogger logger = new MessageLogger(); calServices.compileWorkspace(null, logger); if (logger.getNErrors() > 0) { Assert.fail("Compilation of workspace failed: " + logger.getCompilerMessages()); } } return calServices; } /** * Flush the cache of BasicCALServices instances. If <code>INHIBIT_MULTIPLE_CLEANUPS</code> * is false, then the set of paths for cleaned up directories is also cleared. */ public static void flushCommonCALServicesCache() { if (SHOW_DEBUGGING_OUTPUT) { System.out.println("Cached BasicCALServices to be flushed: " + commonCALServices.keySet()); } commonCALServices.clear(); if (!INHIBIT_MULTIPLE_CLEANUPS) { cleanedUpDirectories.clear(); } } /** * (Set<Pair<String, ModuleName>) The set of pairs (workspace directory ID, module name) that have been cleaned up. */ private static Set<Pair<String, ModuleName>> cleanedUpDirectories = new HashSet<Pair<String, ModuleName>>(); /** * Cleans up any generated resources left behind by previous runtimes. * This clean up occurs only if <code>ENABLE_RUNTIME_CLEANUP</code> is * set to true via system properties. Also, if <code>INHIBIT_MULTIPLE_CLEANUPS</code> * is also set to true, then no directory would be cleaned up more than once for * the duration of the unit test run. * * @param calServices the copy of BasicCALServices whose resources are to be cleaned up. * @param rootSuffix the suffix for the root output directory. */ private static void conditionallyCleanupRuntime(BasicCALServices calServices, String rootSuffix) { if (ENABLE_RUNTIME_CLEANUP) { ProgramResourceRepository resourceRepository = calServices.getWorkspaceManager().getRepository(); if (resourceRepository != null && !resourceRepository.isEmpty()) { ModuleName[] moduleNames = resourceRepository.getModules(); String workspaceDirectoryID = calServices.getCALWorkspace().getWorkspaceID(); if (rootSuffix != null) { if (workspaceDirectoryID == null) { workspaceDirectoryID = rootSuffix; } else { workspaceDirectoryID += rootSuffix; } } List<Folder> moduleFolderLocatorList = new ArrayList<Folder>(moduleNames.length); for (final ModuleName moduleName : moduleNames) { Pair<String, ModuleName> moduleDirectoryID = new Pair<String, ModuleName>(workspaceDirectoryID, moduleName); // add the module directory ID to the set of cleaned up directories... the add() call // returns true if the add was successful, i.e. the set did not contain the element before // In our case that means the directory has not been cleaned up before. boolean hasNotCleanedUpBefore = cleanedUpDirectories.add(moduleDirectoryID); if (hasNotCleanedUpBefore || !INHIBIT_MULTIPLE_CLEANUPS) { moduleFolderLocatorList.add(new ProgramResourceLocator.Folder(moduleName, ResourcePath.EMPTY_PATH)); } } if (!moduleFolderLocatorList.isEmpty()) { ProgramResourceLocator[] moduleFolderLocators = moduleFolderLocatorList.toArray(new ProgramResourceLocator[moduleFolderLocatorList.size()]); try { calServices.getWorkspaceManager().getRepository().delete(moduleFolderLocators); // TODO: actually handle this. } catch (IOException ioe) { // There was a problem deleting one or more files. // This used to be just ignored. System.err.println("Problem deleting one or more files: " + ioe); } } } } } /** * Runs the specified function without any arguments. * * @param funcName * the function to be run * @param calServices * the copy of BasicCALServices to use * @return the result from running the function */ public static Object runNamedFunction(QualifiedName funcName, BasicCALServices calServices) throws GemCompilationException, CALExecutorException { return runExpr( funcName.getModuleName().toSourceText().replace('.', '_') + "_" + funcName.getUnqualifiedName(), SourceModel.Expr.makeGemCall(funcName), calServices); } /** * Evaluates the specified expression in a temporarily created function * without any arguments. * * @param testName * an name for identifying the test being run * @param expr * the expression to be evaluated * @param calServices * the copy of BasicCALServices to use * @return the result from evaluating the expression */ public static Object runExpr(String testName, SourceModel.Expr expr, BasicCALServices calServices) throws CALExecutorException, GemCompilationException { String functionName = "runCode_" + testName; ModuleName moduleName = ModuleName.make("TEST_" + functionName); SourceModel.FunctionDefn function = SourceModel.FunctionDefn.Algebraic.make( functionName, Scope.PRIVATE, null, expr); try { EntryPointSpec entryPointSpec = calServices.addNewModuleWithFunction(moduleName, function); return calServices.runFunction(entryPointSpec, new Object[0]); } finally { Status status = new Status("Removal of " + moduleName + " module"); calServices.getWorkspaceManager().removeModule(moduleName, status); Assert.assertTrue(status.isOK()); } } /** * Helper method to access the module source definition group from the * workspace * * @param calServices * the calServices containing the workspace * @return the module source definition group */ public static ModuleSourceDefinitionGroup getModuleSourceDefinitionGroup(BasicCALServices calServices) { return calServices.getCALWorkspace().getSourceDefinitionGroup(); } /** * Helper method to access the module type info of a module. Handy for getting information * about programmatically created modules. * * @param workspaceManager * @param moduleName * @return the module type info for the module, or null if the module is not in the program. */ public static ModuleTypeInfo getUnrestrictedModuleTypeInfo(WorkspaceManager workspaceManager, ModuleName moduleName) { return workspaceManager.getProgramManager().getModuleTypeInfo(moduleName); } /** * Test whether a given workspace compiles. * @param workspaceName the name of the workspace. */ public static void testWorkspaceValidity(String workspaceName) { BasicCALServices privateCopyLeccServices = CALServicesTestUtilities.makeUnsharedUnitTestCALServices(workspaceName, false); MessageLogger logger = new MessageLogger(); privateCopyLeccServices.compileWorkspace(null, logger); if (logger.getNErrors() > 0) { String message = "Compilation of the workspace failed with these errors:\n" + logger.toString(); if (SHOW_DEBUGGING_OUTPUT) { System.err.println(message); } Assert.fail(message); } } }