001/* 002 * SonarQube 003 * Copyright (C) 2009-2016 SonarSource SA 004 * mailto:contact AT sonarsource DOT com 005 * 006 * This program 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 * This program 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 */ 020package org.sonar.api.batch.sensor.internal; 021 022import com.google.common.annotations.Beta; 023import com.google.common.collect.HashBasedTable; 024import com.google.common.collect.Table; 025import java.io.File; 026import java.io.Serializable; 027import java.nio.file.Path; 028import java.util.ArrayList; 029import java.util.Collection; 030import java.util.Collections; 031import java.util.EnumMap; 032import java.util.HashMap; 033import java.util.List; 034import java.util.Map; 035import javax.annotation.CheckForNull; 036import org.sonar.api.SonarQubeVersion; 037import org.sonar.api.batch.AnalysisMode; 038import org.sonar.api.batch.fs.InputModule; 039import org.sonar.api.batch.fs.internal.DefaultFileSystem; 040import org.sonar.api.batch.fs.internal.DefaultInputModule; 041import org.sonar.api.batch.fs.internal.DefaultTextPointer; 042import org.sonar.api.batch.rule.ActiveRules; 043import org.sonar.api.batch.rule.internal.ActiveRulesBuilder; 044import org.sonar.api.batch.sensor.Sensor; 045import org.sonar.api.batch.sensor.SensorContext; 046import org.sonar.api.batch.sensor.coverage.CoverageType; 047import org.sonar.api.batch.sensor.coverage.NewCoverage; 048import org.sonar.api.batch.sensor.coverage.internal.DefaultCoverage; 049import org.sonar.api.batch.sensor.cpd.NewCpdTokens; 050import org.sonar.api.batch.sensor.cpd.internal.DefaultCpdTokens; 051import org.sonar.api.batch.sensor.highlighting.NewHighlighting; 052import org.sonar.api.batch.sensor.highlighting.TypeOfText; 053import org.sonar.api.batch.sensor.highlighting.internal.DefaultHighlighting; 054import org.sonar.api.batch.sensor.highlighting.internal.SyntaxHighlightingRule; 055import org.sonar.api.batch.sensor.issue.Issue; 056import org.sonar.api.batch.sensor.issue.NewIssue; 057import org.sonar.api.batch.sensor.issue.internal.DefaultIssue; 058import org.sonar.api.batch.sensor.measure.Measure; 059import org.sonar.api.batch.sensor.measure.NewMeasure; 060import org.sonar.api.batch.sensor.measure.internal.DefaultMeasure; 061import org.sonar.api.config.Settings; 062import org.sonar.api.internal.SonarQubeVersionFactory; 063import org.sonar.api.measures.Metric; 064import org.sonar.api.utils.System2; 065import org.sonar.api.utils.Version; 066import org.sonar.duplications.internal.pmd.TokensLine; 067 068/** 069 * Utility class to help testing {@link Sensor}. 070 * 071 * Usage: call {@link #create(File)} to create an "in memory" implementation of {@link SensorContext} then 072 * pass it to your {@link Sensor}. You can then query elements provided by your sensor using methods {@link #allIssues()}, ... 073 * 074 * @since 5.1 075 */ 076@Beta 077public class SensorContextTester implements SensorContext { 078 079 private Settings settings; 080 private DefaultFileSystem fs; 081 private ActiveRules activeRules; 082 private InMemorySensorStorage sensorStorage; 083 private InputModule module; 084 private SonarQubeVersion sqVersion; 085 086 private SensorContextTester(Path moduleBaseDir) { 087 this.settings = new Settings(); 088 this.fs = new DefaultFileSystem(moduleBaseDir); 089 this.activeRules = new ActiveRulesBuilder().build(); 090 this.sensorStorage = new InMemorySensorStorage(); 091 this.module = new DefaultInputModule("projectKey"); 092 this.sqVersion = SonarQubeVersionFactory.create(System2.INSTANCE); 093 } 094 095 public static SensorContextTester create(File moduleBaseDir) { 096 return new SensorContextTester(moduleBaseDir.toPath()); 097 } 098 099 public static SensorContextTester create(Path moduleBaseDir) { 100 return new SensorContextTester(moduleBaseDir); 101 } 102 103 @Override 104 public Settings settings() { 105 return settings; 106 } 107 108 public SensorContextTester setSettings(Settings settings) { 109 this.settings = settings; 110 return this; 111 } 112 113 @Override 114 public DefaultFileSystem fileSystem() { 115 return fs; 116 } 117 118 public SensorContextTester setFileSystem(DefaultFileSystem fs) { 119 this.fs = fs; 120 return this; 121 } 122 123 @Override 124 public ActiveRules activeRules() { 125 return activeRules; 126 } 127 128 public SensorContextTester setActiveRules(ActiveRules activeRules) { 129 this.activeRules = activeRules; 130 return this; 131 } 132 133 /** 134 * Default value is the version of this API. You can override it 135 * using {@link #setSonarQubeVersion(Version)} to test your Sensor behavior. 136 * @since 5.5 137 */ 138 @Override 139 public Version getSonarQubeVersion() { 140 return sqVersion.get(); 141 } 142 143 /** 144 * @since 5.5 145 */ 146 public SensorContextTester setSonarQubeVersion(Version version) { 147 this.sqVersion = new SonarQubeVersion(version); 148 return this; 149 } 150 151 @Override 152 public InputModule module() { 153 return module; 154 } 155 156 @Override 157 public <G extends Serializable> NewMeasure<G> newMeasure() { 158 return new DefaultMeasure<>(sensorStorage); 159 } 160 161 public Collection<Measure> measures(String componentKey) { 162 return sensorStorage.measuresByComponentAndMetric.row(componentKey).values(); 163 } 164 165 public <G extends Serializable> Measure<G> measure(String componetKey, Metric<G> metric) { 166 return measure(componetKey, metric.key()); 167 } 168 169 public <G extends Serializable> Measure<G> measure(String componentKey, String metricKey) { 170 return sensorStorage.measuresByComponentAndMetric.row(componentKey).get(metricKey); 171 } 172 173 @Override 174 public NewIssue newIssue() { 175 return new DefaultIssue(sensorStorage); 176 } 177 178 public Collection<Issue> allIssues() { 179 return sensorStorage.allIssues; 180 } 181 182 @CheckForNull 183 public Integer lineHits(String fileKey, CoverageType type, int line) { 184 Map<CoverageType, DefaultCoverage> defaultCoverageByType = sensorStorage.coverageByComponent.get(fileKey); 185 if (defaultCoverageByType == null) { 186 return null; 187 } 188 if (defaultCoverageByType.containsKey(type)) { 189 return defaultCoverageByType.get(type).hitsByLine().get(line); 190 } 191 return null; 192 } 193 194 @CheckForNull 195 public Integer conditions(String fileKey, CoverageType type, int line) { 196 Map<CoverageType, DefaultCoverage> defaultCoverageByType = sensorStorage.coverageByComponent.get(fileKey); 197 if (defaultCoverageByType == null) { 198 return null; 199 } 200 if (defaultCoverageByType.containsKey(type)) { 201 return defaultCoverageByType.get(type).conditionsByLine().get(line); 202 } 203 return null; 204 } 205 206 @CheckForNull 207 public Integer coveredConditions(String fileKey, CoverageType type, int line) { 208 Map<CoverageType, DefaultCoverage> defaultCoverageByType = sensorStorage.coverageByComponent.get(fileKey); 209 if (defaultCoverageByType == null) { 210 return null; 211 } 212 if (defaultCoverageByType.containsKey(type)) { 213 return defaultCoverageByType.get(type).coveredConditionsByLine().get(line); 214 } 215 return null; 216 } 217 218 @CheckForNull 219 public List<TokensLine> cpdTokens(String componentKey) { 220 DefaultCpdTokens defaultCpdTokens = sensorStorage.cpdTokensByComponent.get(componentKey); 221 return defaultCpdTokens != null ? defaultCpdTokens.getTokenLines() : null; 222 } 223 224 @Override 225 public NewHighlighting newHighlighting() { 226 return new DefaultHighlighting(sensorStorage); 227 } 228 229 @Override 230 public NewCoverage newCoverage() { 231 return new DefaultCoverage(sensorStorage); 232 } 233 234 @Override 235 public NewCpdTokens newCpdTokens() { 236 return new DefaultCpdTokens(settings, sensorStorage); 237 } 238 239 public List<TypeOfText> highlightingTypeAt(String componentKey, int line, int lineOffset) { 240 DefaultHighlighting syntaxHighlightingData = sensorStorage.highlightingByComponent.get(componentKey); 241 if (syntaxHighlightingData == null) { 242 return Collections.emptyList(); 243 } 244 List<TypeOfText> result = new ArrayList<>(); 245 DefaultTextPointer location = new DefaultTextPointer(line, lineOffset); 246 for (SyntaxHighlightingRule sortedRule : syntaxHighlightingData.getSyntaxHighlightingRuleSet()) { 247 if (sortedRule.range().start().compareTo(location) <= 0 && sortedRule.range().end().compareTo(location) > 0) { 248 result.add(sortedRule.getTextType()); 249 } 250 } 251 return result; 252 } 253 254 public static class MockAnalysisMode implements AnalysisMode { 255 private boolean isPreview = false; 256 private boolean isIssues = false; 257 258 @Override 259 public boolean isPreview() { 260 return isPreview; 261 } 262 263 public void setPreview(boolean value) { 264 this.isPreview = value; 265 } 266 267 @Override 268 public boolean isIssues() { 269 return this.isIssues; 270 } 271 272 public void setIssues(boolean issues) { 273 this.isIssues = issues; 274 } 275 276 @Override 277 public boolean isPublish() { 278 return !isPreview && !isIssues; 279 } 280 } 281 282 private static class InMemorySensorStorage implements SensorStorage { 283 284 private Table<String, String, Measure> measuresByComponentAndMetric = HashBasedTable.create(); 285 286 private Collection<Issue> allIssues = new ArrayList<>(); 287 288 private Map<String, DefaultHighlighting> highlightingByComponent = new HashMap<>(); 289 private Map<String, DefaultCpdTokens> cpdTokensByComponent = new HashMap<>(); 290 private Map<String, Map<CoverageType, DefaultCoverage>> coverageByComponent = new HashMap<>(); 291 292 @Override 293 public void store(Measure measure) { 294 measuresByComponentAndMetric.row(measure.inputComponent().key()).put(measure.metric().key(), measure); 295 } 296 297 @Override 298 public void store(Issue issue) { 299 allIssues.add(issue); 300 } 301 302 @Override 303 public void store(DefaultHighlighting highlighting) { 304 highlightingByComponent.put(highlighting.inputFile().key(), highlighting); 305 } 306 307 @Override 308 public void store(DefaultCoverage defaultCoverage) { 309 String key = defaultCoverage.inputFile().key(); 310 if (!coverageByComponent.containsKey(key)) { 311 coverageByComponent.put(key, new EnumMap<CoverageType, DefaultCoverage>(CoverageType.class)); 312 } 313 coverageByComponent.get(key).put(defaultCoverage.type(), defaultCoverage); 314 } 315 316 @Override 317 public void store(DefaultCpdTokens defaultCpdTokens) { 318 cpdTokensByComponent.put(defaultCpdTokens.inputFile().key(), defaultCpdTokens); 319 } 320 321 } 322 323}