369N/A/*
369N/A * CDDL HEADER START
369N/A *
369N/A * The contents of this file are subject to the terms of the
369N/A * Common Development and Distribution License (the "License").
369N/A * You may not use this file except in compliance with the License.
369N/A *
369N/A * See LICENSE.txt included in this distribution for the specific
369N/A * language governing permissions and limitations under the License.
369N/A *
369N/A * When distributing Covered Code, include this CDDL HEADER in each
369N/A * file and include the License file at LICENSE.txt.
369N/A * If applicable, add the following below this CDDL HEADER, with the
369N/A * fields enclosed by brackets "[]" replaced with your own identifying
369N/A * information: Portions Copyright [yyyy] [name of copyright owner]
369N/A *
369N/A * CDDL HEADER END
369N/A */
369N/A
369N/A/*
1069N/A * Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
369N/A */
369N/A
369N/Apackage org.opensolaris.opengrok.search.context;
369N/A
1001N/Aimport java.io.ByteArrayInputStream;
369N/Aimport java.io.CharArrayReader;
369N/Aimport java.io.Reader;
389N/Aimport java.io.StringReader;
369N/Aimport java.io.StringWriter;
1069N/Aimport java.util.ArrayList;
369N/Aimport java.util.Arrays;
1069N/Aimport java.util.List;
1001N/Aimport javax.xml.parsers.DocumentBuilderFactory;
986N/Aimport org.apache.lucene.queryParser.ParseException;
1069N/Aimport org.junit.After;
1069N/Aimport org.junit.Before;
369N/Aimport org.junit.Test;
1071N/Aimport org.opensolaris.opengrok.analysis.Definitions;
1069N/Aimport org.opensolaris.opengrok.configuration.RuntimeEnvironment;
1069N/Aimport org.opensolaris.opengrok.search.Hit;
986N/Aimport org.opensolaris.opengrok.search.QueryBuilder;
1001N/Aimport org.w3c.dom.Document;
369N/Aimport static org.junit.Assert.*;
369N/A
1470N/A/**
1470N/A * {@link Context} Test
1470N/A */
369N/Apublic class ContextTest {
1069N/A /**
1069N/A * The value returned by {@link RuntimeEnvironment#isQuickContextScan()}
1069N/A * before the test is run. Will be used to restore the flag after each
1069N/A * test case.
1069N/A */
1069N/A private boolean savedQuickContextScanFlag;
1069N/A
1470N/A @SuppressWarnings("javadoc")
1069N/A @Before
1069N/A public void setUp() {
1069N/A // Save initial value of the quick context scan flag.
1069N/A savedQuickContextScanFlag =
1470N/A RuntimeEnvironment.getConfig().isQuickContextScan();
1069N/A }
1069N/A
1470N/A @SuppressWarnings("javadoc")
1069N/A @After
1069N/A public void tearDown() {
1069N/A // Restore the initial value of the quick context scan flag.
1470N/A RuntimeEnvironment.getConfig().
1470N/A setQuickContextScan(savedQuickContextScanFlag);
1069N/A }
1069N/A
1069N/A /**
1069N/A * Tests for the isEmpty() method.
1470N/A * @throws ParseException
1069N/A */
1470N/A @SuppressWarnings("static-method")
1069N/A @Test
1069N/A public void testIsEmpty() throws ParseException {
1069N/A String term = "qwerty";
1069N/A
1069N/A // Definition search should be used
1069N/A QueryBuilder qb = new QueryBuilder().setDefs(term);
1069N/A Context c = new Context(qb.build(), qb.getQueries());
1069N/A assertFalse(c.isEmpty());
1069N/A
1069N/A // Symbol search should be used
1069N/A qb = new QueryBuilder().setRefs(term);
1069N/A c = new Context(qb.build(), qb.getQueries());
1069N/A assertFalse(c.isEmpty());
1069N/A
1069N/A // Full search should be used
1069N/A qb = new QueryBuilder().setFreetext(term);
1069N/A c = new Context(qb.build(), qb.getQueries());
1069N/A assertFalse(c.isEmpty());
1069N/A
1069N/A // History search should not be used
1069N/A qb = new QueryBuilder().setHist(term);
1069N/A c = new Context(qb.build(), qb.getQueries());
1069N/A assertTrue(c.isEmpty());
1069N/A
1069N/A // Path search should not be used
1069N/A qb = new QueryBuilder().setPath(term);
1069N/A c = new Context(qb.build(), qb.getQueries());
1069N/A assertTrue(c.isEmpty());
1069N/A
1069N/A // Combined search should be fine
1069N/A qb = new QueryBuilder().setHist(term).setFreetext(term);
1069N/A c = new Context(qb.build(), qb.getQueries());
1069N/A assertFalse(c.isEmpty());
1069N/A }
1069N/A
1069N/A /**
1069N/A * Tests for the getContext() method.
1470N/A * @throws ParseException
1069N/A */
1470N/A @SuppressWarnings("static-method")
1069N/A @Test
1069N/A public void testGetContext() throws ParseException {
1069N/A testGetContext(true, true); // limited scan, output to list
1069N/A testGetContext(false, true); // unlimited scan, output to list
1069N/A testGetContext(true, false); // limited scan, output to writer
1069N/A testGetContext(false, false); // unlimited scan, output to writer
1069N/A }
1069N/A
1069N/A /**
1069N/A * Helper method for testing various paths through the getContext() method.
1069N/A *
1069N/A * @param limit true if limited, quick context scan should be used
1069N/A * @param hitList true if output should be written to a list instead of
1069N/A * a writer
1069N/A */
1470N/A @SuppressWarnings({ "resource", "null" })
1470N/A private static void testGetContext(boolean limit, boolean hitList)
1470N/A throws ParseException
1470N/A {
1069N/A StringReader in = new StringReader("abc def ghi\n");
1069N/A StringWriter out = hitList ? null : new StringWriter();
1069N/A List<Hit> hits = hitList ? new ArrayList<Hit>() : null;
1069N/A
1470N/A RuntimeEnvironment.getConfig().setQuickContextScan(limit);
1071N/A
1071N/A // Search freetext for the term "def"
1069N/A QueryBuilder qb = new QueryBuilder().setFreetext("def");
1069N/A Context c = new Context(qb.build(), qb.getQueries());
1069N/A assertTrue(c.getContext(in, out, "", "", "", null, limit, hits));
1069N/A
1069N/A if (hitList) {
1069N/A assertEquals(1, hits.size());
1069N/A assertEquals("1", hits.get(0).getLineno());
1069N/A }
1069N/A
1470N/A String expectedOutput = hitList
1470N/A ? "abc <b>def</b> ghi"
1470N/A : "<a class=\"rsh\" href=\"#1\"><span class=\"l\">1</span> "
1470N/A + "abc <b>def</b> ghi</a><br/>";
1069N/A
1069N/A String actualOutput = hitList ? hits.get(0).getLine() : out.toString();
1069N/A
1069N/A assertEquals(expectedOutput, actualOutput);
1071N/A
1071N/A // Search with definitions
1071N/A Definitions defs = new Definitions();
1071N/A defs.addTag(1, "def", "type", "text");
1071N/A in = new StringReader("abc def ghi\n");
1071N/A out = hitList ? null : new StringWriter();
1071N/A hits = hitList ? new ArrayList<Hit>() : null;
1071N/A qb = new QueryBuilder().setDefs("def");
1071N/A c = new Context(qb.build(), qb.getQueries());
1071N/A assertTrue(c.getContext(in, out, "", "", "", defs, limit, hits));
1071N/A
1071N/A if (hitList) {
1071N/A assertEquals(1, hits.size());
1071N/A assertEquals("1", hits.get(0).getLineno());
1071N/A }
1071N/A
1470N/A expectedOutput = hitList
1470N/A ? "abc <b>def</b> ghi"
1470N/A : "<a class=\"rsh\" href=\"#1\"><span class=\"l\">1</span> "
1470N/A + "abc <b>def</b> ghi</a> <span class=\"rshd\"> type</span> <br/>";
1071N/A actualOutput = hitList ? hits.get(0).getLine() : out.toString();
1071N/A assertEquals(expectedOutput, actualOutput);
1071N/A
1071N/A // Search with no input (will search definitions)
1071N/A in = null;
1071N/A out = hitList ? null : new StringWriter();
1071N/A hits = hitList ? new ArrayList<Hit>() : null;
1071N/A qb = new QueryBuilder().setDefs("def");
1071N/A c = new Context(qb.build(), qb.getQueries());
1071N/A assertTrue(c.getContext(in, out, "", "", "", defs, limit, hits));
1071N/A
1071N/A if (hitList) {
1071N/A assertEquals(1, hits.size());
1071N/A assertEquals("1", hits.get(0).getLineno());
1071N/A }
1071N/A
1470N/A expectedOutput = hitList
1470N/A ? "text"
1470N/A : "<a class=\"rsh\" href=\"#1\"><span class=\"l\">1</span> "
1470N/A + "text</a> <span class=\"rshd\">type</span><br/>";
1071N/A actualOutput = hitList ? hits.get(0).getLine() : out.toString();
1071N/A assertEquals(expectedOutput, actualOutput);
1071N/A
1071N/A // Search with no results
1071N/A in = new StringReader("abc def ghi\n");
1071N/A out = hitList ? null : new StringWriter();
1071N/A hits = hitList ? new ArrayList<Hit>() : null;
1071N/A qb = new QueryBuilder().setFreetext("no_match");
1071N/A c = new Context(qb.build(), qb.getQueries());
1071N/A assertFalse(c.getContext(in, out, "", "", "", null, limit, hits));
1071N/A if (hitList) {
1071N/A assertEquals(0, hits.size());
1071N/A } else {
1071N/A assertEquals("", out.toString());
1071N/A }
1071N/A
1071N/A // History search (should not show source context)
1071N/A in = new StringReader("abc def ghi\n");
1071N/A out = hitList ? null : new StringWriter();
1071N/A hits = hitList ? new ArrayList<Hit>() : null;
1071N/A qb = new QueryBuilder().setHist("abc");
1071N/A c = new Context(qb.build(), qb.getQueries());
1071N/A assertFalse(c.getContext(in, out, "", "", "", null, limit, hits));
1071N/A if (hitList) {
1071N/A assertEquals(0, hits.size());
1071N/A } else {
1071N/A assertEquals("", out.toString());
1071N/A }
1069N/A }
369N/A
369N/A /**
369N/A * Test that we don't get an {@code ArrayIndexOutOfBoundsException} when
369N/A * a long (&gt;100 characters) line which contains a match is not
369N/A * terminated with a newline character before the buffer boundary.
369N/A * Bug #383.
1470N/A * @throws ParseException
369N/A */
1470N/A @SuppressWarnings("static-method")
369N/A @Test
986N/A public void testLongLineNearBufferBoundary() throws ParseException {
369N/A char[] chars = new char[Context.MAXFILEREAD];
369N/A Arrays.fill(chars, 'a');
369N/A char[] substring = " this is a test ".toCharArray();
1470N/A System.arraycopy(substring, 0, chars,
1470N/A Context.MAXFILEREAD - substring.length, substring.length);
369N/A Reader in = new CharArrayReader(chars);
986N/A QueryBuilder qb = new QueryBuilder().setFreetext("test");
986N/A Context c = new Context(qb.build(), qb.getQueries());
369N/A StringWriter out = new StringWriter();
1470N/A boolean match = c.getContext(in, out, "", "", "", null, true, null);
369N/A assertTrue("No match found", match);
369N/A String s = out.toString();
1470N/A assertTrue("Match not written to Writer", s.contains(" this is a <b>test</b>"));
369N/A assertTrue("No match on line #1", s.contains("href=\"#1\""));
369N/A }
369N/A
389N/A /**
389N/A * Test that we get the [all...] link if a very long line crosses the
389N/A * buffer boundary. Bug 383.
1470N/A * @throws ParseException
389N/A */
1470N/A @SuppressWarnings("static-method")
389N/A @Test
986N/A public void testAllLinkWithLongLines() throws ParseException {
389N/A // Create input which consists of one single line longer than
389N/A // Context.MAXFILEREAD.
389N/A StringBuilder sb = new StringBuilder();
389N/A sb.append("search_for_me");
389N/A while (sb.length() <= Context.MAXFILEREAD) {
389N/A sb.append(" more words");
389N/A }
389N/A Reader in = new StringReader(sb.toString());
389N/A StringWriter out = new StringWriter();
389N/A
986N/A QueryBuilder qb = new QueryBuilder().setFreetext("search_for_me");
986N/A Context c = new Context(qb.build(), qb.getQueries());
389N/A
1470N/A boolean match = c.getContext(in, out, "", "", "", null, true, null);
389N/A assertTrue("No match found", match);
389N/A String s = out.toString();
1185N/A assertTrue("No [all...] link", s.contains(">[all...]</a>"));
389N/A }
390N/A
390N/A /**
390N/A * Test that a line with more than 100 characters after the first match
390N/A * is truncated, and that &hellip; is appended to show that the line is
390N/A * truncated. Bug 383.
1470N/A * @throws ParseException
390N/A */
1470N/A @SuppressWarnings("static-method")
390N/A @Test
986N/A public void testLongTruncatedLine() throws ParseException {
390N/A StringBuilder sb = new StringBuilder();
390N/A sb.append("search_for_me");
390N/A while (sb.length() <= 100) {
390N/A sb.append(" more words");
390N/A }
390N/A sb.append("should not be found");
390N/A
390N/A Reader in = new StringReader(sb.toString());
390N/A StringWriter out = new StringWriter();
390N/A
986N/A QueryBuilder qb = new QueryBuilder().setFreetext("search_for_me");
986N/A Context c = new Context(qb.build(), qb.getQueries());
390N/A
1470N/A boolean match = c.getContext(in, out, "", "", "", null, true, null);
390N/A assertTrue("No match found", match);
390N/A String s = out.toString();
390N/A assertTrue("Match not listed", s.contains("<b>search_for_me</b>"));
390N/A assertFalse("Line not truncated", s.contains("should not be found"));
390N/A assertTrue("Ellipsis not found", s.contains("&hellip;"));
390N/A }
1001N/A
1001N/A /**
1001N/A * Test that valid HTML is generated for a match that spans multiple
1001N/A * lines. It used to nest the tags incorrectly. Bug #15632.
1470N/A * @throws Exception
1001N/A */
1470N/A @SuppressWarnings("static-method")
1001N/A @Test
1001N/A public void testMultiLineMatch() throws Exception {
1001N/A StringReader in = new StringReader("a\nb\nc\n");
1001N/A StringWriter out = new StringWriter();
1001N/A
1001N/A // XML boilerplate
1001N/A out.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
1001N/A out.append("<document>\n");
1001N/A
1001N/A // Search for a multi-token phrase that spans multiple lines in the
1001N/A // input file. The generated HTML fragment is inserted inside a root
1001N/A // element so that the StringWriter contains a valid XML document.
1001N/A QueryBuilder qb = new QueryBuilder().setFreetext("\"a b c\"");
1001N/A Context c = new Context(qb.build(), qb.getQueries());
1470N/A assertTrue("No match found",
1470N/A c.getContext(in, out, "", "", "", null, true, null));
1001N/A
1001N/A // Close the XML document body
1001N/A out.append("\n</document>");
1001N/A
1001N/A // Check that valid XML was generated. This call used to fail with
1001N/A // SAXParseException: [Fatal Error] :3:55: The element type "b" must
1001N/A // be terminated by the matching end-tag "</b>".
1001N/A assertNotNull(parseXML(out.toString()));
1001N/A }
1001N/A
1001N/A /**
1001N/A * Parse the XML document contained in a string.
1001N/A *
1001N/A * @param document string with the contents of an XML document
1001N/A * @return a DOM representation of the document
1001N/A * @throws Exception if the document cannot be parsed
1001N/A */
1470N/A private static Document parseXML(String document) throws Exception {
1001N/A ByteArrayInputStream in =
1470N/A new ByteArrayInputStream(document.getBytes("UTF-8"));
1001N/A return DocumentBuilderFactory.newInstance().
1470N/A newDocumentBuilder().parse(in);
1001N/A }
1082N/A
1082N/A /**
1082N/A * Verify that the matching lines are shown in their original form and
1082N/A * not lower-cased (bug #16848).
1470N/A * @throws Exception
1082N/A */
1470N/A @SuppressWarnings("static-method")
1082N/A @Test
1082N/A public void bug16848() throws Exception {
1082N/A StringReader in = new StringReader("Mixed case: abc AbC dEf\n");
1082N/A StringWriter out = new StringWriter();
1082N/A QueryBuilder qb = new QueryBuilder().setFreetext("mixed");
1082N/A Context c = new Context(qb.build(), qb.getQueries());
1082N/A assertTrue(c.getContext(in, out, "", "", "", null, false, null));
1470N/A assertEquals("<a class=\"rsh\" href=\"#0\"><span class=\"l\">0</span> "
1470N/A + "<b>Mixed</b> case: abc AbC dEf</a><br/>", out.toString());
1082N/A }
1138N/A
1138N/A /**
1138N/A * The results from mixed-case symbol search should contain tags.
1470N/A * @throws Exception
1138N/A */
1470N/A @SuppressWarnings("static-method")
1138N/A @Test
1138N/A public void bug17582() throws Exception {
1138N/A // Freetext search should match regardless of case
1138N/A bug17582(new QueryBuilder().setFreetext("Bug17582"),
1470N/A new int[] {2, 3}, new String[] { "type1", "type2" });
1138N/A
1138N/A // Defs search should only match if case matches
1138N/A bug17582(new QueryBuilder().setDefs("Bug17582"),
1470N/A new int[] {3}, new String[] { "type2" });
1138N/A
1138N/A // Refs search should only match if case matches
1138N/A bug17582(new QueryBuilder().setRefs("Bug17582"),
1470N/A new int[] {3}, new String[] { "type2" });
1138N/A
1138N/A // Path search shouldn't match anything in source
1138N/A bug17582(new QueryBuilder().setPath("Bug17582"),
1470N/A new int[0], new String[0]);
1138N/A
1138N/A // Refs should only match if case matches, but freetext will match
1138N/A // regardless of case
1138N/A bug17582(new QueryBuilder().setRefs("Bug17582").setFreetext("Bug17582"),
1470N/A new int[] {2, 3}, new String[] { "type1", "type2" });
1138N/A
1138N/A // Refs should only match if case matches, hist shouldn't match
1138N/A // anything in source
1138N/A bug17582(new QueryBuilder().setRefs("Bug17582").setHist("bug17582"),
1470N/A new int[] {3}, new String[] { "type2" });
1138N/A }
1138N/A
1138N/A /**
1138N/A * Helper method which does the work for {@link #bug17582()}.
1138N/A *
1138N/A * @param builder builder for the query we want to test
1138N/A * @param lines the expected line numbers in the hit list
1138N/A * @param tags the expected tags in the hit list
1138N/A */
1470N/A @SuppressWarnings("boxing")
1470N/A private static void bug17582(QueryBuilder builder, int[] lines, String[] tags)
1470N/A throws Exception
1470N/A {
1138N/A assertEquals(lines.length, tags.length);
1138N/A
1138N/A StringReader in = new StringReader("abc\nbug17582\nBug17582\n");
1138N/A Definitions defs = new Definitions();
1138N/A defs.addTag(2, "bug17582", "type1", "text1");
1138N/A defs.addTag(3, "Bug17582", "type2", "text2");
1138N/A
1138N/A Context context = new Context(builder.build(), builder.getQueries());
1138N/A ArrayList<Hit> hits = new ArrayList<Hit>();
1138N/A assertEquals(lines.length != 0,
1470N/A context.getContext(in, null, "", "", "", defs, false, hits));
1138N/A assertEquals("Unexpected number of hits", lines.length, hits.size());
1138N/A for (int i = 0; i < lines.length; i++) {
1138N/A assertEquals(Integer.toString(lines[i]), hits.get(i).getLineno());
1138N/A assertEquals(tags[i], hits.get(i).getTag());
1138N/A }
1138N/A }
369N/A}