/*
* 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);
}
}
}