/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* See LICENSE.txt included in this distribution for the specific
* language governing permissions and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at LICENSE.txt.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright (c) 2010, 2016, Oracle and/or its affiliates. All rights reserved.
*/
package org.opensolaris.opengrok.analysis;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.Arrays;
import javax.xml.parsers.DocumentBuilderFactory;
import org.apache.lucene.document.Document;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.opensolaris.opengrok.analysis.c.CXref;
import org.opensolaris.opengrok.analysis.c.CxxXref;
import org.opensolaris.opengrok.analysis.csharp.CSharpXref;
import org.opensolaris.opengrok.analysis.document.TroffXref;
import org.opensolaris.opengrok.analysis.executables.JavaClassAnalyzerFactory;
import org.opensolaris.opengrok.analysis.fortran.FortranXref;
import org.opensolaris.opengrok.analysis.haskell.HaskellXref;
import org.opensolaris.opengrok.analysis.java.JavaXref;
import org.opensolaris.opengrok.analysis.lisp.LispXref;
import org.opensolaris.opengrok.analysis.perl.PerlXref;
import org.opensolaris.opengrok.analysis.plain.PlainXref;
import org.opensolaris.opengrok.analysis.plain.XMLXref;
import org.opensolaris.opengrok.analysis.scala.ScalaXref;
import org.opensolaris.opengrok.analysis.sh.ShXref;
import org.opensolaris.opengrok.analysis.sql.SQLXref;
import org.opensolaris.opengrok.analysis.tcl.TclXref;
import org.opensolaris.opengrok.analysis.uue.UuencodeXref;
import org.opensolaris.opengrok.configuration.RuntimeEnvironment;
import org.opensolaris.opengrok.util.TestRepository;
import org.xml.sax.InputSource;
import static org.junit.Assert.*;
/**
* Unit tests for JFlexXref.
*/
public class JFlexXrefTest {
private static Ctags ctags;
private static TestRepository repository;
/**
* This is what we expect to find at the beginning of the first line
* returned by an xref.
*/
private static final String FIRST_LINE_PREAMBLE =
"<a class=\"l\" name=\"1\" href=\"#1\">1</a>";
@BeforeClass
public static void setUpClass() throws Exception {
ctags = new Ctags();
ctags.setBinary(RuntimeEnvironment.getInstance().getCtags());
repository = new TestRepository();
repository.create(JFlexXrefTest.class.getResourceAsStream(
"/org/opensolaris/opengrok/index/source.zip"));
}
@AfterClass
public static void tearDownClass() throws Exception {
ctags.close();
ctags = null;
repository.destroy();
}
/**
* Regression test case for bug #15890. Check that we get the expected the
* expected line count from input with some special characters that used
* to cause trouble.
*/
@Test
public void testBug15890LineCount() throws Exception {
String fileContents =
"line 1\n" +
"line 2\n" +
"line 3\n" +
"line 4 with \u000B char\n" +
"line 5 with \u000C char\n" +
"line 6 with \u0085 char\n" +
"line 7 with \u2028 char\n" +
"line 8 with \u2029 char\n" +
"line 9\n";
bug15890LineCount(new CXref(new StringReader(fileContents)));
bug15890LineCount(new CxxXref(new StringReader(fileContents)));
bug15890LineCount(new LispXref(new StringReader(fileContents)));
bug15890LineCount(new JavaXref(new StringReader(fileContents)));
bug15890LineCount(new ScalaXref(new StringReader(fileContents)));
bug15890LineCount(new FortranXref(new StringReader(fileContents)));
bug15890LineCount(new HaskellXref(new StringReader(fileContents)));
bug15890LineCount(new XMLXref(new StringReader(fileContents)));
bug15890LineCount(new ShXref(new StringReader(fileContents)));
bug15890LineCount(new TclXref(new StringReader(fileContents)));
bug15890LineCount(new SQLXref(new StringReader(fileContents)));
bug15890LineCount(new TroffXref(new StringReader(fileContents)));
bug15890LineCount(new PlainXref(new StringReader(fileContents)));
bug15890LineCount(new PerlXref(new StringReader(fileContents)));
}
/**
* Helper method that checks the line count for
* {@link #testBug15890LineCount()}.
*
* @param xref an instance of the xref class to test
*/
private void bug15890LineCount(JFlexXref xref) throws Exception {
xref.write(new StringWriter());
assertEquals(10, xref.getLineNumber());
}
/**
* Regression test case for bug #15890. Check that an anchor is correctly
* inserted for definitions that appear after some special characters that
* used to cause trouble.
*/
@Test
public void testBug15890Anchor() throws Exception {
bug15890Anchor(CXref.class, "c/bug15890.c");
bug15890Anchor(CxxXref.class, "c/bug15890.c");
bug15890Anchor(HaskellXref.class, "haskell/bug15890.hs");
bug15890Anchor(LispXref.class, "lisp/bug15890.lisp");
bug15890Anchor(JavaXref.class, "java/bug15890.java");
}
/**
* Helper method for {@link #testBug15890Anchor()}.
*
* @param klass the Xref sub-class to test
* @param path path to input file with a definition
*/
private void bug15890Anchor(Class<? extends JFlexXref> klass, String path)
throws Exception {
File file = new File(repository.getSourceRoot() + File.separator + path);
Definitions defs = ctags.doCtags(file.getAbsolutePath() + "\n");
// Input files contain non-ascii characters and are encoded in UTF-8
Reader in = new InputStreamReader(new FileInputStream(file), "UTF-8");
JFlexXref xref = klass.getConstructor(Reader.class).newInstance(in);
xref.setDefs(defs);
StringWriter out = new StringWriter();
xref.write(out);
//TODO improve below to reflect all possible classes of a definition
assertTrue(
"No anchor found",
out.toString().contains("\" name=\"bug15890\"/><a href="));
}
/**
* Regression test case for bug #14663, which used to break syntax
* highlighting in ShXref.
*/
@Test
public void testBug14663() throws Exception {
// \" should not start a new string literal
assertXrefLine(ShXref.class, "echo \\\"", "<b>echo</b> \\\"");
// \" should not terminate a string literal
assertXrefLine(ShXref.class, "echo \"\\\"\"",
"<b>echo</b> <span class=\"s\">\"\\\"\"</span>");
// \` should not start a command substitution
assertXrefLine(ShXref.class, "echo \\`", "<b>echo</b> \\`");
// \` should not start command substitution inside a string
assertXrefLine(ShXref.class, "echo \"\\`\"",
"<b>echo</b> <span class=\"s\">\"\\`\"</span>");
// \` should not terminate command substitution
assertXrefLine(ShXref.class, "echo `\\``",
"<b>echo</b> <span>`\\``</span>");
// $# should not start a comment
assertXrefLine(ShXref.class, "$#", "$#");
}
/**
* Helper method that checks that the expected output is produced for a
* line with the specified xref class. Fails if the output is not as
* expected.
*
* @param xrefClass xref class to test
* @param inputLine the source code line to parse
* @param expectedOutput the expected output from the xreffer
*/
private void assertXrefLine(Class<? extends JFlexXref> xrefClass,
String inputLine, String expectedOutput) throws Exception {
JFlexXref xref = xrefClass.getConstructor(Reader.class).newInstance(
new StringReader(inputLine));
StringWriter output = new StringWriter();
xref.write(output);
assertEquals(FIRST_LINE_PREAMBLE + expectedOutput, output.toString());
}
/**
* Regression test case for bug #16883. Some of the state used to survive
* across invocations in ShXref, so that a syntax error in one file might
* cause broken highlighting in subsequent files. Test that the instance
* is properly reset now.
*/
@Test
public void bug16883() throws Exception {
// Analyze a script with broken syntax (unterminated string literal)
ShXref xref = new ShXref(new StringReader("echo \"xyz"));
StringWriter out = new StringWriter();
xref.write(out);
assertEquals(
FIRST_LINE_PREAMBLE +
"<b>echo</b> <span class=\"s\">\"xyz</span>",
out.toString());
// Reuse the xref and verify that the broken syntax in the previous
// file doesn't cause broken highlighting in the next file
out = new StringWriter();
String contents = "echo \"hello\"";
xref.reInit(contents.toCharArray(), contents.length());
xref.write(out);
assertEquals(
FIRST_LINE_PREAMBLE +
"<b>echo</b> <span class=\"s\">\"hello\"</span>",
out.toString());
}
/**
* <p>
* Test the handling of #include in C and C++. In particular, these issues
* are tested:
* </p>
*
* <ul>
*
* <li>
* Verify that we use breadcrumb path for both #include <x/y.h> and
* #include "x/y.h" in C and C++ (bug #17817)
* </li>
*
* <li>
* Verify that the link generated for #include <vector> performs a
* path search (bug #17816)
* </li>
*
* </ul>
*/
@Test
public void testCXrefInclude() throws Exception {
testCXrefInclude(CXref.class);
testCXrefInclude(CxxXref.class);
}
private void testCXrefInclude(Class<? extends JFlexXref> klass) throws Exception {
String[][] testData = {
{"#include <abc.h>", "#<b>include</b> <<a href=\"/source/s?path=abc.h\">abc.h</a>>"},
{"#include <abc/def.h>", "#<b>include</b> <<a href=\"/source/s?path=abc/\">abc</a>/<a href=\"/source/s?path=abc/def.h\">def.h</a>>"},
{"#include \"abc.h\"", "#<b>include</b> <span class=\"s\">\"<a href=\"/source/s?path=abc.h\">abc.h</a>\"</span>"},
{"#include \"abc/def.h\"", "#<b>include</b> <span class=\"s\">\"<a href=\"/source/s?path=abc/\">abc</a>/<a href=\"/source/s?path=abc/def.h\">def.h</a>\"</span>"},
{"#include <vector>", "#<b>include</b> <<a href=\"/source/s?path=vector\">vector</a>>"},
};
for (String[] s : testData) {
StringReader in = new StringReader(s[0]);
StringWriter out = new StringWriter();
JFlexXref xref = klass.getConstructor(Reader.class).newInstance(in);
xref.write(out);
assertEquals(FIRST_LINE_PREAMBLE + s[1], out.toString());
}
}
/**
* Verify that template parameters are treated as class names rather than
* filenames.
*/
@Test
public void testCxxXrefTemplateParameters() throws Exception {
StringReader in = new StringReader("#include <vector>\nclass MyClass;\nstd::vector<MyClass> *v;");
StringWriter out = new StringWriter();
JFlexXref xref = new CxxXref(in);
xref.write(out);
assertTrue("Link to search for definition of class not found",
out.toString().contains("<<a href=\"/source/s?defs=MyClass\""));
}
/**
* Verify that ShXref handles here-documents. Bug #18198.
*/
@Test
public void testShXrefHeredoc() throws IOException {
StringReader in = new StringReader(
"cat<<EOF\n" +
"This shouldn't cause any problem.\n" +
"EOF\n" +
"var='some string'\n");
ShXref xref = new ShXref(in);
StringWriter out = new StringWriter();
xref.write(out);
String[] result = out.toString().split("\n");
// The single-quote on line 2 shouldn't start a string literal.
assertTrue(result[1].endsWith("This shouldn't cause any problem."));
// The string literal on line 4 should be recognized as one.
assertTrue(
result[3].endsWith("=<span class=\"s\">'some string'</span>"));
}
/**
* Test that JavaXref handles empty Java comments. Bug #17885.
*/
@Test
public void testEmptyJavaComment() throws IOException {
StringReader in = new StringReader("/**/\nclass xyz { }\n");
JavaXref xref = new JavaXref(in);
StringWriter out = new StringWriter();
xref.write(out);
// Verify that the comment's <span> block is terminated.
assertTrue(out.toString().contains("<span class=\"c\">/**/</span>"));
}
@Test
public void bug18586() throws IOException {
String filename = repository.getSourceRoot() + "/sql/bug18586.sql";
Reader in = new InputStreamReader(new FileInputStream(filename), "UTF-8");
SQLXref xref = new SQLXref(in);
xref.setDefs(ctags.doCtags(filename + "\n"));
// The next call used to fail with an ArrayIndexOutOfBoundsException.
xref.write(new StringWriter());
}
/**
* Test that unterminated heredocs don't cause infinite loop in ShXref.
* This originally became a problem after upgrade to JFlex 1.5.0.
*/
@Test
public void unterminatedHeredoc() throws IOException {
ShXref xref = new ShXref(new StringReader(
"cat << EOF\nunterminated heredoc"));
StringWriter out = new StringWriter();
// The next call used to loop forever.
xref.write(out);
assertEquals("<a class=\"l\" name=\"1\" href=\"#1\">1</a>"
+ "<a href=\"/source/s?defs=cat\" class=\"intelliWindow-symbol\" data-definition-place=\"undefined-in-file\">cat</a> << EOF"
+ "<span class=\"s\">\n"
+ "<a class=\"l\" name=\"2\" href=\"#2\">2</a>"
+ "unterminated heredoc</span>",
out.toString());
}
/**
* Truncated uuencoded files used to cause infinite loops. Verify that
* they work now.
*/
@Test
public void truncatedUuencodedFile() throws IOException {
UuencodeXref xref = new UuencodeXref(
new StringReader("begin 644 test.txt\n"));
// Generating the xref used to loop forever.
StringWriter out = new StringWriter();
xref.write(out);
assertEquals("<a class=\"l\" name=\"1\" href=\"#1\">1</a>"
+ "<strong>begin</strong> <i>644</i> "
+ "<a href=\"/source/s?q=test.txt\">test.txt</a>"
+ "<span class='c'>\n"
+ "<a class=\"l\" name=\"2\" href=\"#2\">2</a>",
out.toString());
}
/**
* Test that CSharpXref correctly handles verbatim strings that end with backslash
*/
@Test
public void testCsharpXrefVerbatimString() throws IOException {
StringReader in = new StringReader("test(@\"\\some_windows_path_in_a_string\\\");");
CSharpXref xref = new CSharpXref(in);
StringWriter out = new StringWriter();
xref.write(out);
assertTrue(out.toString().contains("<span class=\"s\">@\"\\some_windows_path_in_a_string\\\"</span>"));
}
/**
* Test that special characters in URLs are escaped in the xref.
*/
@Test
public void testEscapeLink() throws IOException {
StringReader in = new StringReader("http://www.example.com/?a=b&c=d");
PlainXref xref = new PlainXref(in);
StringWriter out = new StringWriter();
xref.write(out);
assertTrue(out.toString().contains(
"<a href=\"http://www.example.com/?a=b&c=d\">" +
"http://www.example.com/?a=b&c=d</a>"));
}
/**
* Test that JFlex rules that contain quotes don't cause invalid xref
* to be produced.
*/
@Test
public void testJFlexRule() throws Exception {
StringReader in = new StringReader("\\\" { yybegin(STRING); }");
// JFlex files are usually analyzed with CAnalyzer.
CXref xref = new CXref(in);
StringWriter out = new StringWriter();
xref.write(out);
// Verify that the xref is well-formed XML. Used to throw
// SAXParseException: The element type "span" must be terminated
// by the matching end-tag "</span>".
DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(
new InputSource(new StringReader("<doc>" + out + "</doc>")));
}
/**
* Unterminated string literals or comments made CXref produce output
* that was not valid XML, due to missing end tags. Test that it is no
* longer so.
*/
@Test
public void testUnterminatedElements() throws Exception {
for (String str : Arrays.asList("#define STR \"abc\n",
"void f(); /* unterminated comment\n",
"const char c = 'x\n")) {
StringReader in = new StringReader(str);
CXref xref = new CXref(in);
StringWriter out = new StringWriter();
xref.write(out);
// Used to throw SAXParseException.
DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(
new InputSource(new StringReader("<doc>" + out + "</doc>")));
}
}
/**
* Test that JavaClassAnalyzer produces well-formed output.
*/
@Test
public void testJavaClassAnalyzer() throws Exception {
StreamSource src = new StreamSource() {
@Override public InputStream getStream() throws IOException {
final String path = "/" +
StringWriter.class.getName().replace('.', '/') +
".class";
return StringWriter.class.getResourceAsStream(path);
}
};
Document doc = new Document();
StringWriter out = new StringWriter();
new JavaClassAnalyzerFactory().getAnalyzer().analyze(doc, src, out);
// Used to throw SAXParseException.
DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(
new InputSource(new StringReader("<doc>" + out + "</doc>")));
}
/**
* Test that special characters in Fortran files are escaped.
*/
@Test
public void testFortranSpecialCharacters() throws Exception {
FortranXref xref = new FortranXref(new StringReader("<?php?>"));
StringWriter out = new StringWriter();
xref.write(out);
// Used to throw SAXParseException.
DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(
new InputSource(new StringReader("<doc>" + out + "</doc>")));
}
}