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    }