/*
* 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, 2011, Oracle and/or its affiliates. All rights reserved.
*/
package org.opensolaris.opengrok.analysis;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
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.document.TroffXref;
import org.opensolaris.opengrok.analysis.fortran.FortranXref;
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.sh.ShXref;
import org.opensolaris.opengrok.analysis.sql.SQLXref;
import org.opensolaris.opengrok.analysis.tcl.TclXref;
import org.opensolaris.opengrok.configuration.RuntimeEnvironment;
import org.opensolaris.opengrok.util.TestRepository;
import org.opensolaris.opengrok.web.Prefix;
/**
* 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.
*/
public static final String FIRST_LINE_PREAMBLE =
"<div id='lines'\n><div>";
/** the end of each line in an xref file */
public static final String LINE_SUFFIX = "</div\n>";
/** the end of an xref file */
public static final String EOS = "</div\n>";
@SuppressWarnings("javadoc")
@BeforeClass
public static void setUpClass() throws Exception {
ctags = new Ctags();
ctags.setBinary(RuntimeEnvironment.getConfig().getCtags());
repository = new TestRepository();
repository.create(JFlexXrefTest.class.getResourceAsStream(
"/org/opensolaris/opengrok/index/source.zip"));
}
@SuppressWarnings({ "javadoc", "unused" })
@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.
* @throws Exception
*/
@SuppressWarnings("static-method")
@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 FortranXref(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
*/
@SuppressWarnings("resource")
private static void bug15890LineCount(JFlexXref xref) throws Exception {
xref.write(new XrefWriter(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.
* @throws Exception
*/
@SuppressWarnings("static-method")
@Test
public void testBug15890Anchor() throws Exception {
bug15890Anchor(CXref.class, "c/bug15890.c");
bug15890Anchor(CxxXref.class, "c/bug15890.c");
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
*/
@SuppressWarnings("resource")
private static 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(new XrefWriter(out));
//TODO improve below to reflect all possible classes of a definition
assertTrue(
"No anchor found",
out.toString().contains("\" name=\"bug15890\"/><a href="));
out.close();
in.close();
}
/**
* Regression test case for bug #14663, which used to break syntax
* highlighting in ShXref.
* @throws Exception
*/
@SuppressWarnings("static-method")
@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
*/
@SuppressWarnings("resource")
private static 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(new XrefWriter(output));
assertEquals(FIRST_LINE_PREAMBLE + expectedOutput + LINE_SUFFIX + EOS,
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.
* @throws Exception
*/
@SuppressWarnings({ "static-method", "resource" })
@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(new XrefWriter(out));
assertEquals(FIRST_LINE_PREAMBLE
+ "<b>echo</b> <span class=\"s\">\"xyz</span>" + LINE_SUFFIX + EOS,
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(new XrefWriter(out));
assertEquals(FIRST_LINE_PREAMBLE
+ "<b>echo</b> <span class=\"s\">\"hello\"</span>" + LINE_SUFFIX + EOS,
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 &lt;x/y.h&gt; and
* #include "x/y.h" in C and C++ (bug #17817)
* </li>
*
* <li>
* Verify that the link generated for #include &lt;vector&gt; performs a
* path search (bug #17816)
* </li>
*
* </ul>
* @throws Exception
*/
@SuppressWarnings("static-method")
@Test
public void testCXrefInclude() throws Exception {
testCXrefInclude(CXref.class);
testCXrefInclude(CxxXref.class);
}
@SuppressWarnings("resource")
private static void testCXrefInclude(Class<? extends JFlexXref> klass)
throws Exception
{
String[][] testData = {
{"#include <abc.h>", "#<b>include</b> &lt;<a href=\"/source"
+ Prefix.SEARCH_R + "?path=abc.h\">abc.h</a>&gt;"},
{"#include <abc/def.h>", "#<b>include</b> &lt;<a href=\"/source"
+ Prefix.SEARCH_R + "?path=abc/\">abc</a>/<a href=\"/source"
+ Prefix.SEARCH_R + "?path=abc/def.h\">def.h</a>&gt;"},
{"#include \"abc.h\"", "#<b>include</b> <span class=\"s\">\"<a href=\"/source"
+ Prefix.SEARCH_R + "?path=abc.h\">abc.h</a>\"</span>"},
{"#include \"abc/def.h\"", "#<b>include</b> <span class=\"s\">\"<a href=\"/source"
+ Prefix.SEARCH_R + "?path=abc/\">abc</a>/<a href=\"/source"
+ Prefix.SEARCH_R + "?path=abc/def.h\">def.h</a>\"</span>"},
{"#include <vector>", "#<b>include</b> &lt;<a href=\"/source"
+ Prefix.SEARCH_R + "?path=vector\">vector</a>&gt;"},
};
for (String[] s : testData) {
StringReader in = new StringReader(s[0]);
StringWriter out = new StringWriter();
JFlexXref xref = klass.getConstructor(Reader.class).newInstance(in);
xref.write(new XrefWriter(out));
assertEquals(FIRST_LINE_PREAMBLE + s[1] + LINE_SUFFIX + EOS,
out.toString());
}
}
/**
* Verify that ShXref handles here-documents. Bug #18198.
* @throws IOException
*/
@SuppressWarnings({ "static-method", "resource" })
@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(new XrefWriter(out));
String[] result = out.toString().split("\n");
// The single-quote on line 2 shouldn't start a string literal.
assertTrue(result[2],
result[2].endsWith("This shouldn't cause any problem.</span>"
+ (LINE_SUFFIX + EOS).split("\n")[0]));
// The string literal on line 4 should be recognized as one.
assertTrue(result[4],
result[4].endsWith("=<span class=\"s\">'some string'</span>"
+ (LINE_SUFFIX + EOS).split("\n")[0]));
}
/**
* Test that JavaXref handles empty Java comments. Bug #17885.
* @throws IOException
*/
@SuppressWarnings({ "static-method", "resource" })
@Test
public void testEmptyJavaComment() throws IOException {
StringReader in = new StringReader("/**/\nclass xyz { }\n");
JavaXref xref = new JavaXref(in);
StringWriter out = new StringWriter();
xref.write(new XrefWriter(out));
// Verify that the comment's <span> block is terminated.
assertTrue(out.toString().contains("<span class=\"c\">/**/</span>"));
}
@SuppressWarnings({ "static-method", "javadoc", "resource" })
@Test
public void bug18586() throws IOException {
String filename = repository.getSourceRoot() + "/sql/bug18586.sql";
FileInputStream fis = new FileInputStream(filename);
SQLXref xref = new SQLXref(fis);
xref.setDefs(ctags.doCtags(filename + "\n"));
// The next call used to fail with an ArrayIndexOutOfBoundsException.
xref.write(new XrefWriter(new StringWriter()));
fis.close();
}
}