/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright 1997-2010 Oracle and/or its affiliates. All rights reserved. * * Oracle and Java are registered trademarks of Oracle and/or its affiliates. * Other names may be trademarks of their respective owners. * * The contents of this file are subject to the terms of either the GNU * General Public License Version 2 only ("GPL") or the Common * Development and Distribution License("CDDL") (collectively, the * "License"). You may not use this file except in compliance with the * License. You can obtain a copy of the License at * http://www.netbeans.org/cddl-gplv2.html * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the * specific language governing permissions and limitations under the * License. When distributing the software, include this License Header * Notice in each file and include the License file at * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the GPL Version 2 section of the License file that * accompanied this code. If applicable, add the following below the * License Header, with the fields enclosed by brackets [] replaced by * your own identifying information: * "Portions Copyrighted [year] [name of copyright owner]" * * Contributor(s): * * The Original Software is NetBeans. The Initial Developer of the Original * Software is Sun Microsystems, Inc. Portions Copyright 1997-2008 Sun * Microsystems, Inc. All Rights Reserved. * * If you wish your version of this file to be governed by only the CDDL * or only the GPL Version 2, indicate your decision by adding * "[Contributor] elects to include this software in this distribution * under the [CDDL or GPL Version 2] license." If you do not indicate a * single choice of license, a recipient has the option to distribute * your version of this file under either the CDDL, the GPL Version 2 or * to extend the choice of license to its licensees as provided above. * However, if you add GPL Version 2 code and therefore, elected the GPL * Version 2 license, then the option applies only if the new code is * made subject to such option by the copyright holder. */ package org.netbeans.modules.csl.api.test; import java.awt.event.ActionEvent; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.io.StringWriter; import java.io.Writer; import java.lang.ref.Reference; import java.lang.ref.SoftReference; import java.lang.reflect.Method; import java.net.URL; import java.nio.CharBuffer; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import java.util.Set; import java.util.concurrent.Callable; import java.util.logging.LogRecord; import java.util.logging.Logger; import java.util.prefs.Preferences; import javax.swing.Action; import javax.swing.JTextArea; import javax.swing.SwingUtilities; import javax.swing.text.BadLocationException; import javax.swing.text.Caret; import org.netbeans.api.lexer.Language; import org.netbeans.api.lexer.TokenSequence; import org.netbeans.junit.NbTestCase; import org.netbeans.lib.lexer.LanguageManager; import org.netbeans.modules.csl.api.CodeCompletionContext; import org.netbeans.modules.csl.api.Error; import org.netbeans.modules.csl.api.HintFix; import org.netbeans.modules.csl.api.KeystrokeHandler; import org.netbeans.modules.csl.api.ColoringAttributes; import org.netbeans.modules.csl.api.CodeCompletionHandler; import org.netbeans.modules.csl.api.CodeCompletionHandler.QueryType; import org.netbeans.modules.csl.api.CodeCompletionResult; import org.netbeans.modules.csl.api.CompletionProposal; import org.netbeans.modules.csl.api.ElementKind; import org.netbeans.modules.csl.api.Formatter; import org.netbeans.modules.csl.api.GsfLanguage; import org.netbeans.modules.csl.api.Hint; import org.netbeans.modules.csl.api.HintsProvider; import org.netbeans.modules.csl.api.HtmlFormatter; import org.netbeans.modules.csl.api.InstantRenamer; import org.netbeans.modules.csl.api.OccurrencesFinder; import org.netbeans.modules.csl.api.SemanticAnalyzer; import org.netbeans.modules.csl.api.StructureItem; import org.netbeans.modules.csl.api.StructureScanner; import org.netbeans.modules.csl.api.HintSeverity; import org.netbeans.modules.csl.api.Rule; import org.netbeans.modules.csl.api.Rule.AstRule; import org.netbeans.modules.csl.api.Rule.ErrorRule; import org.netbeans.modules.csl.api.Rule.SelectionRule; import org.netbeans.modules.csl.api.Rule.UserConfigurableRule; import org.netbeans.modules.csl.api.RuleContext; import org.netbeans.modules.csl.spi.DefaultLanguageConfig; import org.netbeans.modules.parsing.api.ResultIterator; import org.netbeans.modules.parsing.lucene.support.Index.Status; import org.netbeans.modules.parsing.lucene.support.Queries.QueryKind; import org.netbeans.modules.parsing.spi.indexing.Indexable; import org.openide.ErrorManager; import org.openide.filesystems.FileObject; import org.openide.filesystems.FileSystem; import org.openide.filesystems.FileUtil; import org.openide.util.Exceptions; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.WeakHashMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.logging.Handler; import java.util.logging.Level; import javax.swing.JEditorPane; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentEvent.ElementChange; import javax.swing.event.DocumentEvent.EventType; import javax.swing.event.DocumentListener; import javax.swing.text.DefaultEditorKit; import javax.swing.text.Document; import javax.swing.text.Element; import org.netbeans.api.annotations.common.NullAllowed; import org.netbeans.api.editor.mimelookup.MimeLookup; import org.netbeans.api.editor.mimelookup.MimePath; import org.netbeans.api.editor.mimelookup.test.MockMimeLookup; import org.netbeans.api.editor.settings.SimpleValueNames; import org.netbeans.api.java.classpath.ClassPath; import org.netbeans.api.java.classpath.GlobalPathRegistry; import org.netbeans.api.lexer.TokenHierarchy; import org.netbeans.api.lexer.TokenHierarchyEvent; import org.netbeans.api.lexer.TokenHierarchyListener; import org.netbeans.api.project.Project; import org.netbeans.api.project.ProjectManager; import org.netbeans.editor.BaseDocument; import org.netbeans.editor.BaseKit; import org.netbeans.editor.Utilities; import org.netbeans.junit.MockServices; import org.netbeans.modules.editor.indent.spi.CodeStylePreferences; import org.netbeans.modules.csl.api.DeclarationFinder; import org.netbeans.modules.csl.api.DeclarationFinder.DeclarationLocation; import org.netbeans.modules.csl.api.EditHistory; import org.netbeans.modules.csl.api.EditList; import org.netbeans.modules.csl.api.OffsetRange; import org.netbeans.modules.csl.api.PreviewableFix; import org.netbeans.modules.csl.api.Severity; import org.netbeans.modules.csl.core.CslEditorKit; import org.netbeans.modules.csl.core.GsfIndentTaskFactory; import org.netbeans.modules.csl.core.GsfReformatTaskFactory; import org.netbeans.modules.csl.core.LanguageRegistry; import org.netbeans.modules.csl.editor.codetemplates.CslCorePackageAccessor; import org.netbeans.modules.csl.hints.infrastructure.GsfHintsManager; import org.netbeans.modules.csl.hints.infrastructure.HintsSettings; import org.netbeans.modules.csl.hints.infrastructure.Pair; import org.netbeans.modules.csl.spi.DefaultError; import org.netbeans.modules.csl.spi.GsfUtilities; import org.netbeans.modules.csl.spi.ParserResult; import org.netbeans.modules.editor.NbEditorKit; import org.netbeans.modules.editor.bracesmatching.api.BracesMatchingTestUtils; import org.netbeans.modules.editor.indent.api.Reformat; import org.netbeans.modules.parsing.api.ParserManager; import org.netbeans.modules.parsing.api.Snapshot; import org.netbeans.modules.parsing.api.Source; import org.netbeans.modules.parsing.api.UserTask; import org.netbeans.modules.parsing.impl.indexing.CacheFolder; import org.netbeans.modules.parsing.impl.indexing.FileObjectIndexable; import org.netbeans.modules.parsing.impl.indexing.RepositoryUpdater; import org.netbeans.modules.parsing.impl.indexing.SPIAccessor; import org.netbeans.modules.parsing.impl.indexing.lucene.LuceneIndexFactory; import org.netbeans.modules.parsing.lucene.support.DocumentIndex; import org.netbeans.modules.parsing.lucene.support.IndexDocument; import org.netbeans.modules.parsing.lucene.support.Queries; import org.netbeans.modules.parsing.spi.Parser; import org.netbeans.modules.parsing.spi.indexing.Context; import org.netbeans.modules.parsing.spi.indexing.EmbeddingIndexer; import org.netbeans.modules.parsing.spi.indexing.EmbeddingIndexerFactory; import org.netbeans.modules.parsing.spi.indexing.PathRecognizer; import org.netbeans.spi.editor.bracesmatching.BracesMatcher; import org.netbeans.spi.editor.bracesmatching.BracesMatcherFactory; import org.netbeans.spi.editor.bracesmatching.MatcherContext; import org.netbeans.spi.java.classpath.ClassPathProvider; import org.openide.loaders.DataObject; import org.openide.loaders.DataObjectNotFoundException; import org.openide.util.test.MockLookup; /** * @author Tor Norbye */ public abstract class CslTestBase extends NbTestCase { public CslTestBase(String testName) { super(testName); } private Map<String, ClassPath> classPathsForTest; @Override protected void setUp() throws Exception { super.setUp(); clearWorkDir(); System.setProperty("netbeans.user", getWorkDirPath()); // XXX are the following four lines actually necessary? final FileObject wd = FileUtil.toFileObject(getWorkDir()); assert wd != null; FileObject cache = FileUtil.createFolder(wd, "var/cache"); assert cache != null; CacheFolder.setCacheFolder(cache); // This has to be before touching ClassPath class MockLookup.setInstances(new TestClassPathProvider(), new TestPathRecognizer()); classPathsForTest = createClassPathsForTest(); if (classPathsForTest != null) { RepositoryUpdater.getDefault().start(true); Logger logger = Logger.getLogger(RepositoryUpdater.class.getName() + ".tests"); logger.setLevel(Level.FINEST); Waiter w = new Waiter(); logger.addHandler(w); // initialize classpaths indexing for(String cpId : classPathsForTest.keySet()) { ClassPath cp = classPathsForTest.get(cpId); GlobalPathRegistry.getDefault().register(cpId, new ClassPath [] { cp }); } w.waitForScanToFinish(); logger.removeHandler(w); } } @Override protected void tearDown() throws Exception { if (classPathsForTest != null && !classPathsForTest.isEmpty()) { Logger logger = Logger.getLogger(RepositoryUpdater.class.getName() + ".tests"); logger.setLevel(Level.FINEST); Waiter w = new Waiter(); logger.addHandler(w); for(String cpId : classPathsForTest.keySet()) { ClassPath cp = classPathsForTest.get(cpId); GlobalPathRegistry.getDefault().unregister(cpId, new ClassPath [] { cp }); } w.waitForScanToFinish(); logger.removeHandler(w); } super.tearDown(); } protected void initializeRegistry() { DefaultLanguageConfig defaultLanguage = getPreferredLanguage(); if (defaultLanguage == null) { fail("If you don't implement getPreferredLanguage(), you must override initializeRegistry!"); return; } if (!LanguageRegistry.getInstance().isSupported(getPreferredMimeType())) { List<Action> actions = Collections.emptyList(); org.netbeans.modules.csl.core.Language dl = new org.netbeans.modules.csl.core.Language( "unknown", getPreferredMimeType(), actions, defaultLanguage, getCodeCompleter(), getRenameHandler(), defaultLanguage.getDeclarationFinder(), defaultLanguage.getFormatter(), getKeystrokeHandler(), getIndexerFactory(), getStructureScanner(), null, defaultLanguage.isUsingCustomEditorKit()); List<org.netbeans.modules.csl.core.Language> languages = new ArrayList<org.netbeans.modules.csl.core.Language>(); languages.add(dl); CslCorePackageAccessor.get().languageRegistryAddLanguages(languages); } } protected FileObject touch(final String dir, final String path) throws IOException { return touch(new File(dir), path); } protected FileObject touch(final File dir, final String path) throws IOException { if (!dir.isDirectory()) { assertTrue("success to create " + dir, dir.mkdirs()); } FileObject dirFO = FileUtil.toFileObject(FileUtil.normalizeFile(dir)); return touch(dirFO, path); } protected FileObject touch(final FileObject dir, final String path) throws IOException { return FileUtil.createData(dir, path); } public static final FileObject copyStringToFileObject(FileObject fo, String content) throws IOException { OutputStream os = fo.getOutputStream(); try { InputStream is = new ByteArrayInputStream(content.getBytes("UTF-8")); try { FileUtil.copy(is, os); return fo; } finally { is.close(); } } finally { os.close(); } } /** Return the offset of the given position, indicated by ^ in the line fragment * from the fuller text */ public static int getCaretOffset(String text, String caretLine) { return getCaretOffsetInternal(text, caretLine).offset; } /** * Like <code>getCaretOffset</code>, but the returned <code>CaretLineOffset</code> * contains also the modified <code>caretLine</code> param. * @param text * @param caretLine * @return */ private static CaretLineOffset getCaretOffsetInternal(String text, String caretLine) { int caretDelta = caretLine.indexOf('^'); assertTrue(caretDelta != -1); caretLine = caretLine.substring(0, caretDelta) + caretLine.substring(caretDelta + 1); int lineOffset = text.indexOf(caretLine); assertTrue("No occurrence of caretLine " + caretLine + " in text '" + text + "'", lineOffset != -1); int caretOffset = lineOffset + caretDelta; return new CaretLineOffset(caretOffset, caretLine); } /** Copy-pasted from APISupport. */ protected static String slurp(File file) throws IOException { InputStream is = new FileInputStream(file); try { ByteArrayOutputStream baos = new ByteArrayOutputStream(); FileUtil.copy(is, baos); return baos.toString("UTF-8"); } finally { is.close(); } } protected FileObject getTestFile(String relFilePath) { File wholeInputFile = new File(getDataDir(), relFilePath); if (!wholeInputFile.exists()) { NbTestCase.fail("File " + wholeInputFile + " not found."); } FileObject fo = FileUtil.toFileObject(wholeInputFile); assertNotNull(fo); return fo; } /** * Gets the <code>Source</code> for a file. This method makes sure that a * <code>Document</code> is loaded from the file and accessible through the * returned <code>Source</code> instance. Many language-specific feature * implementations rely on that. * * @param relFilePath The file path relative to <code>getDataDir()</code>. * * @return The <code>Source</code> instance with a the <code>Document</code> * loaded from the specified file. */ protected Source getTestSource(FileObject f) { Document doc = GsfUtilities.getDocument(f, true); return Source.create(doc); } public Project getTestProject(String relativePath) throws Exception { FileObject projectDir = getTestFile(relativePath); assertNotNull(projectDir); Project project = ProjectManager.getDefault().findProject(projectDir); assertNotNull(project); return project; } protected String readFile(final FileObject fo) { return read(fo); } public static String read(final FileObject fo) { try { final StringBuilder sb = new StringBuilder(5000); fo.getFileSystem().runAtomicAction(new FileSystem.AtomicAction() { public void run() throws IOException { if (fo == null) { return; } InputStream is = fo.getInputStream(); BufferedReader reader = new BufferedReader(new InputStreamReader(is)); while (true) { String line = reader.readLine(); if (line == null) { break; } sb.append(line); sb.append('\n'); } } }); if (sb.length() > 0) { return sb.toString(); } else { return null; } } catch (IOException ioe){ ErrorManager.getDefault().notify(ioe); return null; } } public BaseDocument getDocument(String s, final String mimeType, final Language language) { try { BaseDocument doc = new BaseDocument(true, mimeType) { @Override public boolean isIdentifierPart(char ch) { if (mimeType != null) { org.netbeans.modules.csl.core.Language l = LanguageRegistry.getInstance().getLanguageByMimeType(mimeType); if (l != null) { GsfLanguage gsfLanguage = l.getGsfLanguage(); if (gsfLanguage != null) { return gsfLanguage.isIdentifierChar(ch); } } } return super.isIdentifierPart(ch); } }; //doc.putProperty("mimeType", mimeType); doc.putProperty(org.netbeans.api.lexer.Language.class, language); doc.insertString(0, s, null); return doc; } catch (Exception ex){ fail(ex.toString()); return null; } } public BaseDocument getDocument(String s, String mimeType) { Language<?> language = LanguageManager.getInstance().findLanguage(mimeType); assertNotNull(language); return getDocument(s, mimeType, language); } // public static BaseDocument createDocument(String s) { // try { // BaseDocument doc = new BaseDocument(null, false); // doc.insertString(0, s, null); // // return doc; // } // catch (Exception ex){ // fail(ex.toString()); // return null; // } // } // protected BaseDocument getDocument(String s) { String mimeType = getPreferredMimeType(); assertNotNull("You must implement " + getClass().getName() + ".getPreferredMimeType()", mimeType); GsfLanguage language = getPreferredLanguage(); assertNotNull("You must implement " + getClass().getName() + ".getPreferredLanguage()", language); return getDocument(s, mimeType, language.getLexerLanguage()); } protected BaseDocument getDocument(FileObject fo) { return getDocument(fo, getPreferredMimeType(), getPreferredLanguage().getLexerLanguage()); } protected BaseDocument getDocument(FileObject fo, String mimeType, Language language) { try { // DataObject dobj = DataObject.find(fo); // assertNotNull(dobj); // // EditorCookie ec = (EditorCookie)dobj.getCookie(EditorCookie.class); // assertNotNull(ec); // // return (BaseDocument)ec.openDocument(); BaseDocument doc = getDocument(readFile(fo), mimeType, language); try { DataObject dobj = DataObject.find(fo); doc.putProperty(Document.StreamDescriptionProperty, dobj); } catch (DataObjectNotFoundException dnfe) { fail(dnfe.toString()); } return doc; } catch (Exception ex){ fail(ex.toString()); return null; } } public static String readFile(File f) throws IOException { FileReader r = new FileReader(f); int fileLen = (int)f.length(); CharBuffer cb = CharBuffer.allocate(fileLen); r.read(cb); cb.rewind(); return cb.toString(); } protected File getDataSourceDir() { // Check whether token dump file exists // Try to remove "/build/" from the dump file name if it exists. // Otherwise give a warning. File inputFile = getDataDir(); String inputFilePath = inputFile.getAbsolutePath(); boolean replaced = false; if (inputFilePath.indexOf(pathJoin("build", "test")) != -1) { inputFilePath = inputFilePath.replace(pathJoin("build", "test"), pathJoin("test")); replaced = true; } if (!replaced && inputFilePath.indexOf(pathJoin("test", "work", "sys")) != -1) { inputFilePath = inputFilePath.replace(pathJoin("test", "work", "sys"), pathJoin("test", "unit")); replaced = true; } if (!replaced) { System.err.println("Warning: Attempt to use dump file " + "from sources instead of the generated test files failed.\n" + "Patterns '/build/test/' or '/test/work/sys/' not found in " + inputFilePath ); } inputFile = new File(inputFilePath); assertTrue(inputFile.exists()); return inputFile; } private static String pathJoin(String... chunks) { StringBuilder result = new StringBuilder(File.separator); for (String chunk : chunks) { result.append(chunk).append(File.separatorChar); } return result.toString(); } protected File getDataFile(String relFilePath) { File inputFile = new File(getDataSourceDir(), relFilePath); return inputFile; } protected boolean failOnMissingGoldenFile() { return true; } protected void assertDescriptionMatches(String relFilePath, String description, boolean includeTestName, String ext) throws Exception { assertDescriptionMatches(relFilePath, description, includeTestName, ext, true); } protected void assertDescriptionMatches(String relFilePath, String description, boolean includeTestName, String ext, boolean checkFileExistence) throws Exception { File rubyFile = getDataFile(relFilePath); if (checkFileExistence && !rubyFile.exists()) { NbTestCase.fail("File " + rubyFile + " not found."); } File goldenFile = getDataFile(relFilePath + (includeTestName ? ("." + getName()) : "") + ext); if (!goldenFile.exists()) { if (!goldenFile.createNewFile()) { NbTestCase.fail("Cannot create file " + goldenFile); } FileWriter fw = new FileWriter(goldenFile); try { fw.write(description); } finally{ fw.close(); } if (failOnMissingGoldenFile()) { NbTestCase.fail("Created generated golden file " + goldenFile + "\nPlease re-run the test."); } return; } String expected = readFile(goldenFile); // Because the unit test differ is so bad... if (false) { // disabled if (!expected.equals(description)) { BufferedWriter fw = new BufferedWriter(new FileWriter("/tmp/expected.txt")); fw.write(expected); fw.close(); fw = new BufferedWriter(new FileWriter("/tmp/actual.txt")); fw.write(description); fw.close(); } } String expectedTrimmed = expected.trim(); String actualTrimmed = description.trim(); if (expectedTrimmed.equals(actualTrimmed)) { return; // Actual and expected content are equals --> Test passed } else { // We want to ignore different line separators (like \r\n against \n) because they // might be causing failing tests on a different operation systems like Windows :] String expectedUnified = expectedTrimmed.replaceAll("\r", ""); String actualUnified = actualTrimmed.replaceAll("\r", ""); if (expectedUnified.equals(actualUnified)) { return; // Only difference is in line separation --> Test passed } // There are some diffrerences between expected and actual content --> Test failed fail(getContentDifferences(relFilePath, ext, includeTestName, expectedUnified, actualUnified)); } } private String getContentDifferences(String relFilePath, String ext, boolean includeTestName, String expected, String actual) { StringBuilder sb = new StringBuilder(); sb.append("Content does not match between '").append(relFilePath).append("' and '").append(relFilePath); if (includeTestName) { sb.append(getName()); } sb.append(ext).append("'\n"); sb.append("Expected content is: \n\n").append(expected).append("\n\nbut actual is: \n\n").append(actual).append("\n\n"); sb.append("It differs in the following things: \n\n"); List<String> expectedLines = Arrays.asList(expected.split("\n")); List<String> actualLines = Arrays.asList(actual.split("\n")); if (expectedLines.size() != actualLines.size()) { sb.append("Number of lines: \n\tExpected: ").append(expectedLines.size()).append("\n\tActual: ").append(actualLines.size()).append("\n\n"); } // Appending lines which are missing in expected content and are present in actual content boolean firstOccurence = true; for (String actualLine : actualLines) { if (expectedLines.contains(actualLine) == false) { if (firstOccurence) { sb.append("Actual content contains following lines which are missing in expected content: \n"); firstOccurence = false; } sb.append("\t").append(actualLine).append("\n"); } } // Appending lines which are missing in actual content and are present in expected content firstOccurence = true; for (String expectedLine : expectedLines) { if (actualLines.contains(expectedLine) == false) { // If at least one line missing in actual content we want to append header line if (firstOccurence) { sb.append("Expected content contains following lines which are missing in actual content: \n"); firstOccurence = false; } sb.append("\t").append(expectedLine).append("\n"); } } return sb.toString(); } protected void assertDescriptionMatches(FileObject fileObject, String description, boolean includeTestName, String ext) throws IOException { assertDescriptionMatches(fileObject, description, includeTestName, ext, false); } protected void assertDescriptionMatches(FileObject fileObject, String description, boolean includeTestName, String ext, boolean goldenFileInTestFileDir) throws IOException { String goldenFileDir = goldenFileInTestFileDir ? FileUtil.getRelativePath(FileUtil.toFileObject(getDataDir()), fileObject.getParent()) : "testfiles"; File goldenFile = getDataFile(goldenFileDir + "/" + fileObject.getNameExt() + (includeTestName ? ("." + getName()) : "") + ext); if (!goldenFile.exists()) { if (!goldenFile.createNewFile()) { NbTestCase.fail("Cannot create file " + goldenFile); } FileWriter fw = new FileWriter(goldenFile); try { fw.write(description); } finally{ fw.close(); } NbTestCase.fail("Created generated golden file " + goldenFile + "\nPlease re-run the test."); } String expected = readFile(goldenFile); // Because the unit test differ is so bad... if (false) { // disabled if (!expected.equals(description)) { BufferedWriter fw = new BufferedWriter(new FileWriter("/tmp/expected.txt")); fw.write(expected); fw.close(); fw = new BufferedWriter(new FileWriter("/tmp/actual.txt")); fw.write(description); fw.close(); } } assertEquals("Not matching goldenfile: " + FileUtil.getFileDisplayName(fileObject), expected.trim(), description.trim()); } protected void assertFileContentsMatches(String relFilePath, String description, boolean includeTestName, String ext) throws Exception { File rubyFile = getDataFile(relFilePath); if (!rubyFile.exists()) { NbTestCase.fail("File " + rubyFile + " not found."); } File goldenFile = getDataFile(relFilePath + (includeTestName ? ("." + getName()) : "") + ext); if (!goldenFile.exists()) { if (!goldenFile.createNewFile()) { NbTestCase.fail("Cannot create file " + goldenFile); } FileWriter fw = new FileWriter(goldenFile); try { fw.write(description); } finally{ fw.close(); } NbTestCase.fail("Created generated golden file " + goldenFile + "\nPlease re-run the test."); } String expected = readFile(goldenFile); assertEquals(expected.trim(), description.trim()); } public void assertEquals(Collection<String> s1, Collection<String> s2) { List<String> l1 = new ArrayList<String>(); l1.addAll(s1); Collections.sort(l1); List<String> l2 = new ArrayList<String>(); l2.addAll(s2); Collections.sort(l2); assertEquals(l1.toString(), l2.toString()); } protected void createFilesFromDesc(FileObject folder, String descFile) throws Exception { File taskFile = new File(getDataDir(), descFile); assertTrue(taskFile.exists()); BufferedReader br = new BufferedReader(new FileReader(taskFile)); while (true) { String line = br.readLine(); if (line == null || line.trim().length() == 0) { break; } if (line.endsWith("\r")) { line = line.substring(0, line.length()-1); } String path = line; if (path.endsWith("/")) { path = path.substring(0, path.length()-1); FileObject f = FileUtil.createFolder(folder, path); assertNotNull(f); } else { FileObject f = FileUtil.createData(folder, path); assertNotNull(f); } } } public static void createFiles(File baseDir, String... paths) throws IOException { assertNotNull(baseDir); for (String path : paths) { FileObject baseDirFO = FileUtil.toFileObject(baseDir); assertNotNull(baseDirFO); assertNotNull(FileUtil.createData(baseDirFO, path)); } } public static void createFile(FileObject dir, String relative, String contents) throws IOException { FileObject datafile = FileUtil.createData(dir, relative); OutputStream os = datafile.getOutputStream(); Writer writer = new BufferedWriter(new OutputStreamWriter(os)); writer.write(contents); writer.close(); } //////////////////////////////////////////////////////////////////////////// // Parsing Info Based Tests //////////////////////////////////////////////////////////////////////////// protected Parser getParser() { Parser parser = getPreferredLanguage().getParser(); assertNotNull("You must override getParser(), either from your GsfLanguage or your test class", parser); return parser; } protected void validateParserResult(@NullAllowed ParserResult result) { // Clients can do checks to make sure everything is okay here. } protected DefaultLanguageConfig getPreferredLanguage() { return null; } protected String getPreferredMimeType() { return null; } // public FileObject createFileWithText(String text) throws IOException { // FileObject workDir = FileUtil.toFileObject(getWorkDir()); // // String name = getName() + System.currentTimeMillis(); // FileObject file = workDir.getFileObject(name); // if (file != null) { // file.delete(); // } // file = workDir.createData(name); // return copyStringToFileObject(file, text); // } //////////////////////////////////////////////////////////////////////////// // Parser tests //////////////////////////////////////////////////////////////////////////// protected void checkErrors(final String relFilePath) throws Exception { Source testSource = getTestSource(getTestFile(relFilePath)); ParserManager.parse(Collections.singleton(testSource), new UserTask() { public @Override void run(ResultIterator resultIterator) throws Exception { Parser.Result r = resultIterator.getParserResult(); assertNotNull(r); assertTrue(r instanceof ParserResult); ParserResult pr = (ParserResult) r; List<? extends Error> diagnostics = pr.getDiagnostics(); String annotatedSource = annotateErrors(diagnostics); assertDescriptionMatches(relFilePath, annotatedSource, false, ".errors"); } }); } protected String annotateErrors(List<? extends Error> errors) { List<String> descs = new ArrayList<String>(); for (Error error : errors) { StringBuilder desc = new StringBuilder(); if (error.getKey() != null) { desc.append("["); desc.append(error.getKey()); desc.append("] "); } desc.append(error.getStartPosition()); desc.append("-"); desc.append(error.getEndPosition()); desc.append(":"); desc.append(error.getDisplayName()); if (error.getDescription() != null) { desc.append(" ; " ); desc.append(error.getDescription()); } descs.add(desc.toString()); } Collections.sort(descs); StringBuilder summary = new StringBuilder(); for (String desc : descs) { summary.append(desc); summary.append("\n"); } return summary.toString(); } //////////////////////////////////////////////////////////////////////////// // Keystroke completion tests //////////////////////////////////////////////////////////////////////////// protected KeystrokeHandler getKeystrokeHandler() { KeystrokeHandler handler = getPreferredLanguage().getKeystrokeHandler(); assertNotNull("You must override getKeystrokeHandler, either from your GsfLanguage or your test class", handler); return handler; } // Also requires getFormatter(IndentPref) defined below under the formatting tests protected void assertMatches(String original) throws BadLocationException { KeystrokeHandler bc = getKeystrokeHandler(); int caretPos = original.indexOf('^'); original = original.substring(0, caretPos) + original.substring(caretPos+1); int matchingCaretPos = original.indexOf('^'); assertTrue(caretPos < matchingCaretPos); original = original.substring(0, matchingCaretPos) + original.substring(matchingCaretPos+1); BaseDocument doc = getDocument(original); OffsetRange range = bc.findMatching(doc, caretPos); assertNotSame("Didn't find matching token for " + /*LexUtilities.getToken(doc, caretPos).text().toString()*/ " position " + caretPos, OffsetRange.NONE, range); assertEquals("forward match not found; found '" + doc.getText(range.getStart(), range.getLength()) + "' instead of " + /*LexUtilities.getToken(doc, matchingCaretPos).text().toString()*/ " position " + matchingCaretPos, matchingCaretPos, range.getStart()); // Perform reverse match range = bc.findMatching(doc, matchingCaretPos); assertNotSame(OffsetRange.NONE, range); assertEquals("reverse match not found; found '" + doc.getText(range.getStart(), range.getLength()) + "' instead of " + /*LexUtilities.getToken(doc, caretPos).text().toString()*/ " position " + caretPos, caretPos, range.getStart()); } protected void assertMatches2(String original) throws BadLocationException { BracesMatcherFactory factory = MimeLookup.getLookup(getPreferredMimeType()).lookup(BracesMatcherFactory.class); int caretPos = original.indexOf('^'); original = original.substring(0, caretPos) + original.substring(caretPos+1); int matchingCaretPos = original.indexOf('^'); original = original.substring(0, matchingCaretPos) + original.substring(matchingCaretPos+1); BaseDocument doc = getDocument(original); MatcherContext context = BracesMatchingTestUtils.createMatcherContext(doc, caretPos, false, 1); BracesMatcher matcher = factory.createMatcher(context); int [] origin = null, matches = null; try { origin = matcher.findOrigin(); matches = matcher.findMatches(); } catch (InterruptedException ex) { } assertNotNull("Did not find origin for " + " position " + caretPos, origin); assertNotNull("Did not find matches for " + " position " + caretPos, matches); assertEquals("Incorrect origin", caretPos, origin[0]); assertEquals("Incorrect matches", matchingCaretPos, matches[0]); //Reverse direction context = BracesMatchingTestUtils.createMatcherContext(doc, matchingCaretPos, false, 1); matcher = factory.createMatcher(context); try { origin = matcher.findOrigin(); matches = matcher.findMatches(); } catch (InterruptedException ex) { } assertNotNull("Did not find origin for " + " position " + caretPos, origin); assertNotNull("Did not find matches for " + " position " + caretPos, matches); assertEquals("Incorrect origin", matchingCaretPos, origin[0]); assertEquals("Incorrect matches", caretPos, matches[0]); } // Copied from LexUtilities public static int getLineIndent(BaseDocument doc, int offset) { try { int start = Utilities.getRowStart(doc, offset); int end; if (Utilities.isRowWhite(doc, start)) { end = Utilities.getRowEnd(doc, offset); } else { end = Utilities.getRowFirstNonWhite(doc, start); } int indent = Utilities.getVisualColumn(doc, end); return indent; } catch (BadLocationException ble) { Exceptions.printStackTrace(ble); return 0; } } protected void insertChar(String original, char insertText, String expected, String selection, boolean codeTemplateMode) throws Exception { String source = original; String reformatted = expected; Formatter formatter = getFormatter(null); int sourcePos = source.indexOf('^'); assertNotNull(sourcePos); source = source.substring(0, sourcePos) + source.substring(sourcePos+1); int reformattedPos = reformatted.indexOf('^'); assertNotNull(reformattedPos); reformatted = reformatted.substring(0, reformattedPos) + reformatted.substring(reformattedPos+1); JEditorPane ta = getPane(source); Caret caret = ta.getCaret(); caret.setDot(sourcePos); if (selection != null) { int start = original.indexOf(selection); assertTrue(start != -1); assertTrue("Ambiguous selection - multiple occurrences of selection string", original.indexOf(selection, start+1) == -1); ta.setSelectionStart(start); ta.setSelectionEnd(start+selection.length()); assertEquals(selection, ta.getSelectedText()); } BaseDocument doc = (BaseDocument) ta.getDocument(); if (codeTemplateMode) { // Copied from editor/codetemplates/src/org/netbeans/lib/editor/codetemplates/CodeTemplateInsertHandler.java String EDITING_TEMPLATE_DOC_PROPERTY = "processing-code-template"; // NOI18N doc.putProperty(EDITING_TEMPLATE_DOC_PROPERTY, Boolean.TRUE); } if (formatter != null) { configureIndenters(doc, formatter, true); } setupDocumentIndentation(doc, null); runKitAction(ta, DefaultEditorKit.defaultKeyTypedAction, ""+insertText); String formatted = doc.getText(0, doc.getLength()); assertEquals(reformatted, formatted); if (reformattedPos != -1) { assertEquals(reformattedPos, caret.getDot()); } } protected void deleteChar(String original, String expected) throws Exception { String source = original; String reformatted = expected; Formatter formatter = getFormatter(null); int sourcePos = source.indexOf('^'); assertNotNull(sourcePos); source = source.substring(0, sourcePos) + source.substring(sourcePos+1); int reformattedPos = reformatted.indexOf('^'); assertNotNull(reformattedPos); reformatted = reformatted.substring(0, reformattedPos) + reformatted.substring(reformattedPos+1); JEditorPane ta = getPane(source); Caret caret = ta.getCaret(); caret.setDot(sourcePos); BaseDocument doc = (BaseDocument) ta.getDocument(); if (formatter != null) { configureIndenters(doc, formatter, true); } setupDocumentIndentation(doc, null); runKitAction(ta, DefaultEditorKit.deletePrevCharAction, "\n"); String formatted = doc.getText(0, doc.getLength()); assertEquals(reformatted, formatted); if (reformattedPos != -1) { assertEquals(reformattedPos, caret.getDot()); } } protected void deleteWord(String original, String expected) throws Exception { String source = original; String reformatted = expected; Formatter formatter = getFormatter(null); int sourcePos = source.indexOf('^'); assertNotNull(sourcePos); source = source.substring(0, sourcePos) + source.substring(sourcePos+1); int reformattedPos = reformatted.indexOf('^'); assertNotNull(reformattedPos); reformatted = reformatted.substring(0, reformattedPos) + reformatted.substring(reformattedPos+1); JEditorPane ta = getPane(source); Caret caret = ta.getCaret(); caret.setDot(sourcePos); BaseDocument doc = (BaseDocument) ta.getDocument(); if (formatter != null) { configureIndenters(doc, formatter, true); } setupDocumentIndentation(doc, null); runKitAction(ta, BaseKit.removePreviousWordAction, "\n"); String formatted = doc.getText(0, doc.getLength()); assertEquals(reformatted, formatted); if (reformattedPos != -1) { assertEquals(reformattedPos, caret.getDot()); } } protected void assertLogicalRange(String sourceText, boolean up, String expected) throws Exception { String BEGIN = "%<%"; // NOI18N String END = "%>%"; // NOI18N final int sourceStartPos = sourceText.indexOf(BEGIN); if (sourceStartPos != -1) { sourceText = sourceText.substring(0, sourceStartPos) + sourceText.substring(sourceStartPos+BEGIN.length()); } final int caretPos = sourceText.indexOf('^'); sourceText = sourceText.substring(0, caretPos) + sourceText.substring(caretPos+1); final int sourceEndPos = sourceText.indexOf(END); if (sourceEndPos != -1) { sourceText = sourceText.substring(0, sourceEndPos) + sourceText.substring(sourceEndPos+END.length()); } final int expectedStartPos = expected.indexOf(BEGIN); if (expectedStartPos != -1) { expected = expected.substring(0, expectedStartPos) + expected.substring(expectedStartPos+BEGIN.length()); } final int expectedCaretPos = expected.indexOf('^'); expected = expected.substring(0, expectedCaretPos) + expected.substring(expectedCaretPos+1); final int expectedEndPos = expected.indexOf(END); if (expectedEndPos != -1) { expected = expected.substring(0, expectedEndPos) + expected.substring(expectedEndPos+END.length()); } assertEquals("Only range markers should differ", sourceText, expected); Document doc = getDocument(sourceText); Source testSource = Source.create(doc); final String finalSourceText = sourceText; final boolean finalUp = up; final String finalExpected = expected; enforceCaretOffset(testSource, caretPos); ParserManager.parse(Collections.singleton(testSource), new UserTask() { public @Override void run(ResultIterator resultIterator) throws Exception { Parser.Result r = resultIterator.getParserResult(); assertTrue("Expecting ParserResult, but got " + r, r instanceof ParserResult); ParserResult pr = (ParserResult) r; KeystrokeHandler completer = getKeystrokeHandler(); assertNotNull("getKeystrokeHandler() must be implemented!", completer); List<OffsetRange> ranges = completer.findLogicalRanges(pr, caretPos); OffsetRange expectedRange; if (expectedStartPos != -1) { expectedRange = new OffsetRange(expectedStartPos, expectedEndPos); } else { expectedRange = new OffsetRange(expectedCaretPos, expectedCaretPos); } if (sourceStartPos != -1) { assertTrue(sourceEndPos != -1); OffsetRange selected = new OffsetRange(sourceStartPos, sourceEndPos); for (int i = 0; i < ranges.size(); i++) { if (ranges.get(i).equals(selected)) { if (finalUp) { assertTrue(i < ranges.size()-1); OffsetRange was = ranges.get(i+1); assertEquals("Wrong selection: expected \"" + finalExpected.substring(expectedRange.getStart(),expectedRange.getEnd()) + "\" and was \"" + finalSourceText.substring(was.getStart(), was.getEnd()) + "\"", expectedRange, was); return; } else { if (i == 0) { assertEquals(caretPos, expectedCaretPos); return; } OffsetRange was = ranges.get(i-1); assertEquals("Wrong selection: expected \"" + finalExpected.substring(expectedRange.getStart(),expectedRange.getEnd()) + "\" and was \"" + finalSourceText.substring(was.getStart(), was.getEnd()) + "\"", expectedRange, was); return; } } } fail("Selection range " + selected + " is not in the range; ranges=" + ranges); } else { assertTrue(ranges.size() > 0); OffsetRange was = ranges.get(0); assertEquals("Wrong selection: expected \"" + finalExpected.substring(expectedRange.getStart(),expectedRange.getEnd()) + "\" and was \"" + finalSourceText.substring(was.getStart(), was.getEnd()) + "\"", expectedRange, was); } } }); } //////////////////////////////////////////////////////////////////////////// // Mark Occurrences Tests //////////////////////////////////////////////////////////////////////////// protected OccurrencesFinder getOccurrencesFinder() { OccurrencesFinder handler = getPreferredLanguage().getOccurrencesFinder(); assertNotNull("You must override getOccurrencesFinder, either from your GsfLanguage or your test class", handler); return handler; } /** Test the occurrences to make sure they equal the golden file. * If the symmetric parameter is set, this test will also ensure that asking for * occurrences on ANY of the matches produced by the original caret position will * produce the exact same map. This is obviously not appropriate for things like * occurrences on the exit points. */ protected void checkOccurrences(String relFilePath, String caretLine, final boolean symmetric) throws Exception { Source testSource = getTestSource(getTestFile(relFilePath)); Document doc = testSource.getDocument(true); final int caretOffset = getCaretOffset(doc.getText(0, doc.getLength()), caretLine); final OccurrencesFinder finder = getOccurrencesFinder(); assertNotNull("getOccurrencesFinder must be implemented", finder); finder.setCaretPosition(caretOffset); ParserManager.parse(Collections.singleton(testSource), new UserTask() { public @Override void run(ResultIterator resultIterator) throws Exception { Parser.Result r = resultIterator.getParserResult(caretOffset); if (r instanceof ParserResult) { finder.run((ParserResult) r, null); Map<OffsetRange, ColoringAttributes> occurrences = finder.getOccurrences(); if (occurrences == null) { occurrences = Collections.emptyMap(); } String annotatedSource = annotateFinderResult(resultIterator.getSnapshot(), occurrences, caretOffset); assertDescriptionMatches(resultIterator.getSnapshot().getSource().getFileObject(), annotatedSource, true, ".occurrences"); if (symmetric) { // Extra check: Ensure that occurrences are symmetric: Placing the caret on ANY of the occurrences // should produce the same set!! for (OffsetRange range : occurrences.keySet()) { int midPoint = range.getStart() + range.getLength() / 2; finder.setCaretPosition(midPoint); finder.run((ParserResult) r, null); Map<OffsetRange, ColoringAttributes> alternates = finder.getOccurrences(); assertEquals("Marks differ between caret positions - failed at " + midPoint, occurrences, alternates); } } } } }); } private String annotateFinderResult(Snapshot snapshot, Map<OffsetRange, ColoringAttributes> highlights, int caretOffset) throws Exception { Set<OffsetRange> ranges = highlights.keySet(); StringBuilder sb = new StringBuilder(); CharSequence text = snapshot.getText(); Map<Integer, OffsetRange> starts = new HashMap<Integer, OffsetRange>(100); Map<Integer, OffsetRange> ends = new HashMap<Integer, OffsetRange>(100); for (OffsetRange range : ranges) { starts.put(range.getStart(), range); ends.put(range.getEnd(), range); } int index = 0; int length = text.length(); while (index < length) { int lineStart = findRowStart(text, index); int lineEnd = findRowEnd(text, index); OffsetRange lineRange = new OffsetRange(lineStart, lineEnd); boolean skipLine = true; for (OffsetRange range : ranges) { if (lineRange.containsInclusive(range.getStart()) || lineRange.containsInclusive(range.getEnd())) { skipLine = false; } } if (!skipLine) { for (int i = lineStart; i <= lineEnd; i++) { if (i == caretOffset) { sb.append("^"); } if (starts.containsKey(i)) { sb.append("|>"); OffsetRange range = starts.get(i); ColoringAttributes ca = highlights.get(range); if (ca != null) { sb.append(ca.name()); sb.append(':'); } } if (ends.containsKey(i)) { sb.append("<|"); } sb.append(text.charAt(i)); } } index = lineEnd + 1; } return sb.toString(); } private static int findRowStart(CharSequence text, int startOffset) { for(int i = startOffset - 1; i >= 0; i--) { if (text.charAt(i) == '\n') { return i + 1; } } return 0; } private static int findRowEnd(CharSequence text, int startOffset) { for(int i = startOffset; i < text.length(); i++) { if (text.charAt(i) == '\n') { return i; } } return text.length(); } //////////////////////////////////////////////////////////////////////////// // Semantic Highlighting Tests //////////////////////////////////////////////////////////////////////////// protected SemanticAnalyzer getSemanticAnalyzer() { SemanticAnalyzer handler = getPreferredLanguage().getSemanticAnalyzer(); assertNotNull("You must override getSemanticAnalyzer, either from your GsfLanguage or your test class", handler); return handler; } protected void checkSemantic(final String relFilePath, final String caretLine) throws Exception { Source testSource = getTestSource(getTestFile(relFilePath)); if (caretLine != null) { int caretOffset = getCaretOffset(testSource.createSnapshot().getText().toString(), caretLine); enforceCaretOffset(testSource, caretOffset); } ParserManager.parse(Collections.singleton(testSource), new UserTask() { public @Override void run(ResultIterator resultIterator) throws Exception { Parser.Result r = resultIterator.getParserResult(); assertTrue(r instanceof ParserResult); ParserResult pr = (ParserResult) r; SemanticAnalyzer analyzer = getSemanticAnalyzer(); assertNotNull("getSemanticAnalyzer must be implemented", analyzer); analyzer.run(pr, null); Map<OffsetRange, Set<ColoringAttributes>> highlights = analyzer.getHighlights(); if (highlights == null) { highlights = Collections.emptyMap(); } Document doc = GsfUtilities.getDocument(pr.getSnapshot().getSource().getFileObject(), true); checkNoOverlaps(highlights.keySet(), doc); String annotatedSource = annotateSemanticResults(doc, highlights); assertDescriptionMatches(relFilePath, annotatedSource, false, ".semantic"); } }); } private void checkNoOverlaps(Set<OffsetRange> ranges, Document doc) throws BadLocationException { // Make sure there are no overlapping ranges List<OffsetRange> sortedRanges = new ArrayList<OffsetRange>(ranges); Collections.sort(sortedRanges); OffsetRange prevRange = OffsetRange.NONE; for (OffsetRange range : sortedRanges) { if (range.getStart() < prevRange.getEnd() && range.getEnd() > prevRange.getEnd()) { fail("OffsetRanges should be non-overlapping! " + prevRange + "(" + doc.getText(prevRange.getStart(), prevRange.getLength()) + ") and " + range + "(" + doc.getText(range.getStart(), range.getLength()) + ")"); } prevRange = range; } } private String annotateSemanticResults(Document doc, Map<OffsetRange, Set<ColoringAttributes>> highlights) throws Exception { StringBuilder sb = new StringBuilder(); String text = doc.getText(0, doc.getLength()); Map<Integer, OffsetRange> starts = new HashMap<Integer, OffsetRange>(100); Map<Integer, OffsetRange> ends = new HashMap<Integer, OffsetRange>(100); for (OffsetRange range : highlights.keySet()) { starts.put(range.getStart(), range); ends.put(range.getEnd(), range); } for (int i = 0; i < text.length(); i++) { if (starts.containsKey(i)) { sb.append("|>"); OffsetRange range = starts.get(i); Set<ColoringAttributes> cas = highlights.get(range); if (cas != null) { // Sort to ensure stable unit test golden files List<String> attrs = new ArrayList<String>(cas.size()); for (ColoringAttributes c : cas) { attrs.add(c.name()); } Collections.sort(attrs); boolean first = true; for (String name : attrs) { if (first) { first = false; } else { sb.append(","); } sb.append(name); } sb.append(':'); } } if (ends.containsKey(i)) { sb.append("<|"); } sb.append(text.charAt(i)); } return sb.toString(); } protected void checkSemantic(String relFilePath) throws Exception { checkSemantic(relFilePath, null); } //////////////////////////////////////////////////////////////////////////// // Rename Handling Tests //////////////////////////////////////////////////////////////////////////// protected InstantRenamer getRenameHandler() { InstantRenamer handler = getPreferredLanguage().getInstantRenamer(); assertNotNull("You must override getRenameHandler, either from your GsfLanguage's getInstantRenamer or your test class", handler); return handler; } protected void checkRenameSections(final String relFilePath, final String caretLine) throws Exception { Source testSource = getTestSource(getTestFile(relFilePath)); final int caretOffset; if (caretLine != null) { caretOffset = getCaretOffset(testSource.createSnapshot().getText().toString(), caretLine); enforceCaretOffset(testSource, caretOffset); } else { caretOffset = -1; } ParserManager.parse(Collections.singleton(testSource), new UserTask() { public @Override void run(ResultIterator resultIterator) throws Exception { Parser.Result r = resultIterator.getParserResult(); assertTrue(r instanceof ParserResult); ParserResult pr = (ParserResult) r; InstantRenamer handler = getRenameHandler(); assertNotNull("getRenameHandler must be implemented", handler); String annotatedSource; String[] desc = new String[1]; if (handler.isRenameAllowed(pr, caretOffset, desc)) { Set<OffsetRange> renameRegions = handler.getRenameRegions(pr, caretOffset); annotatedSource = annotateRenameRegions(GsfUtilities.getDocument(pr.getSnapshot().getSource().getFileObject(), true), renameRegions); } else { annotatedSource = "Refactoring not allowed here\n"; if (desc[0] != null) { annotatedSource += desc[0] + "\n"; } } assertDescriptionMatches(relFilePath, annotatedSource, true, ".rename"); } }); } private String annotateRenameRegions(Document doc, Set<OffsetRange> ranges) throws Exception { if (ranges.size() == 0) { return "Requires Interactive Refactoring\n"; } StringBuilder sb = new StringBuilder(); String text = doc.getText(0, doc.getLength()); Map<Integer, OffsetRange> starts = new HashMap<Integer, OffsetRange>(100); Map<Integer, OffsetRange> ends = new HashMap<Integer, OffsetRange>(100); for (OffsetRange range : ranges) { starts.put(range.getStart(), range); ends.put(range.getEnd(), range); } for (int i = 0; i < text.length(); i++) { if (starts.containsKey(i)) { sb.append("|>"); } if (ends.containsKey(i)) { sb.append("<|"); } sb.append(text.charAt(i)); } // Only print lines with result String[] lines = sb.toString().split("\n"); sb = new StringBuilder(); int lineno = 1; for (String line : lines) { if (line.indexOf("|>") != -1) { sb.append(Integer.toString(lineno)); sb.append(": "); sb.append(line); sb.append("\n"); } lineno++; } return sb.toString(); } //////////////////////////////////////////////////////////////////////////// // Indexing Tests //////////////////////////////////////////////////////////////////////////// public EmbeddingIndexerFactory getIndexerFactory() { EmbeddingIndexerFactory handler = getPreferredLanguage().getIndexerFactory(); assertNotNull("You must override getIndexerFactory, either from your GsfLanguage or your test class", handler); return handler; } private List<TestIndexDocumentImpl> _indexFile(String relFilePath) throws Exception { FileObject testSourceFile = getTestFile(relFilePath); Source testSource = getTestSource(testSourceFile); FileObject root = testSourceFile.getParent(); final Indexable indexable = SPIAccessor.getInstance().create(new FileObjectIndexable(root, testSourceFile)); final EmbeddingIndexerFactory factory = getIndexerFactory(); assertNotNull("getIndexer must be implemented", factory); FileObject cacheRoot = CacheFolder.getDataFolder(root.getURL()); TestIndexFactoryImpl tifi = new TestIndexFactoryImpl(); final Context context = SPIAccessor.getInstance().createContext( cacheRoot, root.getURL(), factory.getIndexerName(), factory.getIndexVersion(), tifi, false, false, false, null ); try { ParserManager.parse(Collections.singleton(testSource), new UserTask() { public @Override void run(ResultIterator resultIterator) throws Exception { Parser.Result r = resultIterator.getParserResult(); assertTrue(r instanceof ParserResult); EmbeddingIndexer indexer = factory.createIndexer(indexable, r.getSnapshot()); assertNotNull("getIndexer must be implemented", factory); SPIAccessor.getInstance().index(indexer, indexable, r, context); } }); } finally { DocumentIndex index = SPIAccessor.getInstance().getIndexFactory(context).getIndex(context.getIndexFolder()); if (index != null) { index.removeDirtyKeys(Collections.singleton(indexable.getRelativePath())); index.store(true); } } TestIndexImpl tii = ((TestIndexImpl) tifi.getIndex(context.getIndexFolder())); if (tii != null) { List<TestIndexDocumentImpl> list = tii.documents.get(indexable.getRelativePath()); if (list != null) { return list; } } return Collections.<TestIndexDocumentImpl>emptyList(); } protected void indexFile(String relFilePath) throws Exception { _indexFile(relFilePath); } protected void checkIndexer(String relFilePath) throws Exception { File jsFile = new File(getDataDir(), relFilePath); String fileUrl = jsFile.toURI().toURL().toExternalForm(); String localUrl = fileUrl; int index = localUrl.lastIndexOf('/'); if (index != -1) { localUrl = localUrl.substring(0, index); } List<TestIndexDocumentImpl> result = _indexFile(relFilePath); String annotatedSource = result == null ? "" : prettyPrint(result, localUrl); assertDescriptionMatches(relFilePath, annotatedSource, false, ".indexed"); } protected void checkIsIndexable(String relFilePath, boolean isIndexable) throws Exception { final EmbeddingIndexerFactory factory = getIndexerFactory(); assertNotNull("getIndexerFactory must be implemented", factory); final FileObject fo = getTestFile(relFilePath); assertNotNull(fo); final Boolean result [] = new Boolean [] { null }; Source testSource = getTestSource(fo); ParserManager.parse(Collections.singleton(testSource), new UserTask() { public @Override void run(ResultIterator resultIterator) throws Exception { Parser.Result r = resultIterator.getParserResult(); EmbeddingIndexer indexer = factory.createIndexer( SPIAccessor.getInstance().create(new FileObjectIndexable(fo.getParent(), fo)), r.getSnapshot()); result[0] = Boolean.valueOf(indexer != null); } }); assertNotNull(result[0]); assertEquals(isIndexable, result[0].booleanValue()); } private String sortCommaList(String s) { String[] items = s.split(","); Arrays.sort(items); StringBuilder sb = new StringBuilder(); for (String item : items) { if (sb.length() > 0) { sb.append(","); } sb.append(item); } return sb.toString(); } protected String prettyPrintValue(String key, String value) { return value; } private String prettyPrint(List<TestIndexDocumentImpl> documents, String localUrl) throws IOException { List<String> nonEmptyDocuments = new ArrayList<String>(); List<String> emptyDocuments = new ArrayList<String>(); StringBuilder sb = new StringBuilder(); for (TestIndexDocumentImpl doc : documents) { sb = new StringBuilder(); sb.append("Searchable Keys:"); sb.append("\n"); List<String> strings = new ArrayList<String>(); List<String> keys = doc.indexedKeys; List<String> values = doc.indexedValues; for (int i = 0, n = keys.size(); i < n; i++) { String key = keys.get(i); String value = values.get(i); strings.add(key + " : " + prettyPrintValue(key, value)); } Collections.sort(strings); for (String string : strings) { sb.append(" "); sb.append(string); sb.append("\n"); } sb.append("\n"); sb.append("Not Searchable Keys:"); sb.append("\n"); strings = new ArrayList<String>(); keys = doc.unindexedKeys; values = doc.unindexedValues; for (int i = 0, n = keys.size(); i < n; i++) { String key = keys.get(i); String value = prettyPrintValue(key, values.get(i)); if (value.indexOf(',') != -1) { value = sortCommaList(value); } strings.add(key + " : " + value); } Collections.sort(strings); for (String string : strings) { sb.append(" "); sb.append(string); sb.append("\n"); } String s = sb.toString(); if (doc.indexedKeys.size() == 0 && doc.unindexedKeys.size() == 0) { emptyDocuments.add(s); } else { nonEmptyDocuments.add(s); } } Collections.sort(emptyDocuments); Collections.sort(nonEmptyDocuments); sb = new StringBuilder(); int documentNumber = 0; for (String s : emptyDocuments) { sb.append("\n\nDocument "); sb.append(Integer.toString(documentNumber++)); sb.append("\n"); sb.append(s); } for (String s : nonEmptyDocuments) { sb.append("\n\nDocument "); sb.append(Integer.toString(documentNumber++)); sb.append("\n"); sb.append(s); } return sb.toString().replace(localUrl, "<TESTURL>"); } // public class IndexDocumentImpl extends IndexDocument { // public List<String> indexedKeys = new ArrayList<String>(); // public List<String> indexedValues = new ArrayList<String>(); // public List<String> unindexedKeys = new ArrayList<String>(); // public List<String> unindexedValues = new ArrayList<String>(); // // public String overrideUrl; // // private IndexDocumentImpl(String overrideUrl) { // this.overrideUrl = overrideUrl; // } // // public void addPair(String key, String value, boolean indexed) { // if (indexed) { // indexedKeys.add(key); // indexedValues.add(value); // } else { // unindexedKeys.add(key); // unindexedValues.add(value); // } // } // } // //////////////////////////////////////////////////////////////////////////// // Structure Analyzer Tests //////////////////////////////////////////////////////////////////////////// public StructureScanner getStructureScanner() { StructureScanner handler = getPreferredLanguage().getStructureScanner(); assertNotNull("You must override getStructureScanner, either from your GsfLanguage or your test class", handler); return handler; } protected void checkStructure(String relFilePath) throws Exception { final HtmlFormatter formatter = new HtmlFormatter() { private StringBuilder sb = new StringBuilder(); @Override public void reset() { sb.setLength(0); } @Override public void appendHtml(String html) { sb.append(html); } @Override public void appendText(String text, int fromInclusive, int toExclusive) { sb.append("ESCAPED{"); sb.append(text, fromInclusive, toExclusive); sb.append("}"); } @Override public void name(ElementKind kind, boolean start) { if (start) { sb.append(kind); } } @Override public void active(boolean start) { if (start) { sb.append("ACTIVE{"); } else { sb.append("}"); } } @Override public void parameters(boolean start) { if (start) { sb.append("PARAMETERS{"); } else { sb.append("}"); } } @Override public void type(boolean start) { if (start) { sb.append("TYPE{"); } else { sb.append("}"); } } @Override public void deprecated(boolean start) { if (start) { sb.append("DEPRECATED{"); } else { sb.append("}"); } } @Override public String getText() { return sb.toString(); } @Override public void emphasis(boolean start) { } }; Source testSource = getTestSource(getTestFile(relFilePath)); ParserManager.parse(Collections.singleton(testSource), new UserTask() { public @Override void run(ResultIterator resultIterator) throws Exception { StructureScanner analyzer = getStructureScanner(); assertNotNull("getStructureScanner must be implemented", analyzer); Parser.Result r = resultIterator.getParserResult(); assertTrue(r instanceof ParserResult); List<? extends StructureItem> structure = analyzer.scan((ParserResult) r); String annotatedSource = annotateStructure(structure, formatter); assertDescriptionMatches(resultIterator.getSnapshot().getSource().getFileObject(), annotatedSource, false, ".structure"); } }); } protected void checkFolds(String relFilePath) throws Exception { Source testSource = getTestSource(getTestFile(relFilePath)); ParserManager.parse(Collections.singleton(testSource), new UserTask() { public @Override void run(ResultIterator resultIterator) throws Exception { StructureScanner analyzer = getStructureScanner(); assertNotNull("getStructureScanner must be implemented", analyzer); Parser.Result r = resultIterator.getParserResult(); assertTrue(r instanceof ParserResult); Map<String,List<OffsetRange>> foldsMap = analyzer.folds((ParserResult) r); // Write folding structure String source = resultIterator.getSnapshot().getText().toString(); List<Integer> begins = new ArrayList<Integer>(); List<Integer> ends = new ArrayList<Integer>(); begins.add(0); for (int i = 0; i < source.length(); i++) { char c = source.charAt(i); if (c == '\n') { ends.add(i); if (i < source.length()) { begins.add(i+1); } } } ends.add(source.length()); assertEquals(begins.size(), ends.size()); List<Character> margin = new ArrayList<Character>(begins.size()); for (int i = 0; i < begins.size(); i++) { margin.add(' '); } List<String> typeList = new ArrayList<String>(foldsMap.keySet()); Collections.sort(typeList); for (String type : typeList) { List<OffsetRange> ranges = foldsMap.get(type); for (OffsetRange range : ranges) { int beginIndex = Collections.binarySearch(begins, range.getStart()); if (beginIndex < 0) { beginIndex = -(beginIndex+2); } int endIndex = Collections.binarySearch(ends, range.getEnd()); if (endIndex < 0) { endIndex = -(endIndex+2); } for (int i = beginIndex; i <= endIndex; i++) { char c = margin.get(i); if (i == beginIndex) { c = '+'; } else if (c != '+') { if (i == endIndex) { c = '-'; } else { c = '|'; } } margin.set(i, c); } } } StringBuilder sb = new StringBuilder(3000); for (int i = 0; i < begins.size(); i++) { sb.append(margin.get(i)); sb.append(' '); for (int j = begins.get(i), max = ends.get(i); j < max; j++) { sb.append(source.charAt(j)); } sb.append('\n'); } String annotatedSource = sb.toString(); assertDescriptionMatches(resultIterator.getSnapshot().getSource().getFileObject(), annotatedSource, false, ".folds"); } }); } private void annotateStructureItem(int indent, StringBuilder sb, List<? extends StructureItem> structure, HtmlFormatter formatter) { for (StructureItem element : structure) { for (int i = 0; i < indent; i++) { sb.append(" "); } sb.append(element.getName()); sb.append(":"); sb.append(element.getKind()); sb.append(":"); sb.append(element.getModifiers()); sb.append(":"); formatter.reset(); sb.append(element.getHtml(formatter)); sb.append(":"); sb.append("\n"); List<? extends StructureItem> children = element.getNestedItems(); if (children != null && children.size() > 0) { List<? extends StructureItem> c = new ArrayList<StructureItem>(children); // Sort children to make tests more stable Collections.sort(c, new Comparator<StructureItem>() { public int compare(StructureItem s1, StructureItem s2) { String s1Name = s1.getName(); String s2Name = s2.getName(); if (s1Name == null || s2Name == null) { if (s1Name == (Object)s2Name) { // Object Cast: avoid String==String semantic warning return 0; } else if (s1Name == null) { return -1; } else { return 1; } } else { return s1Name.compareTo(s2Name); } } }); annotateStructureItem(indent+1, sb, c, formatter); } } } private String annotateStructure(List<? extends StructureItem> structure, HtmlFormatter formatter) { StringBuilder sb = new StringBuilder(); annotateStructureItem(0, sb, structure, formatter); return sb.toString(); } //////////////////////////////////////////////////////////////////////////// // Formatting Tests //////////////////////////////////////////////////////////////////////////// protected Formatter getFormatter(IndentPrefs preferences) { Formatter formatter = getPreferredLanguage().getFormatter(); assertNotNull("You must override getFormatter, either from your GsfLanguage or your test class", formatter); return formatter; } public class IndentPrefs { private final int hanging; private final int indent; public IndentPrefs(int indent, int hanging) { super(); this.indent = indent; this.hanging = hanging; } public int getIndentation() { return indent; } public int getHangingIndentation() { return hanging; } } protected void configureIndenters(Document document, Formatter formatter, boolean indentOnly) { configureIndenters(document, formatter, indentOnly, getPreferredMimeType()); } protected void configureIndenters(Document document, Formatter formatter, boolean indentOnly, String mimeType) { // ReformatTask.Factory reformatFactory = new ReformatTask.Factory() { // public ReformatTask createTask(Context context) { // final Context ctx = context; // return new ReformatTask() { // public void reformat() throws BadLocationException { // if (formatter != null) { // formatter.reformat(ctx, compilationInfo); // } // } // // public ExtraLock reformatLock() { // return null; // } // }; // } // }; // IndentTask.Factory indentFactory = new IndentTask.Factory() { // public IndentTask createTask(Context context) { // final Context ctx = context; // return new IndentTask() { // public void reindent() throws BadLocationException { // if (formatter != null) { // formatter.reindent(ctx); // } // } // // public ExtraLock indentLock() { // return null; // } // }; // } // // }; MockServices.setServices(MockMimeLookup.class); if (indentOnly) { MockMimeLookup.setInstances(MimePath.parse(mimeType), new GsfIndentTaskFactory()); } else { MockMimeLookup.setInstances(MimePath.parse(mimeType), new GsfReformatTaskFactory(), new GsfIndentTaskFactory()); } } protected void format(Document document, Formatter formatter, int startPos, int endPos, boolean indentOnly) throws BadLocationException { //assertTrue(SwingUtilities.isEventDispatchThread()); configureIndenters(document, formatter, indentOnly); final Reformat f = Reformat.get(document); f.lock(); try { if (document instanceof BaseDocument) { ((BaseDocument) document).atomicLock(); } try { f.reformat(Math.min(document.getLength(), startPos), Math.min(document.getLength(), endPos)); } finally { if (document instanceof BaseDocument) { ((BaseDocument) document).atomicUnlock(); } } } finally { f.unlock(); } } public void format(String sourceText, String reformatted, IndentPrefs preferences) throws Exception { final Formatter formatter = getFormatter(preferences); //assertNotNull("getFormatter must be implemented", formatter); String BEGIN = "%<%"; // NOI18N int startPos = sourceText.indexOf(BEGIN); if (startPos != -1) { sourceText = sourceText.substring(0, startPos) + sourceText.substring(startPos+BEGIN.length()); } else { startPos = 0; } String END = "%>%"; // NOI18N int endPos = sourceText.indexOf(END); if (endPos != -1) { sourceText = sourceText.substring(0, endPos) + sourceText.substring(endPos+END.length()); } Document doc = getDocument(sourceText); if (endPos == -1) { endPos = doc.getLength(); } setupDocumentIndentation(doc, preferences); format(doc, formatter, startPos, endPos, false); String formatted = doc.getText(0, doc.getLength()); assertEquals(reformatted, formatted); } protected void reformatFileContents(String file, IndentPrefs preferences) throws Exception { FileObject fo = getTestFile(file); assertNotNull(fo); BaseDocument doc = getDocument(fo); assertNotNull(doc); //String before = doc.getText(0, doc.getLength()); Formatter formatter = getFormatter(preferences); //assertNotNull("getFormatter must be implemented", formatter); setupDocumentIndentation(doc, preferences); format(doc, formatter, 0, doc.getLength(), false); String after = doc.getText(0, doc.getLength()); assertDescriptionMatches(file, after, false, ".formatted"); } protected BaseKit getEditorKit(String mimeType) { org.netbeans.modules.csl.core.Language language = LanguageRegistry.getInstance().getLanguageByMimeType(mimeType); assertNotNull(language); if (!language.useCustomEditorKit()) { return new CslEditorKit(mimeType); } fail("Must override getEditorKit() for useCustomEditorKit languages"); return null; } protected void toggleComment(String text, String expected) throws Exception { JEditorPane pane = getPane(text); runKitAction(pane, "toggle-comment", ""); String toggled = pane.getText(); assertEquals(expected, toggled); } protected JEditorPane getPane(String text) throws Exception { if (!SwingUtilities.isEventDispatchThread()) { fail("You must run this test from the event dispatch thread! To do that, add @Override protected boolean runInEQ() { return true } from your testcase!"); } String BEGIN = "$start$"; // NOI18N String END = "$end$"; // NOI18N int sourceStartPos = text.indexOf(BEGIN); int caretPos = -1; int sourceEndPos = -1; if (sourceStartPos != -1) { text = text.substring(0, sourceStartPos) + text.substring(sourceStartPos+BEGIN.length()); sourceEndPos = text.indexOf(END); assertTrue(sourceEndPos != -1); text = text.substring(0, sourceEndPos) + text.substring(sourceEndPos+END.length()); } else { caretPos = text.indexOf('^'); if (caretPos != -1) { text = text.substring(0, caretPos) + text.substring(caretPos+1); } } JEditorPane pane = new JEditorPane(); pane.setContentType(getPreferredMimeType()); final NbEditorKit kit = ((NbEditorKit)getEditorKit(getPreferredMimeType())); Thread preload = new Thread(new Runnable() { @Override public void run() { // Preload actions and other stuff if (kit instanceof Callable) { try { ((Callable) kit).call(); } catch (Exception ex) { Exceptions.printStackTrace(ex); } } kit.getActions(); } }); preload.start(); preload.join(); pane.setEditorKit(kit); pane.setText(text); BaseDocument bdoc = (BaseDocument)pane.getDocument(); bdoc.putProperty(org.netbeans.api.lexer.Language.class, getPreferredLanguage().getLexerLanguage()); bdoc.putProperty("mimeType", getPreferredMimeType()); //bdoc.insertString(0, text, null); if (sourceStartPos != -1) { assertTrue(sourceEndPos != -1); pane.setSelectionStart(sourceStartPos); pane.setSelectionEnd(sourceEndPos); } else if (caretPos != -1) { pane.getCaret().setDot(caretPos); } pane.getCaret().setSelectionVisible(true); return pane; } protected void runKitAction(JEditorPane jt, String actionName, String cmd) { BaseKit kit = (BaseKit)jt.getEditorKit(); Action a = kit.getActionByName(actionName); assertNotNull(a); a.actionPerformed(new ActionEvent(jt, 0, cmd)); } protected void setupDocumentIndentation(Document doc, IndentPrefs preferences) { // Enforce indentprefs if (preferences != null) { assertEquals("Hanging indentation not yet supported; must be exposed through options", preferences.getIndentation(), preferences.getHangingIndentation()); Preferences prefs = CodeStylePreferences.get(doc).getPreferences(); prefs.putInt(SimpleValueNames.INDENT_SHIFT_WIDTH, preferences.getIndentation()); } else { int preferred = 4; if (getPreferredLanguage().hasFormatter()) { preferred = getPreferredLanguage().getFormatter().indentSize(); } Preferences prefs = CodeStylePreferences.get(doc).getPreferences(); prefs.putInt(SimpleValueNames.INDENT_SHIFT_WIDTH, preferred); } } public void insertNewline(String source, String reformatted, IndentPrefs preferences) throws Exception { int sourcePos = source.indexOf('^'); assertNotNull(sourcePos); source = source.substring(0, sourcePos) + source.substring(sourcePos+1); Formatter formatter = getFormatter(null); int reformattedPos = reformatted.indexOf('^'); assertNotNull(reformattedPos); reformatted = reformatted.substring(0, reformattedPos) + reformatted.substring(reformattedPos+1); JEditorPane ta = getPane(source); Caret caret = ta.getCaret(); caret.setDot(sourcePos); BaseDocument doc = (BaseDocument) ta.getDocument(); if (formatter != null) { configureIndenters(doc, formatter, true); } setupDocumentIndentation(doc, preferences); runKitAction(ta, DefaultEditorKit.insertBreakAction, "\n"); String formatted = doc.getText(0, doc.getLength()); assertEquals(reformatted, formatted); if (reformattedPos != -1) { assertEquals(reformattedPos, caret.getDot()); } } protected void insertBreak(String original, String expected) throws Exception { insertNewline(original, expected, null); } //////////////////////////////////////////////////////////////////////////// // Code Completion Tests //////////////////////////////////////////////////////////////////////////// protected CodeCompletionHandler getCodeCompleter() { CodeCompletionHandler handler = getPreferredLanguage().getCompletionHandler(); assertNotNull("You must override getCompletionHandler, either from your GsfLanguage or your test class", handler); return handler; } private String getSourceLine(String s, int offset) { int begin = offset; if (begin > 0) { begin = s.lastIndexOf('\n', offset-1); if (begin == -1) { begin = 0; } else if (begin < s.length()) { begin++; } } if (s.length() == 0) { return s; } // s.charAt(offset); int end = s.indexOf('\n', begin); if (end == -1) { end = s.length(); } if (offset < end) { return (s.substring(begin, offset)+"|"+s.substring(offset,end)).trim(); } else { return (s.substring(begin, end) + "|").trim(); } } protected String getSourceWindow(String s, int offset) { int prevLineBegin; int nextLineEnd; int begin = offset; if (offset > 0) { begin = s.lastIndexOf('\n', offset); if (begin == -1) { begin = 0; prevLineBegin = 0; } else if (begin > 0) { prevLineBegin = s.lastIndexOf('\n', begin-1); if (prevLineBegin == -1) { prevLineBegin = 0; } else if (prevLineBegin < s.length()) { prevLineBegin++; } } else{ prevLineBegin = 0; } } else { prevLineBegin = 0; } int end = s.indexOf('\n', offset); if (end == -1) { end = s.length(); nextLineEnd = end; } else if (end < s.length()) { nextLineEnd = s.indexOf('\n', end+1); if (nextLineEnd == -1) { s.length(); } } else { nextLineEnd = end; } return s.substring(prevLineBegin, offset)+"|"+s.substring(offset, nextLineEnd); } private String describeCompletion(String caretLine, String text, int caretOffset, boolean prefixSearch, boolean caseSensitive, QueryType type, List<CompletionProposal> proposals, boolean includeModifiers, boolean[] deprecatedHolder, final HtmlFormatter formatter) { assertTrue(deprecatedHolder != null && deprecatedHolder.length == 1); StringBuilder sb = new StringBuilder(); sb.append("Code completion result for source line:\n"); String sourceLine = getSourceLine(text, caretOffset); if (sourceLine.length() == 1) { sourceLine = getSourceWindow(text, caretOffset); } sb.append(sourceLine); sb.append("\n(QueryType=" + type + ", prefixSearch=" + prefixSearch + ", caseSensitive=" + caseSensitive + ")"); sb.append("\n"); // Sort to make test more stable Collections.sort(proposals, new Comparator<CompletionProposal>() { public int compare(CompletionProposal p1, CompletionProposal p2) { // Smart items first if (p1.isSmart() != p2.isSmart()) { return p1.isSmart() ? -1 : 1; } if (p1.getKind() != p2.getKind()) { return p1.getKind().compareTo(p2.getKind()); } formatter.reset(); String p1L = p1.getLhsHtml(formatter); formatter.reset(); String p2L = p2.getLhsHtml(formatter); if (!p1L.equals(p2L)) { return p1L.compareTo(p2L); } formatter.reset(); String p1Rhs = p1.getRhsHtml(formatter); formatter.reset(); String p2Rhs = p2.getRhsHtml(formatter); if (p1Rhs == null) { p1Rhs = ""; } if (p2Rhs == null) { p2Rhs = ""; } if (!p1Rhs.equals(p2Rhs)) { return p1Rhs.compareTo(p2Rhs); } // Yuck - tostring comparison of sets!! if (!p1.getModifiers().toString().equals(p2.getModifiers().toString())) { return p1.getModifiers().toString().compareTo(p2.getModifiers().toString()); } return 0; } }); boolean isSmart = true; for (CompletionProposal proposal : proposals) { if (isSmart && !proposal.isSmart()) { sb.append("------------------------------------\n"); isSmart = false; } deprecatedHolder[0] = false; formatter.reset(); proposal.getLhsHtml(formatter); // Side effect to deprecatedHolder used boolean strike = includeModifiers && deprecatedHolder[0]; String n = proposal.getKind().toString(); int MAX_KIND = 10; if (n.length() > MAX_KIND) { sb.append(n.substring(0, MAX_KIND)); } else { sb.append(n); for (int i = n.length(); i < MAX_KIND; i++) { sb.append(" "); } } // if (proposal.getModifiers().size() > 0) { // List<String> modifiers = new ArrayList<String>(); // for (Modifier mod : proposal.getModifiers()) { // modifiers.add(mod.name()); // } // Collections.sort(modifiers); // sb.append(modifiers); // } sb.append(" "); formatter.reset(); n = proposal.getLhsHtml(formatter); int MAX_LHS = 30; if (strike) { MAX_LHS -= 6; // Account for the --- --- strikethroughs sb.append("---"); } if (n.length() > MAX_LHS) { sb.append(n.substring(0, MAX_LHS)); } else { sb.append(n); for (int i = n.length(); i < MAX_LHS; i++) { sb.append(" "); } } if (strike) { sb.append("---"); } sb.append(" "); assertNotNull("Return Collections.emptySet() instead from getModifiers!", proposal.getModifiers()); if (proposal.getModifiers().isEmpty()) { n = ""; } else { n = proposal.getModifiers().toString(); } int MAX_MOD = 9; if (n.length() > MAX_MOD) { sb.append(n.substring(0, MAX_MOD)); } else { sb.append(n); for (int i = n.length(); i < MAX_MOD; i++) { sb.append(" "); } } sb.append(" "); formatter.reset(); sb.append(proposal.getRhsHtml(formatter)); sb.append("\n"); isSmart = proposal.isSmart(); } return sb.toString(); } private static org.netbeans.modules.csl.core.Language getCompletableLanguage(Document doc, int offset) { BaseDocument baseDoc = (BaseDocument)doc; List<org.netbeans.modules.csl.core.Language> list = LanguageRegistry.getInstance().getEmbeddedLanguages(baseDoc, offset); for (org.netbeans.modules.csl.core.Language l : list) { if (l.getCompletionProvider() != null) { return l; } } return null; } public void checkCompletion(final String file, final String caretLine, final boolean includeModifiers) throws Exception { // TODO call TestCompilationInfo.setCaretOffset! final QueryType type = QueryType.COMPLETION; final boolean caseSensitive = true; Source testSource = getTestSource(getTestFile(file)); final int caretOffset; if (caretLine != null) { caretOffset = getCaretOffset(testSource.createSnapshot().getText().toString(), caretLine); enforceCaretOffset(testSource, caretOffset); } else { caretOffset = -1; } ParserManager.parse(Collections.singleton(testSource), new UserTask() { public @Override void run(ResultIterator resultIterator) throws Exception { Parser.Result r = caretOffset == -1 ? resultIterator.getParserResult() : resultIterator.getParserResult(caretOffset); assertTrue(r instanceof ParserResult); ParserResult pr = (ParserResult) r; CodeCompletionHandler cc = getCodeCompleter(); assertNotNull("getCodeCompleter must be implemented", cc); Document doc = GsfUtilities.getDocument(pr.getSnapshot().getSource().getFileObject(), true); boolean upToOffset = type == QueryType.COMPLETION; String prefix = cc.getPrefix(pr, caretOffset, upToOffset); if (prefix == null) { if (prefix == null) { int[] blk = org.netbeans.editor.Utilities.getIdentifierBlock((BaseDocument) doc, caretOffset); if (blk != null) { int start = blk[0]; if (start < caretOffset ) { if (upToOffset) { prefix = doc.getText(start, caretOffset - start); } else { prefix = doc.getText(start, blk[1] - start); } } } } } final int finalCaretOffset = caretOffset; final String finalPrefix = prefix; final ParserResult finalParserResult = pr; CodeCompletionContext context = new CodeCompletionContext() { @Override public int getCaretOffset() { return finalCaretOffset; } @Override public ParserResult getParserResult() { return finalParserResult; } @Override public String getPrefix() { return finalPrefix; } @Override public boolean isPrefixMatch() { return true; } @Override public QueryType getQueryType() { return type; } @Override public boolean isCaseSensitive() { return caseSensitive; } }; CodeCompletionResult completionResult = cc.complete(context); List<CompletionProposal> proposals = completionResult.getItems(); final boolean deprecatedHolder[] = new boolean[1]; final HtmlFormatter formatter = new HtmlFormatter() { private StringBuilder sb = new StringBuilder(); @Override public void reset() { sb.setLength(0); } @Override public void appendHtml(String html) { sb.append(html); } @Override public void appendText(String text, int fromInclusive, int toExclusive) { sb.append(text, fromInclusive, toExclusive); } @Override public void emphasis(boolean start) { } @Override public void active(boolean start) { } @Override public void name(ElementKind kind, boolean start) { } @Override public void parameters(boolean start) { } @Override public void type(boolean start) { } @Override public void deprecated(boolean start) { deprecatedHolder[0] = true; } @Override public String getText() { return sb.toString(); } }; String described = describeCompletion(caretLine, pr.getSnapshot().getSource().createSnapshot().getText().toString(), caretOffset, true, caseSensitive, type, proposals, includeModifiers, deprecatedHolder, formatter); assertDescriptionMatches(file, described, true, ".completion"); } }); } public void checkCompletionDocumentation(final String file, final String caretLine, final boolean includeModifiers, final String itemPrefix) throws Exception { // TODO call TestCompilationInfo.setCaretOffset! final QueryType type = QueryType.COMPLETION; final boolean caseSensitive = true; Source testSource = getTestSource(getTestFile(file)); final int caretOffset; if (caretLine != null) { caretOffset = getCaretOffset(testSource.createSnapshot().getText().toString(), caretLine); enforceCaretOffset(testSource, caretOffset); } else { caretOffset = -1; } ParserManager.parse(Collections.singleton(testSource), new UserTask() { public @Override void run(ResultIterator resultIterator) throws Exception { Parser.Result r = resultIterator.getParserResult(); assertTrue(r instanceof ParserResult); ParserResult pr = (ParserResult) r; CodeCompletionHandler cc = getCodeCompleter(); assertNotNull("getCodeCompleter must be implemented", cc); Document doc = GsfUtilities.getDocument(resultIterator.getSnapshot().getSource().getFileObject(), true); boolean upToOffset = type == QueryType.COMPLETION; String prefix = cc.getPrefix(pr, caretOffset, upToOffset); if (prefix == null) { if (prefix == null) { int[] blk = org.netbeans.editor.Utilities.getIdentifierBlock((BaseDocument) doc, caretOffset); if (blk != null) { int start = blk[0]; if (start < caretOffset ) { if (upToOffset) { prefix = doc.getText(start, caretOffset - start); } else { prefix = doc.getText(start, blk[1] - start); } } } } } // resultIterator.getSource().testUpdateIndex(); final int finalCaretOffset = caretOffset; final String finalPrefix = prefix; final ParserResult finalParserResult = pr; CodeCompletionContext context = new CodeCompletionContext() { @Override public int getCaretOffset() { return finalCaretOffset; } @Override public ParserResult getParserResult() { return finalParserResult; } @Override public String getPrefix() { return finalPrefix; } @Override public boolean isPrefixMatch() { return false; } @Override public QueryType getQueryType() { return type; } @Override public boolean isCaseSensitive() { return caseSensitive; } }; CodeCompletionResult completionResult = cc.complete(context); List<CompletionProposal> proposals = completionResult.getItems(); CompletionProposal match = null; for (CompletionProposal proposal : proposals) { if (proposal.getName().startsWith(itemPrefix)) { match = proposal; break; } } assertNotNull(match); assertNotNull(match.getElement()); // Get documentation String documentation = cc.document(pr, match.getElement()); final boolean deprecatedHolder[] = new boolean[1]; final HtmlFormatter formatter = new HtmlFormatter() { private StringBuilder sb = new StringBuilder(); @Override public void reset() { sb.setLength(0); } @Override public void appendHtml(String html) { sb.append(html); } @Override public void appendText(String text, int fromInclusive, int toExclusive) { sb.append(text, fromInclusive, toExclusive); } @Override public void emphasis(boolean start) { } @Override public void active(boolean start) { } @Override public void name(ElementKind kind, boolean start) { } @Override public void parameters(boolean start) { } @Override public void type(boolean start) { } @Override public void deprecated(boolean start) { deprecatedHolder[0] = true; } @Override public String getText() { return sb.toString(); } }; String described = describeCompletionDoc(pr.getSnapshot().getText().toString(), caretOffset, false, caseSensitive, type, match, documentation, includeModifiers, deprecatedHolder, formatter); assertDescriptionMatches(file, described, true, ".html"); } }); } private String describeCompletionDoc(String text, int caretOffset, boolean prefixSearch, boolean caseSensitive, QueryType type, CompletionProposal proposal, String documentation, boolean includeModifiers, boolean[] deprecatedHolder, final HtmlFormatter formatter) { assertTrue(deprecatedHolder != null && deprecatedHolder.length == 1); StringBuilder sb = new StringBuilder(); sb.append("<html>"); sb.append("<body>\n"); sb.append("<pre>"); sb.append("Code completion result for source line:\n"); String sourceLine = getSourceLine(text, caretOffset); if (sourceLine.length() == 1) { sourceLine = getSourceWindow(text, caretOffset); } sb.append(sourceLine); sb.append("\n(QueryType=" + type + ", prefixSearch=" + prefixSearch + ", caseSensitive=" + caseSensitive + ")"); sb.append("\n"); boolean isSmart = true; if (isSmart && !proposal.isSmart()) { sb.append("------------------------------------\n"); isSmart = false; } deprecatedHolder[0] = false; formatter.reset(); proposal.getLhsHtml(formatter); // Side effect to deprecatedHolder used boolean strike = includeModifiers && deprecatedHolder[0]; String n = proposal.getKind().toString(); int MAX_KIND = 10; if (n.length() > MAX_KIND) { sb.append(n.substring(0, MAX_KIND)); } else { sb.append(n); for (int i = n.length(); i < MAX_KIND; i++) { sb.append(" "); } } // if (proposal.getModifiers().size() > 0) { // List<String> modifiers = new ArrayList<String>(); // for (Modifier mod : proposal.getModifiers()) { // modifiers.add(mod.name()); // } // Collections.sort(modifiers); // sb.append(modifiers); // } sb.append(" "); formatter.reset(); n = proposal.getLhsHtml(formatter); int MAX_LHS = 30; if (strike) { MAX_LHS -= 6; // Account for the --- --- strikethroughs sb.append("---"); } if (n.length() > MAX_LHS) { sb.append(n.substring(0, MAX_LHS)); } else { sb.append(n); for (int i = n.length(); i < MAX_LHS; i++) { sb.append(" "); } } if (strike) { sb.append("---"); } sb.append(" "); assertNotNull("Return Collections.emptySet() instead from getModifiers!", proposal.getModifiers()); if (proposal.getModifiers().isEmpty()) { n = ""; } else { n = proposal.getModifiers().toString(); } int MAX_MOD = 9; if (n.length() > MAX_MOD) { sb.append(n.substring(0, MAX_MOD)); } else { sb.append(n); for (int i = n.length(); i < MAX_MOD; i++) { sb.append(" "); } } sb.append(" "); formatter.reset(); sb.append(proposal.getRhsHtml(formatter)); sb.append("\n"); isSmart = proposal.isSmart(); sb.append("</pre>"); sb.append("<h2>Documentation:</h2>"); sb.append(alterDocumentationForTest(documentation)); sb.append("</body>"); sb.append("</html>"); return sb.toString(); } /** * Sometimes the documentation can contain absolute path. When you overwrite * this method, you can exclude such thinks from it. * @param documentation * @return changed documentation */ protected String alterDocumentationForTest(String documentation) { return documentation; } protected void assertAutoQuery(QueryType queryType, String source, String typedText) { CodeCompletionHandler completer = getCodeCompleter(); int caretPos = source.indexOf('^'); source = source.substring(0, caretPos) + source.substring(caretPos+1); BaseDocument doc = getDocument(source); JTextArea ta = new JTextArea(doc); Caret caret = ta.getCaret(); caret.setDot(caretPos); QueryType qt = completer.getAutoQuery(ta, typedText); assertEquals(queryType, qt); } protected void checkCall(ParserResult info, int caretOffset, String param, boolean expectSuccess) { } public void checkComputeMethodCall(String file, final String caretLine, final String param, final boolean expectSuccess) throws Exception { final QueryType type = QueryType.COMPLETION; //boolean caseSensitive = true; Source testSource = getTestSource(getTestFile(file)); final int caretOffset; if (caretLine != null) { caretOffset = getCaretOffset(testSource.createSnapshot().getText().toString(), caretLine); enforceCaretOffset(testSource, caretOffset); } else { caretOffset = -1; } ParserManager.parse(Collections.singleton(testSource), new UserTask() { public @Override void run(ResultIterator resultIterator) throws Exception { Parser.Result r = resultIterator.getParserResult(); assertTrue(r instanceof ParserResult); ParserResult pr = (ParserResult) r; CodeCompletionHandler cc = getCodeCompleter(); assertNotNull("getCodeCompleter must be implemented", cc); Document doc = GsfUtilities.getDocument(pr.getSnapshot().getSource().getFileObject(), true); boolean upToOffset = type == QueryType.COMPLETION; String prefix = cc.getPrefix(pr, caretOffset, upToOffset); if (prefix == null) { if (prefix == null) { int[] blk = org.netbeans.editor.Utilities.getIdentifierBlock((BaseDocument)doc, caretOffset); if (blk != null) { int start = blk[0]; if (start < caretOffset ) { if (upToOffset) { prefix = doc.getText(start, caretOffset - start); } else { prefix = doc.getText(start, blk[1] - start); } } } } } checkCall(pr, caretOffset, param, expectSuccess); } }); } public void checkPrefix(final String relFilePath) throws Exception { Source testSource = getTestSource(getTestFile(relFilePath)); ParserManager.parse(Collections.singleton(testSource), new UserTask() { public @Override void run(ResultIterator resultIterator) throws Exception { Parser.Result r = resultIterator.getParserResult(); assertTrue(r instanceof ParserResult); ParserResult pr = (ParserResult) r; CodeCompletionHandler completer = getCodeCompleter(); assertNotNull("getSemanticAnalyzer must be implemented", completer); BaseDocument doc = GsfUtilities.getDocument(resultIterator.getSnapshot().getSource().getFileObject(), true); StringBuilder sb = new StringBuilder(); int index = 0; while (index < doc.getLength()) { int lineStart = index; int lineEnd = Utilities.getRowEnd(doc, index); if (lineEnd == -1) { break; } if (Utilities.getRowFirstNonWhite(doc, index) != -1) { String line = doc.getText(lineStart, lineEnd-lineStart); for (int i = lineStart; i <= lineEnd; i++) { String prefix = completer.getPrefix(pr, i, true); // line.charAt(i) if (prefix == null) { continue; } String wholePrefix = completer.getPrefix(pr, i, false); assertNotNull(wholePrefix); sb.append(line +"\n"); //sb.append("Offset "); //sb.append(Integer.toString(i)); //sb.append(" : \""); for (int j = lineStart; j < i; j++) { sb.append(' '); } sb.append('^'); sb.append(prefix.length() > 0 ? prefix : "\"\""); sb.append(","); sb.append(wholePrefix.length() > 0 ? wholePrefix : "\"\""); sb.append("\n"); } } index = lineEnd+1; } String annotatedSource = sb.toString(); assertDescriptionMatches(relFilePath, annotatedSource, false, ".prefixes"); } }); } //////////////////////////////////////////////////////////////////////////// // Ast Offsets Test //////////////////////////////////////////////////////////////////////////// protected String describeNode(ParserResult info, Object node, boolean includePath) throws Exception { // Override in your test return null; } protected void initializeNodes(ParserResult result, List<Object> validNodes, Map<Object,OffsetRange> positions, List<Object> invalidNodes) throws Exception { // Override in your test } protected void checkOffsets(String relFilePath) throws Exception { checkOffsets(relFilePath, null); } protected void checkOffsets(final String relFilePath, final String caretLine) throws Exception { Source testSource = getTestSource(getTestFile(relFilePath)); if (caretLine != null) { int caretOffset = getCaretOffset(testSource.createSnapshot().getText().toString(), caretLine); enforceCaretOffset(testSource, caretOffset); } ParserManager.parse(Collections.singleton(testSource), new UserTask() { public @Override void run(ResultIterator resultIterator) throws Exception { Parser.Result r = resultIterator.getParserResult(); assertTrue(r instanceof ParserResult); ParserResult pr = (ParserResult) r; List<Object> validNodes = new ArrayList<Object>(); List<Object> invalidNodes = new ArrayList<Object>(); Map<Object,OffsetRange> positions = new HashMap<Object,OffsetRange>(); initializeNodes(pr, validNodes, positions, invalidNodes); String annotatedSource = annotateOffsets(validNodes, positions, invalidNodes, pr); assertDescriptionMatches(relFilePath, annotatedSource, false, ".offsets"); } }); } /** Pass the nodes in an in-order traversal order such that it can properly nest * items when they have identical starting or ending endpoints */ private String annotateOffsets(List<Object> validNodes, Map<Object,OffsetRange> positions, List<Object> invalidNodes, ParserResult info) throws Exception { // StringBuilder sb = new StringBuilder(); BaseDocument doc = GsfUtilities.getDocument(info.getSnapshot().getSource().getFileObject(), true); String text = doc.getText(0, doc.getLength()); final Map<Object,Integer> traversalNumber = new HashMap<Object,Integer>(); int id = 0; for (Object node : validNodes) { traversalNumber.put(node, id++); } Comparator<Object> FORWARDS_COMPARATOR = new Comparator<Object>() { public int compare(Object o1, Object o2) { assertTrue(traversalNumber.containsKey(o1)); assertTrue(traversalNumber.containsKey(o2)); return traversalNumber.get(o1) - traversalNumber.get(o2); } }; Comparator<Object> BACKWARDS_COMPARATOR = new Comparator<Object>() { public int compare(Object o1, Object o2) { assertTrue(traversalNumber.containsKey(o1)); assertTrue(traversalNumber.containsKey(o2)); return traversalNumber.get(o2) - traversalNumber.get(o1); } }; Map<Integer,List<Object>> starts = new HashMap<Integer,List<Object>>(100); Map<Integer,List<Object>> ends = new HashMap<Integer,List<Object>>(100); for (Object node : validNodes) { OffsetRange range = positions.get(node); List<Object> list = starts.get(range.getStart()); if (list == null) { list = new ArrayList<Object>(); starts.put(range.getStart(), list); } list.add(node); list = ends.get(range.getEnd()); if (list == null) { list = new ArrayList<Object>(); ends.put(range.getEnd(), list); } list.add(node); } // Sort nodes for (List<Object> list : starts.values()) { Collections.sort(list, FORWARDS_COMPARATOR); } for (List<Object> list : ends.values()) { Collections.sort(list, BACKWARDS_COMPARATOR); } // Include 0-0 nodes first List<String> missing = new ArrayList<String>(); for (Object n : invalidNodes) { String desc = describeNode(info, n, true); assertNotNull("You must implement describeNode()", desc); missing.add("Missing position for node " + desc); } Collections.sort(missing); for (String s : missing) { sb.append(s); sb.append("\n"); } sb.append("\n"); for (int i = 0; i < text.length(); i++) { List<Object> deferred = null; if (ends.containsKey(i)) { List<Object> ns = ends.get(i); List<Object> sts = starts.get(i); for (Object n : ns) { if (sts != null && sts.contains(n)) { if (deferred == null) { deferred = new ArrayList<Object>(); } deferred.add(n); } else { sb.append("</"); String desc = describeNode(info, n, false); assertNotNull(desc); sb.append(desc); sb.append(">"); } } } if (starts.containsKey(i)) { List<Object> ns = starts.get(i); List<Object> ets = ends.get(i); for (Object n : ns) { if (ets != null && ets.contains(n)) { if (deferred == null) { deferred = new ArrayList<Object>(); } else if (deferred.get(deferred.size()-1) != n) { deferred.add(n); } } else { sb.append("<"); String desc = describeNode(info, n, false); assertNotNull(desc); sb.append(desc); sb.append(">"); } } } if (deferred != null) { for (Object n : deferred) { sb.append("<"); String desc = describeNode(info, n, false); assertNotNull(desc); sb.append(desc); sb.append("/>"); } } char c = text.charAt(i); switch (c) { case '&': sb.append("&"); break; case '<': sb.append("<"); break; case '>': sb.append(">"); break; default: sb.append(c); } } return sb.toString(); } //////////////////////////////////////////////////////////////////////////// // Incremental Parsing and Offsets //////////////////////////////////////////////////////////////////////////// protected void verifyIncremental(ParserResult result, EditHistory history, ParserResult oldResult) { // Your module should check that the parser results are really okay and incremental here } public class TestDocumentEvent implements DocumentEvent { private int offset; private int length; public TestDocumentEvent(int offset, int length) { this.offset = offset; this.length = length; } public int getOffset() { return offset; } public int getLength() { return length; } public Document getDocument() { return null; } public EventType getType() { return null; } public ElementChange getChange(Element elem) { return null; } } public static final String INSERT = "insert:"; // NOI18N public static final String REMOVE = "remove:"; // NOI18N // public class IncrementalParse { // public ParserResult oldParserResult; // public GsfTestCompilationInfo info; // public ParserResult newParserResult; // public ParserResult fullParseResult; // public EditHistory history; // public String initialSource; // public String modifiedSource; // // public IncrementalParse(ParserResult oldParserResult, GsfTestCompilationInfo info, ParserResult newParserResult, // EditHistory history, // String initialSource, String modifiedSource, // ParserResult fullParseResult // ) { // this.oldParserResult = oldParserResult; // this.info = info; // this.newParserResult = newParserResult; // this.history = history; // this.initialSource = initialSource; // this.modifiedSource = modifiedSource; // this.fullParseResult = fullParseResult; // } // } protected final Pair<EditHistory,String> getEditHistory(String initialText, String... edits) { return getEditHistory(initialText, new EditHistory(), edits); } protected final Pair<EditHistory,String> getEditHistory(String initialText, EditHistory history, String... edits) { assertNotNull("Must provide a list of edits", edits); assertTrue("Should be an even number of edit events: pairs of caret, insert/remove", edits.length % 2 == 0); String modifiedText = initialText; for (int i = 0, n = edits.length; i < n; i += 2) { String caretLine = edits[i]; String event = edits[i+1]; int caretOffset = getCaretOffset(modifiedText, caretLine); assertTrue(event + " must start with " + INSERT + " or " + REMOVE, event.startsWith(INSERT) || event.startsWith(REMOVE)); if (event.startsWith(INSERT)) { event = event.substring(INSERT.length()); history.insertUpdate(new TestDocumentEvent(caretOffset, event.length())); modifiedText = modifiedText.substring(0, caretOffset) + event + modifiedText.substring(caretOffset); } else { assertTrue(event.startsWith(REMOVE)); event = event.substring(REMOVE.length()); assertTrue(modifiedText.regionMatches(caretOffset, event, 0, event.length())); history.removeUpdate(new TestDocumentEvent(caretOffset, event.length())); modifiedText = modifiedText.substring(0, caretOffset) + modifiedText.substring(caretOffset+event.length()); } } return new Pair<EditHistory,String>(history, modifiedText); } protected final Pair<EditHistory,String> getEditHistory(BaseDocument doc, final EditHistory history, String... edits) throws BadLocationException { assertNotNull("Must provide a list of edits", edits); assertTrue("Should be an even number of edit events: pairs of caret, insert/remove", edits.length % 2 == 0); String initialText = doc.getText(0, doc.getLength()); doc.addDocumentListener(new DocumentListener() { public void insertUpdate(DocumentEvent e) { history.insertUpdate(e); } public void removeUpdate(DocumentEvent e) { history.removeUpdate(e); } public void changedUpdate(DocumentEvent e) { } }); TokenHierarchy th = TokenHierarchy.get((Document) doc); th.addTokenHierarchyListener(new TokenHierarchyListener() { public void tokenHierarchyChanged(TokenHierarchyEvent e) { // I'm getting empty token change events from tests... // Figure out why this happens // assert e.type() != TokenHierarchyEventType.MODIFICATION || e.tokenChange().addedTokenCount() > 0 || e.tokenChange().removedTokenCount() > 0; history.tokenHierarchyChanged(e); } }); // Attempt to activate them token hierarchy, one of my attempts to get TokenHierarchyEvents fired //// doc.writeLock(); //try { // MutableTextInput input = (MutableTextInput)doc.getProperty(MutableTextInput.class); // assertNotNull(input); // input.tokenHierarchyControl().setActive(true); //} finally { // // doc.writeUnlock(); //} String modifiedText = initialText; for (int i = 0, n = edits.length; i < n; i += 2) { String caretLine = edits[i]; String event = edits[i+1]; int caretOffset = getCaretOffset(modifiedText, caretLine); assertTrue(event + " must start with " + INSERT + " or " + REMOVE, event.startsWith(INSERT) || event.startsWith(REMOVE)); if (event.startsWith(INSERT)) { event = event.substring(INSERT.length()); //assertTrue(th.isActive()); doc.insertString(caretOffset, event, null); modifiedText = modifiedText.substring(0, caretOffset) + event + modifiedText.substring(caretOffset); } else { assertTrue(event.startsWith(REMOVE)); event = event.substring(REMOVE.length()); assertTrue(modifiedText.regionMatches(caretOffset, event, 0, event.length())); doc.remove(caretOffset, event.length()); modifiedText = modifiedText.substring(0, caretOffset) + modifiedText.substring(caretOffset+event.length()); } } assertEquals(modifiedText, doc.getText(0, doc.getLength())); // Make sure the hierarchy is activated - this happens when we obtain a token sequence TokenSequence ts = th.tokenSequence(); ts.moveStart(); for (int i = 0; i < 10; i++) { if (!ts.moveNext()) { break; } } return new Pair<EditHistory,String>(history, modifiedText); } // /** // * Produce an incremental parser result for the given test file with the given // * series of edits. An edit is a pair of caret position string (with ^ representing // * the caret) and a corresponding insert or delete (with insert:string or remove:string) // * as the value. // */ // protected final IncrementalParse getIncrementalResult(String relFilePath, double speedupExpectation, String... edits) throws Exception { // GsfTestCompilationInfo info = getInfo(relFilePath); // // // Obtain the initial parse result // ParserResult initialResult = info.getEmbeddedResult(getPreferredMimeType(), 0); // assertNotNull(initialResult); // // // Apply edits // String initialText = info.getText(); // assertNotNull(initialText); // Pair<EditHistory,String> pair = getEditHistory(initialText, edits); // EditHistory history = pair.getA(); // String modifiedText = pair.getB(); // // info.setText(modifiedText); // info.setEditHistory(history); // info.setPreviousResult(initialResult); // // // Attempt to avoid garbage collection during timing // System.gc(); // System.gc(); // System.gc(); // long incrementalStartTime = System.nanoTime(); // int caretOffset = history.getStart(); // if (history.getSizeDelta() > 0) { // caretOffset += history.getSizeDelta(); // } // info.setCaretOffset(caretOffset); // ParserResult incrementalResult = info.getEmbeddedResult(info.getPreferredMimeType(), 0); // assertNotNull(incrementalResult); // long incrementalEndTime = System.nanoTime(); // verifyIncremental(incrementalResult, history, initialResult); // // info.setEditHistory(null); // info.setPreviousResult(null); // info.setText(modifiedText); // // System.gc(); // System.gc(); // System.gc(); // long fullParseStartTime = System.nanoTime(); // ParserResult fullParseResult = info.getEmbeddedResult(info.getPreferredMimeType(), 0); // long fullParseEndTime = System.nanoTime(); // assertNotNull(fullParseResult); // // if (speedupExpectation > 0.0) { // long incrementalParseTime = incrementalEndTime-incrementalStartTime; // long fullParseTime = fullParseEndTime-fullParseStartTime; // // Figure out how to ensure garbage collections etc. make a fair run. // assertTrue("Incremental parsing time (" + incrementalParseTime + " ns) should be less than full parse time (" + fullParseTime + " ns); speedup was " + // ((double)fullParseTime)/incrementalParseTime, // ((double)incrementalParseTime)*speedupExpectation < fullParseTime); // } // // assertEquals(incrementalResult.getDiagnostics().toString(), fullParseResult.getDiagnostics().size(), incrementalResult.getDiagnostics().size()); // // return new IncrementalParse(initialResult, info, incrementalResult, history, initialText, modifiedText, fullParseResult); // } // // /** // * Check incremental parsing // * @param relFilePath Path to test file to be parsed // * @param speedupExpectation The speed up we're expecting for incremental processing // * over normal full-file analysis. E.g. 1.0d means we want to ensure that incremental // * parsing is at least as fast as normal parsing. For small files there may be extra // * overhead; you can pass 0.0d to turn off this check (but the test runs to ensure // * that things are working okay.) // * @param edits A list of edits to perform. // */ // protected final void checkIncremental(String relFilePath, double speedupExpectation, String... edits) throws Exception { // IncrementalParse parse = getIncrementalResult(relFilePath, speedupExpectation, edits); // // ParserResult incrementalResult = parse.newParserResult; // ParserResult fullParseResult = parse.fullParseResult; // ParserResult info = parse.info; // // BaseDocument doc = (BaseDocument)info.getDocument(); // assertEquals("Parse trees must equal", doc, fullParseResult,incrementalResult); // //// List<Object> incrValidNodes = new ArrayList<Object>(); //// List<Object> incrInvalidNodes = new ArrayList<Object>(); //// Map<Object,OffsetRange> incrPositions = new HashMap<Object,OffsetRange>(); //// initializeNodes(info, incrementalResult, incrValidNodes, incrPositions, incrInvalidNodes); //// //// String incrementalAnnotatedSource = annotateOffsets(incrValidNodes, incrPositions, incrInvalidNodes, info); //// //// // Now make sure we get an identical linearization of the non-incremental result //// List<Object> validNodes = new ArrayList<Object>(); //// List<Object> invalidNodes = new ArrayList<Object>(); //// Map<Object,OffsetRange> positions = new HashMap<Object,OffsetRange>(); //// initializeNodes(info, fullParseResult, validNodes, positions, invalidNodes); //// //// String fullParseAnnotatedSource = annotateOffsets(validNodes, positions, invalidNodes, info); //// //// assertEquals(fullParseAnnotatedSource, incrementalAnnotatedSource); // } protected void assertEquals(String message, BaseDocument doc, ParserResult expected, ParserResult actual) throws Exception { fail("You must override assertEquals(ParserResult,ParserResult)"); } //////////////////////////////////////////////////////////////////////////// // Type Test //////////////////////////////////////////////////////////////////////////// protected void initializeTypeNodes(ParserResult info, List<Object> nodes, Map<Object,OffsetRange> positions, Map<Object,String> types) throws Exception { // Override in your test // Associate type descriptions with a bunch of nodes. // For every node that has an associated type, add position and description information about it. // This will then be used to generate type hints in the source } protected void checkTypes(String relFilePath) throws Exception { checkTypes(relFilePath, null); } protected void checkTypes(final String relFilePath, final String caretLine) throws Exception { Source testSource = getTestSource(getTestFile(relFilePath)); if (caretLine != null) { int caretOffset = getCaretOffset(testSource.createSnapshot().getText().toString(), caretLine); enforceCaretOffset(testSource, caretOffset); } ParserManager.parse(Collections.singleton(testSource), new UserTask() { public @Override void run(ResultIterator resultIterator) throws Exception { Parser.Result r = resultIterator.getParserResult(); assertTrue(r instanceof ParserResult); ParserResult pr = (ParserResult) r; List<Object> nodes = new ArrayList<Object>(); Map<Object,String> types = new HashMap<Object,String>(); Map<Object,OffsetRange> positions = new HashMap<Object,OffsetRange>(); initializeTypeNodes(pr, nodes, positions, types); String annotatedSource = annotateTypes(nodes, positions, types, pr); assertDescriptionMatches(relFilePath, annotatedSource, false, ".types"); } }); } /** Pass the nodes in an in-order traversal order such that it can properly nest * items when they have identical starting or ending endpoints */ private String annotateTypes(List<Object> validNodes, Map<Object,OffsetRange> positions, Map<Object,String> types, ParserResult info) throws Exception { // StringBuilder sb = new StringBuilder(); String text = info.getSnapshot().getText().toString(); final Map<Object,Integer> traversalNumber = new HashMap<Object,Integer>(); int id = 0; for (Object node : validNodes) { traversalNumber.put(node, id++); } Comparator<Object> FORWARDS_COMPARATOR = new Comparator<Object>() { public int compare(Object o1, Object o2) { assertTrue(traversalNumber.containsKey(o1)); assertTrue(traversalNumber.containsKey(o2)); return traversalNumber.get(o1) - traversalNumber.get(o2); } }; Comparator<Object> BACKWARDS_COMPARATOR = new Comparator<Object>() { public int compare(Object o1, Object o2) { assertTrue(traversalNumber.containsKey(o1)); assertTrue(traversalNumber.containsKey(o2)); return traversalNumber.get(o2) - traversalNumber.get(o1); } }; Map<Integer,List<Object>> starts = new HashMap<Integer,List<Object>>(100); Map<Integer,List<Object>> ends = new HashMap<Integer,List<Object>>(100); for (Object node : validNodes) { OffsetRange range = positions.get(node); List<Object> list = starts.get(range.getStart()); if (list == null) { list = new ArrayList<Object>(); starts.put(range.getStart(), list); } list.add(node); list = ends.get(range.getEnd()); if (list == null) { list = new ArrayList<Object>(); ends.put(range.getEnd(), list); } list.add(node); } // Sort nodes for (List<Object> list : starts.values()) { Collections.sort(list, FORWARDS_COMPARATOR); } for (List<Object> list : ends.values()) { Collections.sort(list, BACKWARDS_COMPARATOR); } // TODO - include information here about nodes without correct positions for (int i = 0; i < text.length(); i++) { if (starts.containsKey(i)) { List<Object> ns = starts.get(i); for (Object n : ns) { sb.append("<"); String desc = types.get(n); //String desc = describeNode(info, n, false); assertNotNull(desc); sb.append(desc); sb.append(">"); } } if (ends.containsKey(i)) { List<Object> ns = ends.get(i); for (Object n : ns) { sb.append("</"); //String desc = describeNode(info, n, false); String desc = types.get(n); assertNotNull(desc); sb.append(desc); sb.append(">"); } } char c = text.charAt(i); switch (c) { case '&': sb.append("&"); break; case '<': sb.append("<"); break; case '>': sb.append(">"); break; default: sb.append(c); } } return sb.toString(); } //////////////////////////////////////////////////////////////////////////// // Hints / Quickfix Tests //////////////////////////////////////////////////////////////////////////// protected HintsProvider getHintsProvider() { HintsProvider provider = getPreferredLanguage().getHintsProvider(); assertNotNull("You must override getHintsProvider, either from your GsfLanguage or your test class", provider); return provider; } private GsfHintsManager getHintsManager(org.netbeans.modules.csl.core.Language language) { return new GsfHintsManager(getPreferredMimeType(), getHintsProvider(), language); } protected String annotateHints(BaseDocument doc, List<Hint> result, int caretOffset) throws Exception { Map<OffsetRange, List<Hint>> posToDesc = new HashMap<OffsetRange, List<Hint>>(); Set<OffsetRange> ranges = new HashSet<OffsetRange>(); for (Hint desc : result) { int start = desc.getRange().getStart(); int end = desc.getRange().getEnd(); OffsetRange range = new OffsetRange(start, end); List<Hint> l = posToDesc.get(range); if (l == null) { l = new ArrayList<Hint>(); posToDesc.put(range, l); } l.add(desc); ranges.add(range); } StringBuilder sb = new StringBuilder(); String text = doc.getText(0, doc.getLength()); Map<Integer, OffsetRange> starts = new HashMap<Integer, OffsetRange>(100); Map<Integer, OffsetRange> ends = new HashMap<Integer, OffsetRange>(100); for (OffsetRange range : ranges) { starts.put(range.getStart(), range); ends.put(range.getEnd(), range); } int index = 0; int length = text.length(); while (index < length) { int lineStart = Utilities.getRowStart(doc, index); int lineEnd = Utilities.getRowEnd(doc, index); OffsetRange lineRange = new OffsetRange(lineStart, lineEnd); boolean skipLine = true; for (OffsetRange range : ranges) { if (lineRange.containsInclusive(range.getStart()) || lineRange.containsInclusive(range.getEnd())) { skipLine = false; } } if (!skipLine) { List<Hint> descsOnLine = null; int underlineStart = -1; int underlineEnd = -1; for (int i = lineStart; i <= lineEnd; i++) { if (i == caretOffset) { sb.append("^"); } if (starts.containsKey(i)) { if (descsOnLine == null) { descsOnLine = new ArrayList<Hint>(); } underlineStart = i-lineStart; OffsetRange range = starts.get(i); if (posToDesc.get(range) != null) { for (Hint desc : posToDesc.get(range)) { descsOnLine.add(desc); } } } if (ends.containsKey(i)) { underlineEnd = i-lineStart; } sb.append(text.charAt(i)); } if (underlineStart != -1) { for (int i = 0; i < underlineStart; i++) { sb.append(" "); } for (int i = underlineStart; i < underlineEnd; i++) { sb.append("-"); } sb.append("\n"); } if (descsOnLine != null) { Collections.sort(descsOnLine, new Comparator<Hint>() { public int compare(Hint arg0, Hint arg1) { return arg0.getDescription().compareTo(arg1.getDescription()); } }); for (Hint desc : descsOnLine) { sb.append("HINT:"); sb.append(desc.getDescription()); sb.append("\n"); List<HintFix> list = desc.getFixes(); if (list != null) { for (HintFix fix : list) { sb.append("FIX:"); sb.append(fix.getDescription()); sb.append("\n"); } } } } } index = lineEnd + 1; } return sb.toString(); } protected boolean parseErrorsOk; protected boolean checkAllHintOffsets() { return true; } protected void customizeHintError(Error error, int start) { // Optionally override } protected enum ChangeOffsetType { NONE, OVERLAP, OUTSIDE }; private void customizeHintInfo(ParserResult result, ChangeOffsetType changeOffsetType) { if (changeOffsetType == ChangeOffsetType.NONE) { return; } if (result == null) { return; } // Test offset handling to make sure we can handle bogus node positions // Document doc = GsfUtilities.getDocument(result.getSnapshot().getSource().getFileObject(), true); // int docLength = doc.getLength(); int docLength = result.getSnapshot().getText().length(); // Replace errors with offsets List<Error> errors = new ArrayList<Error>(); List<? extends Error> oldErrors = result.getDiagnostics(); for (Error error : oldErrors) { int start = error.getStartPosition(); int end = error.getEndPosition(); // Modify document position to be off int length = end - start; if (changeOffsetType == ChangeOffsetType.OUTSIDE) { start = docLength + 1; } else { start = docLength - 1; } end = start + length; if (end <= docLength) { end = docLength + 1; } DefaultError newError = new DefaultError( error.getKey(), error.getDisplayName(), error.getDescription(), error.getFile(), start, end, error.getSeverity() ); newError.setParameters(error.getParameters()); customizeHintError(error, start); // XXX: should not this be newError ?? errors.add(newError); } // XXX: there is no way to fiddle with parser errors // oldErrors.clear(); // oldErrors.addAll(errors); } protected ComputedHints getHints(NbTestCase test, Rule hint, String relFilePath, FileObject fileObject, String caretLine) throws Exception { ComputedHints hints = computeHints(test, hint, relFilePath, fileObject, caretLine, ChangeOffsetType.NONE); if (checkAllHintOffsets()) { // Run alternate hint computation AFTER the real computation above since we will destroy the document... Logger.global.addHandler(new Handler() { @Override public void publish(LogRecord record) { if (record.getThrown() != null) { StringWriter sw = new StringWriter(); record.getThrown().printStackTrace(new PrintWriter(sw)); fail("Encountered error: " + sw.toString()); } } @Override public void flush() { } @Override public void close() throws SecurityException { } }); for (ChangeOffsetType type : new ChangeOffsetType[] { ChangeOffsetType.OUTSIDE, ChangeOffsetType.OVERLAP }) { computeHints(test, hint, relFilePath, fileObject, caretLine, type); } } return hints; } protected ComputedHints computeHints(final NbTestCase test, final Rule hint, String relFilePath, FileObject fileObject, final String lineWithCaret, final ChangeOffsetType changeOffsetType) throws Exception { assertTrue(relFilePath == null || fileObject == null); initializeRegistry(); if (fileObject == null) { fileObject = getTestFile(relFilePath); } Source testSource = getTestSource(fileObject); final int caretOffset; final String caretLine; if (lineWithCaret != null) { CaretLineOffset caretLineOffset = getCaretOffsetInternal(testSource.createSnapshot().getText().toString(), lineWithCaret); caretOffset = caretLineOffset.offset; caretLine = caretLineOffset.caretLine; enforceCaretOffset(testSource, caretLineOffset.offset); } else { caretOffset = -1; caretLine = lineWithCaret; } final ComputedHints [] result = new ComputedHints[] { null }; ParserManager.parse(Collections.singleton(testSource), new UserTask() { public @Override void run(ResultIterator resultIterator) throws Exception { Parser.Result r = resultIterator.getParserResult(); assertTrue(r instanceof ParserResult); ParserResult pr = (ParserResult) r; Document document = pr.getSnapshot().getSource().getDocument(true); assert document != null : test; // remember the original document content, we are going to destroy it // and then restore String originalDocumentContent = null; if (changeOffsetType != ChangeOffsetType.NONE) { customizeHintInfo(pr, changeOffsetType); // Also: Delete te contents from the document!!! // This ensures that the node offsets will be out of date by the time the rules run originalDocumentContent = document.getText(0, document.getLength()); document.remove(0, document.getLength()); } try { if (!(hint instanceof ErrorRule)) { // only expect testcase source errors in error tests if (parseErrorsOk) { int caretOffset = 0; result[0] = new ComputedHints(pr, new ArrayList<Hint>(), caretOffset); return; } else { boolean errors = false; for(Error e : pr.getDiagnostics()) { if (e.getSeverity() == Severity.ERROR) { errors = true; break; } } assertTrue("Unexpected parse error in test case " + FileUtil.getFileDisplayName(pr.getSnapshot().getSource().getFileObject()) + "\nErrors = " + pr.getDiagnostics(), !errors); } } String text = pr.getSnapshot().getText().toString(); org.netbeans.modules.csl.core.Language language = LanguageRegistry.getInstance().getLanguageByMimeType(getPreferredMimeType()); HintsProvider provider = getHintsProvider(); GsfHintsManager manager = getHintsManager(language); UserConfigurableRule ucr = null; if (hint instanceof UserConfigurableRule) { ucr = (UserConfigurableRule)hint; } // Make sure the hint is enabled if (ucr != null && !HintsSettings.isEnabled(manager, ucr)) { Preferences p = HintsSettings.getPreferences(manager, ucr, HintsSettings.getCurrentProfileId()); HintsSettings.setEnabled(p, true); } List<Hint> hints = new ArrayList<Hint>(); if (hint instanceof ErrorRule) { RuleContext context = manager.createRuleContext(pr, language, -1, -1, -1); // It's an error! // Create a hint registry which contains ONLY our hint (so other registered // hints don't interfere with the test) Map<Object, List<? extends ErrorRule>> testHints = new HashMap<Object, List<? extends ErrorRule>>(); if (hint.appliesTo(context)) { ErrorRule ErrorRule = (ErrorRule)hint; for (Object key : ErrorRule.getCodes()) { testHints.put(key, Collections.singletonList(ErrorRule)); } } manager.setTestingRules(testHints, null, null, null); provider.computeErrors(manager, context, hints, new ArrayList<Error>()); } else if (hint instanceof SelectionRule) { SelectionRule rule = (SelectionRule)hint; List<SelectionRule> testHints = new ArrayList<SelectionRule>(); testHints.add(rule); manager.setTestingRules(null, null, null, testHints); if (caretLine != null) { int start = text.indexOf(caretLine.toString()); int end = start+caretLine.length(); RuleContext context = manager.createRuleContext(pr, language, -1, start, end); provider.computeSelectionHints(manager, context, hints, start, end); } } else { assertTrue(hint instanceof AstRule && ucr != null); AstRule AstRule = (AstRule)hint; // Create a hint registry which contains ONLY our hint (so other registered // hints don't interfere with the test) Map<Object, List<? extends AstRule>> testHints = new HashMap<Object, List<? extends AstRule>>(); RuleContext context = manager.createRuleContext(pr, language, caretOffset, -1, -1); if (hint.appliesTo(context)) { for (Object nodeId : AstRule.getKinds()) { testHints.put(nodeId, Collections.singletonList(AstRule)); } } if (HintsSettings.getSeverity(manager, ucr) == HintSeverity.CURRENT_LINE_WARNING) { manager.setTestingRules(null, Collections.EMPTY_MAP, testHints, null); provider.computeSuggestions(manager, context, hints, caretOffset); } else { manager.setTestingRules(null, testHints, null, null); context.caretOffset = -1; provider.computeHints(manager, context, hints); } } result[0] = new ComputedHints(pr, hints, caretOffset); } finally { if (originalDocumentContent != null) { assert document != null; document.remove(0, document.getLength()); document.insertString(0, originalDocumentContent, null); } } } }); return result[0]; } protected void checkHints(NbTestCase test, Rule hint, String relFilePath, String caretLine) throws Exception { findHints(test, hint, relFilePath, null, caretLine); } protected void checkHints(Rule hint, String relFilePath, String selStartLine, String selEndLine) throws Exception { FileObject fo = getTestFile(relFilePath); String text = read(fo); assert selStartLine != null; assert selEndLine != null; int selStartOffset = -1; int lineDelta = selStartLine.indexOf("^"); assertTrue(lineDelta != -1); selStartLine = selStartLine.substring(0, lineDelta) + selStartLine.substring(lineDelta + 1); int lineOffset = text.indexOf(selStartLine); assertTrue(lineOffset != -1); selStartOffset = lineOffset + lineDelta; int selEndOffset = -1; lineDelta = selEndLine.indexOf("^"); assertTrue(lineDelta != -1); selEndLine = selEndLine.substring(0, lineDelta) + selEndLine.substring(lineDelta + 1); lineOffset = text.indexOf(selEndLine); assertTrue(lineOffset != -1); selEndOffset = lineOffset + lineDelta; String caretLine = text.substring(selStartOffset, selEndOffset) + "^"; checkHints(this, hint, relFilePath, caretLine); } // TODO - rename to "checkHints" protected void findHints(NbTestCase test, Rule hint, FileObject fileObject, String caretLine) throws Exception { findHints(test, hint, null, fileObject, caretLine); } protected String getGoldenFileSuffix() { return ""; } // TODO - rename to "checkHints" protected void findHints(NbTestCase test, Rule hint, String relFilePath, FileObject fileObject, String caretLine) throws Exception { ComputedHints r = getHints(test, hint, relFilePath, fileObject, caretLine); ParserResult info = r.info; List<Hint> result = r.hints; int caretOffset = r.caretOffset; String annotatedSource = annotateHints((BaseDocument) info.getSnapshot().getSource().getDocument(true), result, caretOffset); if (fileObject != null) { assertDescriptionMatches(fileObject, annotatedSource, true, getGoldenFileSuffix() + ".hints"); } else { assertDescriptionMatches(relFilePath, annotatedSource, true, getGoldenFileSuffix() + ".hints"); } } protected void applyHint(NbTestCase test, Rule hint, String relFilePath, String selStartLine, String selEndLine, String fixDesc) throws Exception { FileObject fo = getTestFile(relFilePath); String text = read(fo); assert selStartLine != null; assert selEndLine != null; int selStartOffset = -1; int lineDelta = selStartLine.indexOf("^"); assertTrue(lineDelta != -1); selStartLine = selStartLine.substring(0, lineDelta) + selStartLine.substring(lineDelta + 1); int lineOffset = text.indexOf(selStartLine); assertTrue(lineOffset != -1); selStartOffset = lineOffset + lineDelta; int selEndOffset = -1; lineDelta = selEndLine.indexOf("^"); assertTrue(lineDelta != -1); selEndLine = selEndLine.substring(0, lineDelta) + selEndLine.substring(lineDelta + 1); lineOffset = text.indexOf(selEndLine); assertTrue(lineOffset != -1); selEndOffset = lineOffset + lineDelta; String caretLine = text.substring(selStartOffset, selEndOffset) + "^"; applyHint(test, hint, relFilePath, caretLine, fixDesc); } protected void applyHint(NbTestCase test, Rule hint, String relFilePath, String caretLine, String fixDesc) throws Exception { ComputedHints r = getHints(test, hint, relFilePath, null, caretLine); ParserResult info = r.info; HintFix fix = findApplicableFix(r, fixDesc); assertNotNull(fix); String fixed = null; Document doc = info.getSnapshot().getSource().getDocument(true); String originalDocumentContent = doc.getText(0, doc.getLength()); try { if (fix.isInteractive() && fix instanceof PreviewableFix) { PreviewableFix preview = (PreviewableFix)fix; assertTrue(preview.canPreview()); EditList editList = preview.getEditList(); editList.applyToDocument((BaseDocument) doc); } else { fix.implement(); } fixed = doc.getText(0, doc.getLength()); } finally { doc.remove(0, doc.getLength()); doc.insertString(0, originalDocumentContent, null); } assertDescriptionMatches(relFilePath, fixed, true, ".fixed"); } @SuppressWarnings("unchecked") protected final void ensureRegistered(AstRule hint) throws Exception { org.netbeans.modules.csl.core.Language language = LanguageRegistry.getInstance().getLanguageByMimeType(getPreferredMimeType()); assertNotNull(language.getHintsProvider()); GsfHintsManager hintsManager = language.getHintsManager(); Map<?, List<? extends AstRule>> hints = (Map<?, List<? extends AstRule>>)hintsManager.getHints(); Set<?> kinds = hint.getKinds(); for (Object nodeType : kinds) { List<? extends AstRule> rules = hints.get(nodeType); assertNotNull(rules); boolean found = false; for (AstRule rule : rules) { if (rule.getClass() == hint.getClass()) { found = true; break; } } assertTrue(found); } } @SuppressWarnings("unchecked") protected final void ensureRegistered(ErrorRule hint) throws Exception { org.netbeans.modules.csl.core.Language language = LanguageRegistry.getInstance().getLanguageByMimeType(getPreferredMimeType()); assertNotNull(language.getHintsProvider()); GsfHintsManager hintsManager = language.getHintsManager(); Map<?, List<? extends ErrorRule>> hints = (Map<?, List<? extends ErrorRule>>)hintsManager.getErrors(); Set<?> kinds = hint.getCodes(); for (Object codes : kinds) { List<? extends ErrorRule> rules = hints.get(codes); assertNotNull(rules); boolean found = false; for (ErrorRule rule : rules) { if (rule.getClass() == hint.getClass()) { found = true; break; } } assertTrue(found); } } // public void ensureRegistered(AstRule hint) throws Exception { // Map<Integer, List<AstRule>> hints = JsRulesManager.getInstance().getHints(); // Set<Integer> kinds = hint.getKinds(); // for (int nodeType : kinds) { // List<AstRule> rules = hints.get(nodeType); // assertNotNull(rules); // boolean found = false; // for (AstRule rule : rules) { // if (rule instanceof BlockVarReuse) { // found = true; // break; // } // } // // assertTrue(found); // } // } private HintFix findApplicableFix(ComputedHints r, String text) { boolean substringMatch = true; if (text.endsWith("\n")) { text = text.substring(0, text.length()-1); substringMatch = false; } int caretOffset = r.caretOffset; for (Hint desc : r.hints) { int start = desc.getRange().getStart(); int end = desc.getRange().getEnd(); OffsetRange range = new OffsetRange(start, end); if (range.containsInclusive(caretOffset) || caretOffset == range.getEnd()+1) { // special case for wrong JRuby offsets // Optionally make sure the text is the one we're after such that // tests can disambiguate among multiple fixes // special case for wrong JRuby offsets // Optionally make sure the text is the one we're after such that // tests can disambiguate among multiple fixes List<HintFix> list = desc.getFixes(); assertNotNull(list); for (HintFix fix : list) { if (text == null || (substringMatch && fix.getDescription().indexOf(text) != -1) || (!substringMatch && fix.getDescription().equals(text))) { return fix; } } } } return null; } protected static class ComputedHints { ComputedHints(ParserResult info, List<Hint> hints, int caretOffset) { this.info = info; this.hints = hints; this.caretOffset = caretOffset; } @Override public String toString() { return "ComputedHints(caret=" + caretOffset + ",info=" + info + ",hints=" + hints + ")"; } public ParserResult info; public List<Hint> hints; public int caretOffset; } //////////////////////////////////////////////////////////////////////////// // DeclarationFinder //////////////////////////////////////////////////////////////////////////// protected DeclarationFinder getFinder() { DeclarationFinder finder = getPreferredLanguage().getDeclarationFinder(); if (finder == null) { fail("You must override getFinder()"); } return finder; } protected DeclarationLocation findDeclaration(String relFilePath, final String caretLine) throws Exception { Source testSource = getTestSource(getTestFile(relFilePath)); final int caretOffset = getCaretOffset(testSource.createSnapshot().getText().toString(), caretLine); enforceCaretOffset(testSource, caretOffset); final DeclarationLocation [] location = new DeclarationLocation[] { null }; ParserManager.parse(Collections.singleton(testSource), new UserTask() { public @Override void run(ResultIterator resultIterator) throws Exception { Parser.Result r = resultIterator.getParserResult(); assertTrue(r instanceof ParserResult); ParserResult pr = (ParserResult) r; DeclarationFinder finder = getFinder(); location[0] = finder.findDeclaration(pr, caretOffset); } }); return location[0]; } protected void checkDeclaration(String relFilePath, String caretLine, URL url) throws Exception { DeclarationLocation location = findDeclaration(relFilePath, caretLine); if (location == DeclarationLocation.NONE) { // if we dont found a declaration, bail out. assertTrue("DeclarationLocation.NONE", false); } assertEquals(location.getUrl(), url); } protected void checkDeclaration(String relFilePath, String caretLine, String declarationLine) throws Exception { DeclarationLocation location = findDeclaration(relFilePath, caretLine); if (location == DeclarationLocation.NONE) { // if we dont found a declaration, bail out. fail("DeclarationLocation.NONE"); } int caretDelta = declarationLine.indexOf('^'); assertTrue(caretDelta != -1); declarationLine = declarationLine.substring(0, caretDelta) + declarationLine.substring(caretDelta + 1); String text = readFile(location.getFileObject()); int lineOffset = text.indexOf(declarationLine); assertTrue(lineOffset != -1); int caretOffset = lineOffset + caretDelta; if (caretOffset != location.getOffset()) { fail("Offset mismatch (expected " + caretOffset + " vs. actual " + location.getOffset() + ": got " + getSourceWindow(text, location.getOffset())); } } protected void checkDeclaration(String relFilePath, String caretLine, String file, int offset) throws Exception { DeclarationLocation location = findDeclaration(relFilePath, caretLine); if (location == DeclarationLocation.NONE) { // if we dont found a declaration, bail out. assertTrue("DeclarationLocation.NONE", false); } assertEquals(file, location.getFileObject() != null ? location.getFileObject().getNameExt() : "<none>"); assertEquals(offset, location.getOffset()); } protected final void enforceCaretOffset(Source source, int offset) { try { Method m = GsfUtilities.class.getDeclaredMethod("setLastKnowCaretOffset", Source.class, Integer.TYPE); m.setAccessible(true); m.invoke(null, source, offset); } catch (Exception e) { throw new IllegalStateException("Can't enforce caret offset on " + source, e); } } private static final class TestIndexFactoryImpl extends LuceneIndexFactory { // -------------------------------------------------------------------- // IndexFactoryImpl implementation // -------------------------------------------------------------------- public @Override IndexDocument createDocument(Indexable indexable) { return new TestIndexDocumentImpl(indexable, super.createDocument(indexable)); } public @Override DocumentIndex createIndex(Context ctx) throws IOException { DocumentIndex ii = super.createIndex(ctx); Reference<TestIndexImpl> ttiRef = indexImpls.get(ii); TestIndexImpl tii = ttiRef != null ? ttiRef.get() : null; if (tii == null) { tii = new TestIndexImpl(ii); indexImpls.put(ii, new SoftReference<TestIndexImpl>(tii)); } return tii; } public @Override DocumentIndex getIndex(FileObject indexFolder) throws IOException { DocumentIndex ii = super.getIndex(indexFolder); Reference<TestIndexImpl> ttiRef = indexImpls.get(ii); return ttiRef != null ? ttiRef.get() : null; } private final Map<DocumentIndex, Reference<TestIndexImpl>> indexImpls = new WeakHashMap<DocumentIndex, Reference<TestIndexImpl>>(); } // End of TestIndexFactoryImpl class private static final class TestIndexImpl implements DocumentIndex { public TestIndexImpl(DocumentIndex original) { this.original = original; } @Override public Status getStatus() throws IOException { return Status.VALID; } // -------------------------------------------------------------------- // IndexImpl implementation // -------------------------------------------------------------------- @Override public void addDocument(IndexDocument document) { assert document instanceof TestIndexDocumentImpl; original.addDocument(((TestIndexDocumentImpl) document).getOriginal()); TestIndexDocumentImpl tidi = (TestIndexDocumentImpl) document; List<TestIndexDocumentImpl> list = documents.get(tidi.getIndexable().getRelativePath()); if (list == null) { list = new ArrayList<TestIndexDocumentImpl>(); documents.put(tidi.getIndexable().getRelativePath(), list); } list.add(tidi); } @Override public void removeDocument(String relativePath) { original.removeDocument(relativePath); Collection<String> toRemove = new HashSet<String>(); for(String rp : documents.keySet()) { if (rp.equals(relativePath)) { toRemove.add(rp); } } documents.keySet().removeAll(toRemove); } @Override public void store(boolean optimize) throws IOException { original.store(optimize); } @Override public Collection<? extends IndexDocument> query(String fieldName, String value, Queries.QueryKind kind, String... fieldsToLoad) throws IOException, InterruptedException { return original.query(fieldName, value, kind, fieldsToLoad); } @Override public Collection<? extends IndexDocument> findByPrimaryKey(String primaryKeyValue, QueryKind kind, String... fieldsToLoad) throws IOException, InterruptedException { return original.findByPrimaryKey(primaryKeyValue, kind, fieldsToLoad); } @Override public void close() throws IOException { original.close(); } @Override public void markKeyDirty(String primaryKey) { } @Override public void removeDirtyKeys(Collection<? extends String> dirtyKeys) { } @Override public Collection<? extends String> getDirtyKeys() { return Collections.<String>emptySet(); } // -------------------------------------------------------------------- // private implementation // -------------------------------------------------------------------- private final DocumentIndex original; private Map<String, List<TestIndexDocumentImpl>> documents = new HashMap<String, List<TestIndexDocumentImpl>>(); } // End of TestIndexImpl class private static final class TestIndexDocumentImpl implements IndexDocument { public final List<String> indexedKeys = new ArrayList<String>(); public final List<String> indexedValues = new ArrayList<String>(); public final List<String> unindexedKeys = new ArrayList<String>(); public final List<String> unindexedValues = new ArrayList<String>(); private final Indexable indexable; private final IndexDocument original; public TestIndexDocumentImpl(Indexable indexable, IndexDocument original) { this.indexable = indexable; this.original = original; } public Indexable getIndexable() { return indexable; } public IndexDocument getOriginal() { return original; } public @Override void addPair(String key, String value, boolean searchable, boolean stored) { original.addPair(key, value, searchable, stored); if (searchable) { indexedKeys.add(key); indexedValues.add(value); } else { unindexedKeys.add(key); unindexedValues.add(value); } } public String getValue(String key) { return original.getValue(key); } public String[] getValues(String key) { return original.getValues(key); } public String getPrimaryKey() { return original.getPrimaryKey(); } } // End of TestIndexFactoryImpl class protected Map<String, ClassPath> createClassPathsForTest() { return null; } private class TestClassPathProvider implements ClassPathProvider { public TestClassPathProvider() { } public ClassPath findClassPath(FileObject file, String type) { Map<String, ClassPath> map = classPathsForTest; if (map != null) { return map.get(type); } else { return null; } } } // End of TestClassPathProvider class private class TestPathRecognizer extends PathRecognizer { @Override public Set<String> getSourcePathIds() { return CslTestBase.this.getPreferredLanguage().getSourcePathIds(); } @Override public Set<String> getLibraryPathIds() { return CslTestBase.this.getPreferredLanguage().getLibraryPathIds(); } @Override public Set<String> getBinaryLibraryPathIds() { return CslTestBase.this.getPreferredLanguage().getBinaryLibraryPathIds(); } @Override public Set<String> getMimeTypes() { return Collections.singleton(CslTestBase.this.getPreferredMimeType()); } } // End of TestPathRecognizer class private static final class Waiter extends Handler { private final CountDownLatch latch = new CountDownLatch(1); public Waiter() { } public void waitForScanToFinish() { try { latch.await(60000, TimeUnit.MILLISECONDS); if (latch.getCount() > 0) { fail("Waiting for classpath scanning to finish timed out"); } } catch (InterruptedException ex) { fail("Waiting for classpath scanning to finish was interrupted"); } } @Override public void publish(LogRecord record) { String msg = record.getMessage(); if ("scanSources".equals(msg)) { latch.countDown(); } } @Override public void flush() { } @Override public void close() throws SecurityException { } } // End of Waiter class private static class CaretLineOffset { private final int offset; private final String caretLine; public CaretLineOffset(int offset, String caretLine) { this.offset = offset; this.caretLine = caretLine; } } }