001 /*
002 * SonarQube, open source software quality management tool.
003 * Copyright (C) 2008-2014 SonarSource
004 * mailto:contact AT sonarsource DOT com
005 *
006 * SonarQube is free software; you can redistribute it and/or
007 * modify it under the terms of the GNU Lesser General Public
008 * License as published by the Free Software Foundation; either
009 * version 3 of the License, or (at your option) any later version.
010 *
011 * SonarQube is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014 * Lesser General Public License for more details.
015 *
016 * You should have received a copy of the GNU Lesser General Public License
017 * along with this program; if not, write to the Free Software Foundation,
018 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
019 */
020 package org.sonar.batch.mediumtest;
021
022 import org.apache.commons.io.IOUtils;
023 import org.slf4j.Logger;
024 import org.slf4j.LoggerFactory;
025 import org.sonar.api.SonarPlugin;
026 import org.sonar.api.batch.bootstrap.ProjectReactor;
027 import org.sonar.api.batch.debt.internal.DefaultDebtModel;
028 import org.sonar.api.batch.fs.InputDir;
029 import org.sonar.api.batch.fs.InputFile;
030 import org.sonar.api.batch.fs.InputPath;
031 import org.sonar.api.batch.fs.internal.DefaultInputFile;
032 import org.sonar.api.batch.sensor.duplication.DuplicationGroup;
033 import org.sonar.api.batch.sensor.highlighting.TypeOfText;
034 import org.sonar.api.batch.sensor.issue.Issue;
035 import org.sonar.api.batch.sensor.measure.Measure;
036 import org.sonar.api.batch.sensor.symbol.Symbol;
037 import org.sonar.api.measures.CoreMetrics;
038 import org.sonar.api.measures.Metric;
039 import org.sonar.api.platform.PluginMetadata;
040 import org.sonar.batch.bootstrap.PluginsReferential;
041 import org.sonar.batch.bootstrap.TaskProperties;
042 import org.sonar.batch.bootstrapper.Batch;
043 import org.sonar.batch.bootstrapper.EnvironmentInformation;
044 import org.sonar.batch.duplication.DuplicationCache;
045 import org.sonar.batch.highlighting.SyntaxHighlightingData;
046 import org.sonar.batch.highlighting.SyntaxHighlightingRule;
047 import org.sonar.batch.index.Cache.Entry;
048 import org.sonar.batch.index.ComponentDataCache;
049 import org.sonar.batch.protocol.input.ActiveRule;
050 import org.sonar.batch.protocol.input.GlobalReferentials;
051 import org.sonar.batch.protocol.input.ProjectReferentials;
052 import org.sonar.batch.referential.GlobalReferentialsLoader;
053 import org.sonar.batch.referential.ProjectReferentialsLoader;
054 import org.sonar.batch.scan.filesystem.InputPathCache;
055 import org.sonar.batch.scan2.AnalyzerIssueCache;
056 import org.sonar.batch.scan2.AnalyzerMeasureCache;
057 import org.sonar.batch.scan2.ProjectScanContainer;
058 import org.sonar.batch.scan2.ScanTaskObserver;
059 import org.sonar.batch.symbol.SymbolData;
060 import org.sonar.core.plugins.DefaultPluginMetadata;
061 import org.sonar.core.plugins.RemotePlugin;
062 import org.sonar.core.source.SnapshotDataTypes;
063
064 import javax.annotation.CheckForNull;
065
066 import java.io.File;
067 import java.io.FileReader;
068 import java.util.ArrayList;
069 import java.util.Collections;
070 import java.util.Date;
071 import java.util.HashMap;
072 import java.util.List;
073 import java.util.Map;
074 import java.util.Properties;
075 import java.util.Set;
076
077 /**
078 * Main utility class for writing batch medium tests.
079 *
080 */
081 public class BatchMediumTester {
082
083 private Batch batch;
084
085 public static BatchMediumTesterBuilder builder() {
086 return new BatchMediumTesterBuilder().registerCoreMetrics();
087 }
088
089 public static class BatchMediumTesterBuilder {
090 private final FakeGlobalReferentialsLoader globalRefProvider = new FakeGlobalReferentialsLoader();
091 private final FakeProjectReferentialsLoader projectRefProvider = new FakeProjectReferentialsLoader();
092 private final FakePluginsReferential pluginsReferential = new FakePluginsReferential();
093 private final Map<String, String> bootstrapProperties = new HashMap<String, String>();
094
095 public BatchMediumTester build() {
096 return new BatchMediumTester(this);
097 }
098
099 public BatchMediumTesterBuilder registerPlugin(String pluginKey, File location) {
100 pluginsReferential.addPlugin(pluginKey, location);
101 return this;
102 }
103
104 public BatchMediumTesterBuilder registerPlugin(String pluginKey, SonarPlugin instance) {
105 pluginsReferential.addPlugin(pluginKey, instance);
106 return this;
107 }
108
109 public BatchMediumTesterBuilder registerCoreMetrics() {
110 for (Metric<?> m : CoreMetrics.getMetrics()) {
111 registerMetric(m);
112 }
113 return this;
114 }
115
116 public BatchMediumTesterBuilder registerMetric(Metric<?> metric) {
117 globalRefProvider.add(metric);
118 return this;
119 }
120
121 public BatchMediumTesterBuilder addQProfile(String language, String name) {
122 projectRefProvider.addQProfile(language, name);
123 return this;
124 }
125
126 public BatchMediumTesterBuilder addDefaultQProfile(String language, String name) {
127 addQProfile(language, name);
128 globalRefProvider.globalSettings().put("sonar.profile." + language, name);
129 return this;
130 }
131
132 public BatchMediumTesterBuilder bootstrapProperties(Map<String, String> props) {
133 bootstrapProperties.putAll(props);
134 return this;
135 }
136
137 public BatchMediumTesterBuilder activateRule(ActiveRule activeRule) {
138 projectRefProvider.addActiveRule(activeRule);
139 return this;
140 }
141
142 }
143
144 public void start() {
145 batch.start();
146 }
147
148 public void stop() {
149 batch.stop();
150 }
151
152 private BatchMediumTester(BatchMediumTesterBuilder builder) {
153 batch = Batch.builder()
154 .setEnableLoggingConfiguration(true)
155 .addComponents(
156 new EnvironmentInformation("mediumTest", "1.0"),
157 builder.pluginsReferential,
158 builder.globalRefProvider,
159 builder.projectRefProvider,
160 new DefaultDebtModel())
161 .setBootstrapProperties(builder.bootstrapProperties)
162 .build();
163 }
164
165 public TaskBuilder newTask() {
166 return new TaskBuilder(this);
167 }
168
169 public TaskBuilder newScanTask(File sonarProps) {
170 Properties prop = new Properties();
171 FileReader reader = null;
172 try {
173 reader = new FileReader(sonarProps);
174 prop.load(reader);
175 } catch (Exception e) {
176 throw new IllegalStateException("Unable to read configuration file", e);
177 } finally {
178 if (reader != null) {
179 IOUtils.closeQuietly(reader);
180 }
181 }
182 TaskBuilder builder = new TaskBuilder(this);
183 builder.property("sonar.task", "scan");
184 builder.property("sonar.projectBaseDir", sonarProps.getParentFile().getAbsolutePath());
185 for (Map.Entry entry : prop.entrySet()) {
186 builder.property(entry.getKey().toString(), entry.getValue().toString());
187 }
188 return builder;
189 }
190
191 public static class TaskBuilder {
192 private final Map<String, String> taskProperties = new HashMap<String, String>();
193 private BatchMediumTester tester;
194
195 public TaskBuilder(BatchMediumTester tester) {
196 this.tester = tester;
197 }
198
199 public TaskResult start() {
200 TaskResult result = new TaskResult();
201 tester.batch.executeTask(taskProperties,
202 result
203 );
204 return result;
205 }
206
207 public TaskBuilder properties(Map<String, String> props) {
208 taskProperties.putAll(props);
209 return this;
210 }
211
212 public TaskBuilder property(String key, String value) {
213 taskProperties.put(key, value);
214 return this;
215 }
216 }
217
218 public static class TaskResult implements ScanTaskObserver {
219
220 private static final Logger LOG = LoggerFactory.getLogger(BatchMediumTester.TaskResult.class);
221
222 private List<Issue> issues = new ArrayList<Issue>();
223 private List<Measure> measures = new ArrayList<Measure>();
224 private Map<String, List<DuplicationGroup>> duplications = new HashMap<String, List<DuplicationGroup>>();
225 private List<InputFile> inputFiles = new ArrayList<InputFile>();
226 private List<InputDir> inputDirs = new ArrayList<InputDir>();
227 private Map<InputFile, SyntaxHighlightingData> highlightingPerFile = new HashMap<InputFile, SyntaxHighlightingData>();
228 private Map<InputFile, SymbolData> symbolTablePerFile = new HashMap<InputFile, SymbolData>();
229
230 @Override
231 public void scanTaskCompleted(ProjectScanContainer container) {
232 LOG.info("Store analysis results in memory for later assertions in medium test");
233 for (Issue issue : container.getComponentByType(AnalyzerIssueCache.class).all()) {
234 issues.add(issue);
235 }
236
237 for (Measure<?> measure : container.getComponentByType(AnalyzerMeasureCache.class).all()) {
238 measures.add(measure);
239 }
240
241 InputPathCache inputFileCache = container.getComponentByType(InputPathCache.class);
242 for (InputPath inputPath : inputFileCache.all()) {
243 if (inputPath instanceof InputFile) {
244 inputFiles.add((InputFile) inputPath);
245 } else {
246 inputDirs.add((InputDir) inputPath);
247 }
248 }
249
250 ComponentDataCache componentDataCache = container.getComponentByType(ComponentDataCache.class);
251 for (InputFile file : inputFiles) {
252 SyntaxHighlightingData highlighting = componentDataCache.getData(((DefaultInputFile) file).key(), SnapshotDataTypes.SYNTAX_HIGHLIGHTING);
253 if (highlighting != null) {
254 highlightingPerFile.put(file, highlighting);
255 }
256 SymbolData symbolTable = componentDataCache.getData(((DefaultInputFile) file).key(), SnapshotDataTypes.SYMBOL_HIGHLIGHTING);
257 if (symbolTable != null) {
258 symbolTablePerFile.put(file, symbolTable);
259 }
260 }
261
262 DuplicationCache duplicationCache = container.getComponentByType(DuplicationCache.class);
263 for (Entry<List<DuplicationGroup>> entry : duplicationCache.entries()) {
264 String effectiveKey = entry.key()[0].toString();
265 duplications.put(effectiveKey, entry.value());
266 }
267
268 }
269
270 public List<Issue> issues() {
271 return issues;
272 }
273
274 public List<Measure> measures() {
275 return measures;
276 }
277
278 public List<InputFile> inputFiles() {
279 return inputFiles;
280 }
281
282 public List<InputDir> inputDirs() {
283 return inputDirs;
284 }
285
286 public List<DuplicationGroup> duplicationsFor(InputFile inputFile) {
287 return duplications.get(((DefaultInputFile) inputFile).key());
288 }
289
290 /**
291 * Get highlighting types at a given position in an inputfile
292 * @param charIndex 0-based offset in file
293 */
294 public List<TypeOfText> highlightingTypeFor(InputFile file, int charIndex) {
295 SyntaxHighlightingData syntaxHighlightingData = highlightingPerFile.get(file);
296 if (syntaxHighlightingData == null) {
297 return Collections.emptyList();
298 }
299 List<TypeOfText> result = new ArrayList<TypeOfText>();
300 for (SyntaxHighlightingRule sortedRule : syntaxHighlightingData.syntaxHighlightingRuleSet()) {
301 if (sortedRule.getStartPosition() <= charIndex && sortedRule.getEndPosition() > charIndex) {
302 result.add(sortedRule.getTextType());
303 }
304 }
305 return result;
306 }
307
308 /**
309 * Get list of all positions of a symbol in an inputfile
310 * @param symbolStartOffset 0-based start offset for the symbol in file
311 * @param symbolEndOffset 0-based end offset for the symbol in file
312 */
313 @CheckForNull
314 public Set<Integer> symbolReferencesFor(InputFile file, int symbolStartOffset, int symbolEndOffset) {
315 SymbolData data = symbolTablePerFile.get(file);
316 if (data == null) {
317 return null;
318 }
319 for (Symbol symbol : data.referencesBySymbol().keySet()) {
320 if (symbol.getDeclarationStartOffset() == symbolStartOffset && symbol.getDeclarationEndOffset() == symbolEndOffset) {
321 return data.referencesBySymbol().get(symbol);
322 }
323 }
324 return null;
325 }
326 }
327
328 private static class FakeGlobalReferentialsLoader implements GlobalReferentialsLoader {
329
330 private int metricId = 1;
331
332 private GlobalReferentials ref = new GlobalReferentials();
333
334 @Override
335 public GlobalReferentials load() {
336 return ref;
337 }
338
339 public Map<String, String> globalSettings() {
340 return ref.globalSettings();
341 }
342
343 public FakeGlobalReferentialsLoader add(Metric metric) {
344 Boolean optimizedBestValue = metric.isOptimizedBestValue();
345 ref.metrics().add(new org.sonar.batch.protocol.input.Metric(metricId,
346 metric.key(),
347 metric.getType().name(),
348 metric.getDescription(),
349 metric.getDirection(),
350 metric.getName(),
351 metric.getQualitative(),
352 metric.getUserManaged(),
353 metric.getWorstValue(),
354 metric.getBestValue(),
355 optimizedBestValue != null ? optimizedBestValue : false));
356 metricId++;
357 return this;
358 }
359 }
360
361 private static class FakeProjectReferentialsLoader implements ProjectReferentialsLoader {
362
363 private ProjectReferentials ref = new ProjectReferentials();
364
365 @Override
366 public ProjectReferentials load(ProjectReactor reactor, TaskProperties taskProperties) {
367 return ref;
368 }
369
370 public FakeProjectReferentialsLoader addQProfile(String language, String name) {
371 ref.addQProfile(new org.sonar.batch.protocol.input.QProfile(name, name, language, new Date()));
372 return this;
373 }
374
375 public FakeProjectReferentialsLoader addActiveRule(ActiveRule activeRule) {
376 ref.addActiveRule(activeRule);
377 return this;
378 }
379 }
380
381 private static class FakePluginsReferential implements PluginsReferential {
382
383 private List<RemotePlugin> pluginList = new ArrayList<RemotePlugin>();
384 private Map<RemotePlugin, File> pluginFiles = new HashMap<RemotePlugin, File>();
385 Map<PluginMetadata, SonarPlugin> localPlugins = new HashMap<PluginMetadata, SonarPlugin>();
386
387 @Override
388 public List<RemotePlugin> pluginList() {
389 return pluginList;
390 }
391
392 @Override
393 public File pluginFile(RemotePlugin remote) {
394 return pluginFiles.get(remote);
395 }
396
397 public FakePluginsReferential addPlugin(String pluginKey, File location) {
398 RemotePlugin plugin = new RemotePlugin(pluginKey, false);
399 pluginList.add(plugin);
400 pluginFiles.put(plugin, location);
401 return this;
402 }
403
404 public FakePluginsReferential addPlugin(String pluginKey, SonarPlugin pluginInstance) {
405 localPlugins.put(DefaultPluginMetadata.create(pluginKey), pluginInstance);
406 return this;
407 }
408
409 @Override
410 public Map<PluginMetadata, SonarPlugin> localPlugins() {
411 return localPlugins;
412 }
413
414 }
415
416 }