/*
* 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.
*/
/*
* CompilerTestUtilities.java
* Creation date: Mar 2, 2005.
* By: Joseph Wong
*/
package org.openquark.cal.compiler;
import java.io.StringReader;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import junit.framework.Assert;
import org.openquark.cal.compiler.CompilerMessage.AbortCompilation;
import org.openquark.cal.module.Cal.Core.CAL_Prelude;
import org.openquark.cal.services.BasicCALServices;
import org.openquark.cal.services.Status;
import org.openquark.cal.services.StringModuleSourceDefinition;
import org.openquark.cal.services.WorkspaceManager;
import org.openquark.util.General;
/**
* A set of helper methods for use in JUnit test cases that need to access functionalities
* provided by package-scoped classes/methods in the CAL compiler package.
*
* @author Joseph Wong
*/
public class CompilerTestUtilities {
/**
* Helper method to convert a string containing a CAL module definition
* into the corresponding parse tree. Exceptions thrown by the
* compiler are caught by this method, and it returns null when the
* parsing fails.
*
* @param moduleDefnStr
* the string containing the CAL module definition
* @return the corresponding parse tree, or null if the compiler
* cannot parse the supplied argument as a module definition
*/
static ParseTreeNode parseModuleDefnIntoParseTreeNode(String moduleDefnStr) {
return parseModuleDefnIntoParseTreeNode(moduleDefnStr, null);
}
/**
* Helper method to convert a string containing a CAL module definition
* into the corresponding parse tree. Exceptions thrown by the
* compiler are caught by this method, and it returns null when the
* parsing fails.
*
* @param moduleDefnStr
* the string containing the CAL module definition
* @param embellishments - collection in which to store source embellishments
* encounter when parsing source.
* @return the corresponding parse tree, or null if the compiler
* cannot parse the supplied argument as a module definition
*/
static ParseTreeNode parseModuleDefnIntoParseTreeNode(String moduleDefnStr, Collection<SourceEmbellishment> embellishments) {
CALCompiler compiler = new CALCompiler();
// Use a compiler-specific logger, so that fatal errors abort compilation.
CompilerMessageLogger compileLogger = new MessageLogger(true);
compiler.setCompilerMessageLogger(compileLogger);
CALParser parser = freshParser(compiler, new StringReader(moduleDefnStr), embellishments);
CALTreeParser treeParser = new CALTreeParser(compiler);
try {
parser.module();
ParseTreeNode moduleDefnNode = (ParseTreeNode)parser.getAST();
treeParser.module(moduleDefnNode);
return moduleDefnNode;
} catch (antlr.RecognitionException e) {
// Recognition (syntax) error
} catch (antlr.TokenStreamException e) {
// Bad token stream. Maybe a bad token (eg. a stray "$" sign)
} catch (AbortCompilation e) {
//compilation aborted
} catch (Exception e) {
// Major failure - internal coding error
}
return null;
}
/**
* Construct a new parser state from the given reader. The returned parser
* will have its lexer and stream selector set, and will be configured for
* ASTNodes of type ParseTreeNode. Note that tree nodes created by this
* parser will not have any source name (filename) info.
*
* @param compiler
* The compiler. This will be used for message logging and for
* parser access to its stream selector.
* @param reader
* the reader from which to parse.
* @return CALParser a new parser configured for the given args.
*
* (see CALTypeChecker.freshParser(CALCompiler, java.io.Reader) )
*/
private static final CALParser freshParser(CALCompiler compiler, java.io.Reader reader, Collection<SourceEmbellishment> embellishments) {
// Make a multiplexed lexer
CALMultiplexedLexer lexer = new CALMultiplexedLexer(compiler, reader, embellishments);
// Create a parser, it gets its tokens from the multiplexed lexer
CALParser parser = new CALParser(compiler, lexer);
String treeNodeClassName = ParseTreeNode.class.getName();
parser.setASTNodeClass(treeNodeClassName);
return parser;
}
/**
* Returns whether the message kind represents an internal coding error.
* @param messageKind the message kind to check
* @return true iff the message kind represents an internal coding error.
*/
public static boolean isInternalCodingError(MessageKind messageKind) {
return messageKind instanceof MessageKind.Fatal.InternalCodingError;
}
/**
* Get the name of a new module which doesn't exist in the program
* @param calServices the cal services object on which to generate the module name.
* @return the name of a module which doesn't exist in the program.
*/
public static ModuleName getNewModuleName(BasicCALServices calServices) {
WorkspaceManager workspaceManager = calServices.getWorkspaceManager();
Set<ModuleName> moduleNames = new HashSet<ModuleName>(Arrays.asList(workspaceManager.getModuleNamesInProgram()));
String baseName = "CompilerTestModule";
ModuleName moduleName = ModuleName.make(baseName);
int index = 1;
while (moduleNames.contains(moduleName)){
moduleName = ModuleName.make(baseName + "_" + index);
index++;
}
return moduleName;
}
/**
* Check that a given set of outer defns gives a compile error when compiled to a new module.
* @param outerDefnText the text of the outer defns.
* @param expectedErrorClass the class of the expected error.
* @param calServices the CAL services object in use in the test case.
*/
static void checkDefnForExpectedError(String outerDefnText, Class<? extends MessageKind.Error> expectedErrorClass, BasicCALServices calServices) {
checkDefnForExpectedError(new String[]{outerDefnText}, expectedErrorClass, calServices);
}
/**
* Check that a given set of outer defns gives a compile error when compiled to a new module.
* @param outerDefnTextLines the lines of text of the outer defns.
* @param expectedErrorClass the class of the expected error.
* @param calServices the CAL services object in use in the test case.
*/
static void checkDefnForExpectedError(String[] outerDefnTextLines, Class<? extends MessageKind.Error> expectedErrorClass, BasicCALServices calServices) {
ModuleName moduleName = getNewModuleName(calServices);
checkDefnForExpectedError(moduleName, outerDefnTextLines, expectedErrorClass, calServices);
}
/**
* Check that a given set of outer defns gives a compile error when compiled to a module.
* @param moduleName the name of the module.
* @param outerDefnText the text of the outer defns.
* @param expectedErrorClass the class of the expected error.
* @param calServices the CAL services object in use in the test case.
*/
static void checkDefnForExpectedError(ModuleName moduleName, String outerDefnText, Class<? extends MessageKind.Error> expectedErrorClass, BasicCALServices calServices) {
checkDefnForExpectedError(moduleName, new String[]{outerDefnText}, expectedErrorClass, calServices);
}
/**
* Check that a given set of outer defns gives a compile error when compiled to a module.
* @param moduleName the name of the module.
* @param outerDefnTextLines the lines of text of the outer defns.
* @param expectedErrorClass the class of the expected error.
* @param calServices the CAL services object in use in the test case.
*/
static void checkDefnForExpectedError(ModuleName moduleName, String[] outerDefnTextLines, Class<? extends MessageKind.Error> expectedErrorClass, BasicCALServices calServices) {
String outerDefnText = concatLines(outerDefnTextLines);
CompilerMessageLogger logger = compileAndRemoveModule(moduleName, outerDefnText, calServices);
// Check that we got an error message of the expected type.
List<CompilerMessage> messages = logger.getCompilerMessages(CompilerMessage.Severity.ERROR);
Assert.assertTrue("No errors logged.", !messages.isEmpty());
Set<Class<? extends MessageKind>> messageKindClasses = new HashSet<Class<? extends MessageKind>>();
for (int i = 0, n = messages.size(); i < n; i++) {
CompilerMessage message = messages.get(i);
messageKindClasses.add(message.getMessageKind().getClass());
}
Assert.assertTrue("expected: <" + expectedErrorClass + "> but was: <" + messageKindClasses + ">", messageKindClasses.contains(expectedErrorClass));
}
/**
* Compile outer defn and return the messages
* @param moduleName
* @param outerDefnTextLines
* @param calServices
* @return list of compiler messages
*/
static List<CompilerMessage> compileAndGetMessages(ModuleName moduleName,
String[] outerDefnTextLines,
BasicCALServices calServices) {
String outerDefnText = concatLines(outerDefnTextLines);
CompilerMessageLogger logger = compileAndRemoveModule(moduleName, outerDefnText, calServices);
// return the error messages
return logger.getCompilerMessages(CompilerMessage.Severity.ERROR);
}
/**
* Check that a given set of outer defns gives a compile warning when compiled to a new module.
* @param outerDefnText the text of the outer defns.
* @param expectedWarningClass the class of the expected warning.
* @param calServices the CAL services object in use in the test case.
*/
static void checkDefnForExpectedWarning(String outerDefnText, Class<? extends MessageKind.Warning> expectedWarningClass, BasicCALServices calServices) {
checkDefnForExpectedWarning(new String[]{outerDefnText}, expectedWarningClass, calServices);
}
/**
* Check that a given set of outer defns gives a compile warning when compiled to a new module.
* @param outerDefnTextLines the lines of text of the outer defns.
* @param expectedWarningClass the class of the expected warning.
* @param calServices the CAL services object in use in the test case.
*/
static void checkDefnForExpectedWarning(String[] outerDefnTextLines, Class<? extends MessageKind.Warning> expectedWarningClass, BasicCALServices calServices) {
final ModuleName moduleName = getNewModuleName(calServices);
checkDefnForExpectedWarning(moduleName, outerDefnTextLines, expectedWarningClass, calServices);
}
/**
* Check that a given set of outer defns gives a compile warning when compiled to a module.
* @param moduleName the name of the module.
* @param outerDefnText the text of the outer defns.
* @param expectedWarningClass the class of the expected warning.
* @param calServices the CAL services object in use in the test case.
*/
static void checkDefnForExpectedWarning(ModuleName moduleName, String outerDefnText, Class<? extends MessageKind.Warning> expectedWarningClass, BasicCALServices calServices) {
checkDefnForExpectedWarning(moduleName, new String[]{outerDefnText}, expectedWarningClass, calServices);
}
/**
* Check that a given set of outer defns gives a compile warning when compiled to a module.
* @param moduleName the name of the module.
* @param outerDefnTextLines the lines of text of the outer defns.
* @param expectedWarningClass the class of the expected warning.
* @param calServices the CAL services object in use in the test case.
*/
static void checkDefnForExpectedWarning(ModuleName moduleName, String[] outerDefnTextLines, Class<? extends MessageKind.Warning> expectedWarningClass, BasicCALServices calServices) {
final String outerDefnText = concatLines(outerDefnTextLines);
final CompilerMessageLogger logger = compileAndRemoveModule(moduleName, outerDefnText, calServices);
// Check that we got an warning message of the expected type.
final List<CompilerMessage> messages = logger.getCompilerMessages();
CompilerMessage warning = null;
for (int i = 0, n = messages.size(); i < n; i++) {
CompilerMessage message = messages.get(i);
if (message.getSeverity() == CompilerMessage.Severity.WARNING) {
warning = message;
break;
}
}
Assert.assertEquals("Unexpected errors logged.", Collections.EMPTY_LIST, logger.getCompilerMessages(CompilerMessage.Severity.ERROR));
Assert.assertTrue("No warnings logged.", warning != null);
Assert.assertEquals(expectedWarningClass, warning.getMessageKind().getClass());
}
/**
* Check that a given set of outer defns gives a compile warning when compiled to a new module.
* @param outerDefnText the text of the outer defns.
* @param expectedWarningClass the class of the expected warning.
* @param nWarningsExpected the number of warnings of that class to be expected.
* @param calServices the CAL services object in use in the test case.
*/
static void checkDefnForMultipleExpectedWarnings(String outerDefnText, Class<? extends MessageKind.Warning> expectedWarningClass, int nWarningsExpected, BasicCALServices calServices) {
final ModuleName moduleName = getNewModuleName(calServices);
final CompilerMessageLogger logger = compileAndRemoveModule(moduleName, outerDefnText, calServices);
// Check that we got an warning message of the expected type.
final List<CompilerMessage> messages = logger.getCompilerMessages();
Assert.assertEquals("Unexpected errors logged.", Collections.EMPTY_LIST, logger.getCompilerMessages(CompilerMessage.Severity.ERROR));
int nWarningsEncountered = 0;
for (int i = 0, n = messages.size(); i < n; i++) {
CompilerMessage message = messages.get(i);
if (message.getSeverity() == CompilerMessage.Severity.WARNING) {
if (message.getMessageKind().getClass().equals(expectedWarningClass)) {
nWarningsEncountered++;
} else {
Assert.assertEquals("Unexpected warning class", expectedWarningClass, message.getMessageKind().getClass());
}
}
}
Assert.assertEquals("Mismatch between number of warnings expected and number of warnings encountered", nWarningsExpected, nWarningsEncountered);
}
/**
* Asserts that two strings are equal after converting them both to
* the platform line feed representation
* @param expected
* @param actual
*/
static void assertEqualsCanonicalLineFeed(String expected, String actual) {
Assert.assertEquals(General.toPlatformLineSeparators(expected), General.toPlatformLineSeparators(actual));
}
/**
* Concatenate an array of strings into lines of text delimited with newlines.
* @param lines the string to consider as lines of text.
* @return the concatenated string.
*/
private static String concatLines(String[] lines) {
StringBuilder sb = new StringBuilder();
for (final String line : lines) {
sb.append(line).append('\n');
}
return sb.toString();
}
/**
* Compile a new module containing a module definition, an import statement for the Prelude, and the given text.
* @param moduleName the name of the module.
* @param outerDefnText the text of the definitions in the module.
* @param calServices the CAL services object in use in the test case.
* @return a logger which logged the results of the compile.
*/
private static CompilerMessageLogger compileAndRemoveModule(ModuleName moduleName, String outerDefnText, BasicCALServices calServices) {
String sourceDefString = "module " + moduleName + ";\n";
sourceDefString += "import " + CAL_Prelude.MODULE_NAME + ";\n";
sourceDefString += outerDefnText;
ModuleSourceDefinition sourceDef = new StringModuleSourceDefinition(moduleName, sourceDefString);
// Get the workspace manager and the logger.
WorkspaceManager workspaceManager = calServices.getWorkspaceManager();
CompilerMessageLogger logger = new MessageLogger();
// Compile the module.
workspaceManager.makeModule(sourceDef, logger);
// Remove the module.
calServices.getWorkspaceManager().removeModule(moduleName, new Status("Remove module status."));
return logger;
}
}