1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * See LICENSE.txt included in this distribution for the specific
9  * language governing permissions and limitations under the License.
10  *
11  * When distributing Covered Code, include this CDDL HEADER in each
12  * file and include the License file at LICENSE.txt.
13  * If applicable, add the following below this CDDL HEADER, with the
14  * fields enclosed by brackets "[]" replaced with your own identifying
15  * information: Portions Copyright [yyyy] [name of copyright owner]
16  *
17  * CDDL HEADER END
18  */
19 
20 /*
21  * Copyright (c) 2008, 2018, Oracle and/or its affiliates. All rights reserved.
22  * Portions Copyright (c) 2018-2019, Chris Fraire <cfraire@me.com>.
23  */
24 package org.opengrok.indexer.search.context;
25 
26 import java.io.File;
27 import java.io.IOException;
28 import java.nio.file.Files;
29 import java.nio.file.Paths;
30 import java.util.ArrayList;
31 import java.util.Collections;
32 import java.util.HashSet;
33 import java.util.List;
34 import java.util.Map;
35 import java.util.Set;
36 import java.util.TreeSet;
37 import org.apache.lucene.document.Document;
38 import org.apache.lucene.search.ScoreDoc;
39 import org.apache.lucene.search.highlight.InvalidTokenOffsetsException;
40 import org.junit.AfterClass;
41 import static org.junit.Assert.assertNotNull;
42 import static org.junit.Assert.assertTrue;
43 import org.junit.BeforeClass;
44 import org.junit.Test;
45 import org.opengrok.indexer.analysis.AbstractAnalyzer;
46 import org.opengrok.indexer.analysis.plain.PlainAnalyzerFactory;
47 import org.opengrok.indexer.configuration.Project;
48 import org.opengrok.indexer.configuration.RuntimeEnvironment;
49 import org.opengrok.indexer.history.HistoryGuru;
50 import org.opengrok.indexer.index.Indexer;
51 import org.opengrok.indexer.util.TestRepository;
52 import org.opengrok.indexer.history.RepositoryFactory;
53 import org.opengrok.indexer.search.QueryBuilder;
54 import org.opengrok.indexer.search.SearchEngine;
55 import static org.opengrok.indexer.util.CustomAssertions.assertLinesEqual;
56 import org.opengrok.indexer.util.IOUtils;
57 
58 /**
59  * Represents a container for tests of {@link SearchEngine} with
60  * {@link ContextFormatter} etc. with a non-zero tab-size.
61  * <p>
62  * Derived from Trond Norbye's {@code SearchEngineTest}
63  */
64 public class SearchAndContextFormatterTest2 {
65 
66     private static final int TABSIZE = 8;
67 
68     private static final List<File> TEMP_DIRS = new ArrayList<>();
69     private static RuntimeEnvironment env;
70     private static TestRepository repository1;
71     private static TestRepository repository2;
72     private static File configFile;
73     private static boolean originalProjectsEnabled;
74 
75     @BeforeClass
setUpClass()76     public static void setUpClass() throws Exception {
77         env = RuntimeEnvironment.getInstance();
78 
79         originalProjectsEnabled = env.isProjectsEnabled();
80         env.setProjectsEnabled(true);
81 
82         File sourceRoot = createTemporaryDirectory("srcroot");
83         assertTrue("sourceRoot.isDirectory()", sourceRoot.isDirectory());
84         File dataroot = createTemporaryDirectory("dataroot");
85         assertTrue("dataroot.isDirectory()", dataroot.isDirectory());
86 
87         repository1 = new TestRepository();
88         repository1.create(HistoryGuru.class.getResourceAsStream(
89             "repositories.zip"));
90 
91         repository2 = new TestRepository();
92         repository2.create(HistoryGuru.class.getResourceAsStream(
93             "repositories.zip"));
94 
95         // Create symlink #1 underneath source root.
96         final String SYMLINK1 = "symlink1";
97         File symlink1 = new File(sourceRoot.getCanonicalFile(), SYMLINK1);
98         Files.createSymbolicLink(Paths.get(symlink1.getPath()),
99             Paths.get(repository1.getSourceRoot()));
100         assertTrue("symlink1.exists()", symlink1.exists());
101 
102         // Create symlink #2 underneath source root.
103         final String SYMLINK2 = "symlink2";
104         File symlink2 = new File(sourceRoot.getCanonicalFile(), SYMLINK2);
105         Files.createSymbolicLink(Paths.get(symlink2.getPath()),
106             Paths.get(repository2.getSourceRoot()));
107         assertTrue("symlink2.exists()", symlink2.exists());
108 
109         Set<String> allowedSymlinks = new HashSet<>();
110         allowedSymlinks.add(symlink1.getAbsolutePath());
111         allowedSymlinks.add(symlink2.getAbsolutePath());
112         env.setAllowedSymlinks(allowedSymlinks);
113 
114         env.setCtags(System.getProperty(
115             "org.opengrok.indexer.analysis.Ctags", "ctags"));
116         env.setSourceRoot(sourceRoot.getPath());
117         env.setDataRoot(dataroot.getPath());
118         RepositoryFactory.initializeIgnoredNames(env);
119 
120         env.setHistoryEnabled(false);
121         Indexer.getInstance().prepareIndexer(env, true, true,
122                 false, null, null);
123         env.setDefaultProjectsFromNames(new TreeSet<>(Collections.singletonList("/c")));
124 
125         Project proj1 = env.getProjects().get(SYMLINK1);
126         assertNotNull("symlink1 project", proj1);
127         proj1.setTabSize(TABSIZE);
128 
129         Indexer.getInstance().doIndexerExecution(true, null, null);
130 
131         configFile = File.createTempFile("configuration", ".xml");
132         env.writeConfiguration(configFile);
133         RuntimeEnvironment.getInstance().readConfiguration(new File(
134             configFile.getAbsolutePath()));
135     }
136 
137     @AfterClass
tearDownClass()138     public static void tearDownClass() {
139         env.setProjectsEnabled(originalProjectsEnabled);
140         env.setAllowedSymlinks(new HashSet<>());
141 
142         if (repository1 != null) {
143             repository1.destroy();
144         }
145         if (repository2 != null) {
146             repository2.destroy();
147         }
148         if (configFile != null) {
149             configFile.delete();
150         }
151 
152         try {
153             TEMP_DIRS.forEach((tempDir) -> {
154                 try {
155                     IOUtils.removeRecursive(tempDir.toPath());
156                 } catch (IOException e) {
157                     // ignore
158                 }
159             });
160         } finally {
161             TEMP_DIRS.clear();
162         }
163     }
164 
165     @Test
testSearch()166     public void testSearch() throws IOException, InvalidTokenOffsetsException {
167         SearchEngine instance = new SearchEngine();
168         instance.setFreetext("Hello");
169         instance.setFile("renamed2.c");
170         int noHits = instance.search();
171         assertTrue("noHits should be positive", noHits > 0);
172         String[] frags = getFirstFragments(instance);
173         assertNotNull("getFirstFragments() should return something", frags);
174         assertTrue("frags should have one element", frags.length == 1);
175         assertNotNull("frags[0] should be defined", frags[0]);
176 
177         final String CTX =
178             "<a class=\"s\" href=\"/source/symlink1/git/moved2/renamed2.c#16\"><span class=\"l\">16</span> </a><br/>" +
179             "<a class=\"s\" href=\"/source/symlink1/git/moved2/renamed2.c#17\"><span class=\"l\">17</span>         printf ( &quot;<b>Hello</b>, world!\\n&quot; );</a><br/>" +
180             "<a class=\"s\" href=\"/source/symlink1/git/moved2/renamed2.c#18\"><span class=\"l\">18</span> </a><br/>";
181         assertLinesEqual("ContextFormatter output", CTX, frags[0]);
182         instance.destroy();
183     }
184 
getFirstFragments(SearchEngine instance)185     private String[] getFirstFragments(SearchEngine instance)
186             throws IOException, InvalidTokenOffsetsException {
187 
188         ContextArgs args = new ContextArgs((short)1, (short)10);
189 
190         /*
191          * The following `anz' should go unused, but UnifiedHighlighter demands
192          * an analyzer "even if in some circumstances it isn't used."
193          */
194         PlainAnalyzerFactory fac = PlainAnalyzerFactory.DEFAULT_INSTANCE;
195         AbstractAnalyzer anz = fac.getAnalyzer();
196 
197         ContextFormatter formatter = new ContextFormatter(args);
198         OGKUnifiedHighlighter uhi = new OGKUnifiedHighlighter(env,
199             instance.getSearcher(), anz);
200         uhi.setBreakIterator(StrictLineBreakIterator::new);
201         uhi.setFormatter(formatter);
202         uhi.setTabSize(TABSIZE);
203 
204         ScoreDoc[] docs = instance.scoreDocs();
205         for (int i = 0; i < docs.length; ++i) {
206             int docid = docs[i].doc;
207             Document doc = instance.doc(docid);
208 
209             String path = doc.get(QueryBuilder.PATH);
210             System.out.println(path);
211             formatter.setUrl("/source" + path);
212 
213             for (String contextField :
214                 instance.getQueryBuilder().getContextFields()) {
215 
216                 Map<String,String[]> res = uhi.highlightFields(
217                     new String[]{contextField}, instance.getQueryObject(),
218                     new int[] {docid}, new int[] {10});
219                 String[] frags = res.getOrDefault(contextField, null);
220                 if (frags != null) {
221                     return frags;
222                 }
223             }
224         }
225         return null;
226     }
227 
createTemporaryDirectory(String name)228     private static File createTemporaryDirectory(String name)
229             throws IOException {
230         File f = Files.createTempDirectory(name).toFile();
231         TEMP_DIRS.add(f);
232         return f;
233     }
234 }
235