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