diff --git a/executeMvn.bat b/executeMvn.bat index ebf7d50..4d58664 100644 --- a/executeMvn.bat +++ b/executeMvn.bat @@ -1,3 +1,3 @@ rem -Denforcer.skip=true -mvn -e -fn clean package com.hello2morrow.sonargraph:maven-sonargraph-plugin:7.2.3:architect-report -Dsonargraph.file=./sonar-sonargraph-plugin.sonargraph -Dsonargraph.prepareForSonar=true sonar:sonar -Dsonar.exclusions=**/generated-sources/** \ No newline at end of file +mvnDebug -e -fn clean package com.hello2morrow.sonargraph:maven-sonargraph-plugin:7.2.3:architect-report -Dsonargraph.file=./sonar-sonargraph-plugin.sonargraph -Dsonargraph.prepareForSonar=true sonar:sonar -Dsonar.exclusions=**/generated-sources/** \ No newline at end of file diff --git a/src/main/java/com/hello2morrow/sonarplugin/api/SonargraphSensor.java b/src/main/java/com/hello2morrow/sonarplugin/api/SonargraphSensor.java index aece0d3..3189688 100644 --- a/src/main/java/com/hello2morrow/sonarplugin/api/SonargraphSensor.java +++ b/src/main/java/com/hello2morrow/sonarplugin/api/SonargraphSensor.java @@ -78,7 +78,7 @@ public String toString() { return "Sonar-Sonargraph-Plugin [" + PluginVersionReader.INSTANCE.getVersion() + "]"; } - private boolean isValidProject(final Project project, final SensorContext sensorContext) { + boolean isValidProject(final Project project, final SensorContext sensorContext) { if (project == null || sensorContext == null) { LOG.error("Major error calling Sonargraph Sonar Plugin: Project and / or sensorContext are null. " + "Please check your project configuration!"); return false; @@ -103,6 +103,7 @@ private boolean isValidProject(final Project project, final SensorContext sensor return false; } + sensorContext.saveMeasure(SonargraphInternalMetrics.ROOT_PROJECT_TO_BE_PROCESSED, SonarQubeUtilities.FALSE); return true; } @@ -117,8 +118,6 @@ public void analyse(final Project project, final SensorContext sensorContext) { } LOG.info("Sonargraph: Execute for module " + project.getName() + " [" + project.getKey() + "]"); - sensorContext.saveMeasure(SonargraphInternalMetrics.ROOT_PROJECT_TO_BE_PROCESSED, SonarQubeUtilities.FALSE); - final IReportReader reportReader = new ReportFileReader(); reportReader.readSonargraphReport(project, sensorContext.fileSystem(), settings); if (PersistenceUtilities.getSonargraphBasePath(reportReader.getReport()) == null) { diff --git a/src/main/java/com/hello2morrow/sonarplugin/measurecomputer/SonargraphAggregatedMeasureComputer.java b/src/main/java/com/hello2morrow/sonarplugin/measurecomputer/SonargraphAggregatedMeasureComputer.java index 315f45f..3c8d746 100644 --- a/src/main/java/com/hello2morrow/sonarplugin/measurecomputer/SonargraphAggregatedMeasureComputer.java +++ b/src/main/java/com/hello2morrow/sonarplugin/measurecomputer/SonargraphAggregatedMeasureComputer.java @@ -83,11 +83,14 @@ void internalCompute(final MeasureComputerContext context) { } int value = 0; + boolean valuesFound = false; for (final Iterator iterator = iterable.iterator(); iterator.hasNext();) { value += iterator.next().getIntValue(); + valuesFound = true; + } + if (valuesFound) { + context.addMeasure(nextKey, value); } - - context.addMeasure(nextKey, value); } } } diff --git a/src/main/java/com/hello2morrow/sonarplugin/measurecomputer/SonargraphDerivedMeasureComputer.java b/src/main/java/com/hello2morrow/sonarplugin/measurecomputer/SonargraphDerivedMeasureComputer.java index 72782e5..9426302 100644 --- a/src/main/java/com/hello2morrow/sonarplugin/measurecomputer/SonargraphDerivedMeasureComputer.java +++ b/src/main/java/com/hello2morrow/sonarplugin/measurecomputer/SonargraphDerivedMeasureComputer.java @@ -22,8 +22,6 @@ import com.hello2morrow.sonarplugin.metric.SonargraphSimpleMetrics; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.sonar.api.ce.measure.Component; -import org.sonar.api.ce.measure.Component.Type; import org.sonar.api.ce.measure.Measure; import org.sonar.api.measures.Metric; @@ -54,12 +52,6 @@ List getOutputMetrics() { return SonarQubeUtilities.convertMetricListToKeyList(metrics); } - @Override - boolean needsProcessing(final MeasureComputerContext context) { - final Type type = context.getComponent().getType(); - return type == Component.Type.PROJECT || type == Component.Type.MODULE; - } - @Override void internalCompute(final MeasureComputerContext context) { saveViolationMeasures(context); diff --git a/src/main/java/com/hello2morrow/sonarplugin/measurecomputer/SonargraphMeasureComputer.java b/src/main/java/com/hello2morrow/sonarplugin/measurecomputer/SonargraphMeasureComputer.java index f76f69f..0693516 100644 --- a/src/main/java/com/hello2morrow/sonarplugin/measurecomputer/SonargraphMeasureComputer.java +++ b/src/main/java/com/hello2morrow/sonarplugin/measurecomputer/SonargraphMeasureComputer.java @@ -18,6 +18,8 @@ package com.hello2morrow.sonarplugin.measurecomputer; import com.hello2morrow.sonarplugin.metric.internal.SonargraphInternalMetrics; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.sonar.api.ce.measure.Component; import org.sonar.api.ce.measure.Measure; import org.sonar.api.ce.measure.MeasureComputer; @@ -27,6 +29,8 @@ abstract class SonargraphMeasureComputer implements MeasureComputer { + private static final Logger LOGGER = LoggerFactory.getLogger(SonargraphMeasureComputer.class); + public SonargraphMeasureComputer() { super(); } @@ -51,15 +55,19 @@ public final void compute(final MeasureComputerContext context) { internalCompute(context); } + protected final boolean needsProcessing(final MeasureComputerContext context) { + final Measure measure = context.getMeasure(SonargraphInternalMetrics.ROOT_PROJECT_TO_BE_PROCESSED.key()); + final boolean needsProcessing = measure != null && measure.getBooleanValue(); + if (!needsProcessing) { + LOGGER.error("Not processing for module: " + context.getComponent().getKey()); + } + return needsProcessing; + } + abstract void internalCompute(MeasureComputerContext context); abstract List getOutputMetrics(); abstract List getInputMetrics(); - boolean needsProcessing(final MeasureComputerContext context) { - final Measure measure = context.getMeasure(SonargraphInternalMetrics.ROOT_PROJECT_TO_BE_PROCESSED.key()); - return measure != null && measure.getBooleanValue(); - } - } diff --git a/src/main/java/com/hello2morrow/sonarplugin/measurecomputer/SonargraphTransferInternalMeasureComputer.java b/src/main/java/com/hello2morrow/sonarplugin/measurecomputer/SonargraphTransferInternalMeasureComputer.java index 38135dc..fe6443c 100644 --- a/src/main/java/com/hello2morrow/sonarplugin/measurecomputer/SonargraphTransferInternalMeasureComputer.java +++ b/src/main/java/com/hello2morrow/sonarplugin/measurecomputer/SonargraphTransferInternalMeasureComputer.java @@ -64,9 +64,9 @@ private void transferMetricValue(final MeasureComputerContext context, final Met @Override List getInputMetrics() { - return SonarQubeUtilities.convertMetricListToKeyList(Arrays.asList(SonargraphInternalMetrics.SYSTEM_ALL_WARNINGS, SonargraphInternalMetrics.SYSTEM_CYCLE_WARNINGS, - SonargraphInternalMetrics.SYSTEM_THRESHOLD_WARNINGS, SonargraphInternalMetrics.SYSTEM_WORKSPACE_WARNINGS, SonargraphInternalMetrics.SYSTEM_IGNORED_WARNINGS, - SonargraphInternalMetrics.SYSTEM_ALL_TASKS)); + return SonarQubeUtilities.convertMetricListToKeyList(Arrays.asList(SonargraphInternalMetrics.MODULE_PROCESSED_BY_SENSOR, SonargraphInternalMetrics.SYSTEM_ALL_WARNINGS, + SonargraphInternalMetrics.SYSTEM_CYCLE_WARNINGS, SonargraphInternalMetrics.SYSTEM_THRESHOLD_WARNINGS, SonargraphInternalMetrics.SYSTEM_WORKSPACE_WARNINGS, + SonargraphInternalMetrics.SYSTEM_IGNORED_WARNINGS, SonargraphInternalMetrics.SYSTEM_ALL_TASKS)); } @Override diff --git a/src/main/java/com/hello2morrow/sonarplugin/persistence/PersistenceUtilities.java b/src/main/java/com/hello2morrow/sonarplugin/persistence/PersistenceUtilities.java index b64ab25..9663a36 100644 --- a/src/main/java/com/hello2morrow/sonarplugin/persistence/PersistenceUtilities.java +++ b/src/main/java/com/hello2morrow/sonarplugin/persistence/PersistenceUtilities.java @@ -74,7 +74,7 @@ public static String getBuildUnitName(final XsdCycleGroup group) { final String buildUnitName = group.getElementScope(); // special handling for reports produced with free SonarQube license or without Sonargraph system file - if ("My Project".equals(buildUnitName) && group.getParent() != null) { + if (DEFAULT_PROJECT_NAME.equals(buildUnitName) && group.getParent() != null) { return group.getParent(); } diff --git a/src/main/java/com/hello2morrow/sonarplugin/persistence/ReportFileReader.java b/src/main/java/com/hello2morrow/sonarplugin/persistence/ReportFileReader.java index 11f1604..49bbbff 100644 --- a/src/main/java/com/hello2morrow/sonarplugin/persistence/ReportFileReader.java +++ b/src/main/java/com/hello2morrow/sonarplugin/persistence/ReportFileReader.java @@ -53,13 +53,13 @@ public class ReportFileReader implements IReportReader { private ReportContext report; @Override - public void readSonargraphReport(final Project project, final FileSystem moduleFileSystem, final Settings settings) { + public void readSonargraphReport(final Project project, final FileSystem fileSystem, final Settings settings) { if (project == null) { LOG.error("No project provided for reading sonargraph report"); return; } - final String reportFileName = determineReportFileName(moduleFileSystem, settings); + final String reportFileName = determineReportFileName(fileSystem, settings); LOG.info("Reading Sonargraph metrics report from: " + reportFileName); report = null; InputStream input = null; @@ -107,15 +107,15 @@ public ReportContext getReport() { return report; } - private static String determineReportFileName(final FileSystem moduleFileSystem, final Settings settings) { + private static String determineReportFileName(final FileSystem fileSystem, final Settings settings) { final String configuredReportPath = SonargraphUtilities.getConfiguredReportPath(settings); - if (moduleFileSystem == null) { + if (fileSystem == null) { return configuredReportPath; } if (configuredReportPath == null || configuredReportPath.length() == 0) { - return moduleFileSystem.workDir().getParentFile().getPath() + '/' + REPORT_DIR + '/' + REPORT_NAME; + return fileSystem.workDir().getParentFile().getPath() + '/' + REPORT_DIR + '/' + REPORT_NAME; } return configuredReportPath; diff --git a/src/test/AlarmClockMain/pom.xml b/src/test/AlarmClockMain/pom.xml index c42584e..1da8d69 100644 --- a/src/test/AlarmClockMain/pom.xml +++ b/src/test/AlarmClockMain/pom.xml @@ -5,7 +5,7 @@ AlarmClockMain 0.0.1-SNAPSHOT pom - AlarmClockMain + AlarmClockMainSg7 Project aggregating the different modules diff --git a/src/test/java/com/hello2morrow/sonarplugin/api/AbstractSonargraphTest.java b/src/test/java/com/hello2morrow/sonarplugin/api/AbstractSonargraphTest.java deleted file mode 100644 index 36afbd8..0000000 --- a/src/test/java/com/hello2morrow/sonarplugin/api/AbstractSonargraphTest.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Sonar Sonargraph Plugin - * Copyright (C) 2009, 2010, 2011 hello2morrow GmbH - * mailto: info AT hello2morrow DOT com - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hello2morrow.sonarplugin.api; - -import com.hello2morrow.sonarplugin.foundation.SonargraphPluginBase; -import com.hello2morrow.sonarplugin.foundation.TestHelper; -import org.junit.Before; -import org.sonar.api.batch.SensorContext; -import org.sonar.api.batch.fs.FileSystem; -import org.sonar.api.config.Settings; -import org.sonar.api.profiles.RulesProfile; -import org.sonar.api.resources.Project; - -import java.io.File; - -public abstract class AbstractSonargraphTest { - - private RulesProfile rulesProfile; - private SensorContext sensorContext; - private FileSystem moduleFileSystem; - private Settings settings; - - public AbstractSonargraphTest() { - super(); - } - - @Before - public void setup() { - rulesProfile = TestHelper.initRulesProfile(); - moduleFileSystem = TestHelper.initModuleFileSystem(); - sensorContext = TestHelper.initSensorContext(moduleFileSystem); - settings = TestHelper.initSettings(); - settings.setProperty(SonargraphPluginBase.REPORT_PATH, getReport()); - } - - protected RulesProfile getRulesProfile() { - return rulesProfile; - } - - protected SensorContext getSensorContext() { - return sensorContext; - } - - protected FileSystem getModuleFileSystem() { - return moduleFileSystem; - } - - protected Settings getSettings() { - return settings; - } - - protected abstract String getReport(); - - protected void initProjectFileSystem(File baseDir, Project project) { - - } -} diff --git a/src/test/java/com/hello2morrow/sonarplugin/api/ReadTest.java b/src/test/java/com/hello2morrow/sonarplugin/api/ReadTest.java index daa7001..d7964d3 100644 --- a/src/test/java/com/hello2morrow/sonarplugin/api/ReadTest.java +++ b/src/test/java/com/hello2morrow/sonarplugin/api/ReadTest.java @@ -18,7 +18,6 @@ package com.hello2morrow.sonarplugin.api; -import com.hello2morrow.sonarplugin.foundation.SonargraphPluginBase; import com.hello2morrow.sonarplugin.foundation.TestHelper; import com.hello2morrow.sonarplugin.persistence.IReportReader; import com.hello2morrow.sonarplugin.persistence.ReportFileReader; @@ -33,8 +32,7 @@ public class ReadTest extends TestCase { @Test public void testAnalyse() { final Project project1 = new Project("test"); - final Settings settings = TestHelper.initSettings(); - settings.setProperty(SonargraphPluginBase.REPORT_PATH, "src/test/resources/infoglue21-report.xml"); + final Settings settings = TestHelper.initSettings("src/test/resources/infoglue21-report.xml"); final IReportReader reader = new ReportFileReader(); reader.readSonargraphReport(project1, null, settings); diff --git a/src/test/java/com/hello2morrow/sonarplugin/api/SonargraphPluginTest.java b/src/test/java/com/hello2morrow/sonarplugin/api/SonargraphPluginTest.java index a1e6a82..7680db0 100644 --- a/src/test/java/com/hello2morrow/sonarplugin/api/SonargraphPluginTest.java +++ b/src/test/java/com/hello2morrow/sonarplugin/api/SonargraphPluginTest.java @@ -33,10 +33,9 @@ public class SonargraphPluginTest { /** * Test method for {@link com.hello2morrow.sonarplugin.api.SonargraphPlugin#getExtensions()}. */ - @SuppressWarnings("deprecation") @Test public void testGetExtensions() { - SonarPlugin plugin = new SonargraphPlugin(); + final SonarPlugin plugin = new SonargraphPlugin(); Assert.assertNotNull(plugin.getExtensions()); Assert.assertTrue(plugin.getExtensions().size() > 0); } diff --git a/src/test/java/com/hello2morrow/sonarplugin/api/SonargraphSensorTest.java b/src/test/java/com/hello2morrow/sonarplugin/api/SonargraphSensorTest.java index 57884c7..6288a6a 100644 --- a/src/test/java/com/hello2morrow/sonarplugin/api/SonargraphSensorTest.java +++ b/src/test/java/com/hello2morrow/sonarplugin/api/SonargraphSensorTest.java @@ -17,81 +17,249 @@ */ package com.hello2morrow.sonarplugin.api; -import com.hello2morrow.sonarplugin.metric.SonargraphSimpleMetrics; -import org.junit.Before; +import com.hello2morrow.sonarplugin.foundation.InMemorySensorStorage; +import com.hello2morrow.sonarplugin.foundation.TestHelper; import org.junit.Test; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; import org.sonar.api.batch.SensorContext; import org.sonar.api.batch.fs.FilePredicate; import org.sonar.api.batch.fs.FileSystem; +import org.sonar.api.batch.fs.InputComponent; +import org.sonar.api.batch.fs.InputDir; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.rule.Severity; +import org.sonar.api.batch.sensor.internal.SensorStorage; +import org.sonar.api.batch.sensor.issue.Issue; +import org.sonar.api.batch.sensor.issue.IssueLocation; +import org.sonar.api.batch.sensor.issue.NewIssue; +import org.sonar.api.batch.sensor.issue.NewIssueLocation; +import org.sonar.api.config.Settings; import org.sonar.api.resources.Project; import org.sonar.api.resources.Qualifiers; +import org.sonar.api.rule.RuleKey; import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.Arrays; +import java.util.Collections; +import java.util.List; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.Matchers.any; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -public class SonargraphSensorTest extends AbstractSonargraphTest { +public class SonargraphSensorTest { - public static final String REPORT = "src/test/resources/sonargraph-sonar-report.xml"; - private SonargraphSensor sensor; + private static final String REPORT = "src/test/resources/sonargraph-sonar-report.xml"; - @Override - protected String getReport() { - return REPORT; - } + @Test + public void testShouldExecuteOnProject() { + final SonargraphSensor sensor = new SonargraphSensor(TestHelper.initSettings(REPORT)); + final Project project = new Project("hello2morrow:AlarmClock", "", "AlarmClock"); + assertTrue(sensor.shouldExecuteOnProject(project)); - @Before - public void initSensor() { - sensor = new SonargraphSensor(getSettings()); + final Project module = new Project("hello2morrow:Foundation", "", "Foundation"); + module.setParent(project); + assertTrue(sensor.shouldExecuteOnProject(project)); + assertTrue(sensor.shouldExecuteOnProject(module)); } @Test - public void testAnalyseRootParentProject() { - final Project rootProject = new Project("hello2morrow:AlarmClock"); - final Project module = new Project("module"); - module.setParent(rootProject); - final SensorContext context = mock(SensorContext.class); - when(context.getMeasure(SonargraphSimpleMetrics.INTERNAL_PACKAGES)).thenReturn(null); - - sensor.analyse(null, context); - assertNull(context.getMeasure(SonargraphSimpleMetrics.INTERNAL_PACKAGES)); + public void testIsValidProject() { + final SonargraphSensor sensor = new SonargraphSensor(TestHelper.initSettings(null)); + assertFalse(sensor.isValidProject(null, null)); + final Project project = mock(Project.class); + assertFalse(sensor.isValidProject(project, null)); + + final Project module = new Project("hello2morrow:Foundation", "", "Foundation"); + when(project.isRoot()).thenReturn(Boolean.TRUE); + when(project.getModules()).thenReturn(Arrays.asList(module)); + + final SensorContext context = TestHelper.initSensorContext(TestHelper.initFileSystem(), mock(SensorStorage.class)); + assertFalse(sensor.isValidProject(project, context)); + + assertFalse(sensor.isValidProject(module, context)); } @Test - public void testAnalyse() { - final Project project = mock(Project.class); // ("hello2morrow:AlarmClock", "", "AlarmClock"); + public void testAnalyse() throws IOException { + final Project project = mock(Project.class); doReturn("hello2morrow:AlarmClock").when(project).key(); doReturn("AlarmClock").when(project).name(); doReturn(Qualifiers.MODULE).when(project).getQualifier(); - final FileSystem projectFileSystem = mock(FileSystem.class); - final File baseDir = new File("src/test/resources"); - when(getModuleFileSystem().baseDir()).thenReturn(baseDir); - final File sourceFile = new File(baseDir, "com/hello2morrow/sonarplugin/Test.java"); - when(projectFileSystem.baseDir()).thenReturn(baseDir); + final File root = new File(".").getCanonicalFile(); + final File baseDir = new File(root, "src/test/AlarmClockMain"); + + final FileSystem fileSystem = mock(FileSystem.class); + when(fileSystem.baseDir()).thenReturn(baseDir); - when(getModuleFileSystem().files(any(FilePredicate.class))).thenReturn(Arrays.asList(sourceFile)); + final InMemorySensorStorage sensorStorage = new InMemorySensorStorage(); + final SensorContext sensorContext = TestHelper.initSensorContext(fileSystem, sensorStorage); + final Settings settings = TestHelper.initSettings(TestHelper.REPORT_PATH2); + final SonargraphSensor sensor = new SonargraphSensor(settings); - sensor.analyse(project, getSensorContext()); - final double value = getSensorContext().getMeasure(SonargraphSimpleMetrics.WORKSPACE_WARNINGS).getValue().doubleValue(); - assertEquals(0.0, value, 0.01); + when(fileSystem.inputFile(any(FilePredicate.class))).thenAnswer(new Answer() { + @Override + public InputFile answer(final InvocationOnMock invocation) throws Throwable { + final Object arg = invocation.getArguments()[0]; + final FilePredicate predicate = (FilePredicate) arg; + return findFile(baseDir, baseDir, predicate); + } + }); + when(fileSystem.inputDir(any(File.class))).thenAnswer(new Answer() { + @Override + public InputDir answer(final InvocationOnMock invocation) throws Throwable { + final Object arg = invocation.getArguments()[0]; + final File dir = (File) arg; + final InputDir mocked = mock(InputDir.class); + final String canonicalPath = dir.getCanonicalPath(); + when(mocked.absolutePath()).thenReturn(canonicalPath); + when(mocked.file()).thenReturn(dir); + when(mocked.relativePath()).thenReturn(calculateRelativePath(baseDir, canonicalPath).toString()); + return mocked; + } + }); + + when(sensorContext.newIssue()).thenAnswer(new Answer() { + @Override + public Issue answer(final InvocationOnMock invocation) throws Throwable { + return new TestIssue(sensorStorage); + } + }); + + sensor.analyse(project, sensorContext); + assertEquals("Wrong number of issue rules", 5, sensorStorage.getIssues().keySet().size()); + assertEquals("Wrong total number of issues", 31, sensorStorage.getNumberOfIssues()); } - @Test - public void testShouldExecuteOnProject() { - final Project project = new Project("hello2morrow:AlarmClock", "", "AlarmClock"); - assertTrue(sensor.shouldExecuteOnProject(project)); + private InputFile findFile(final File baseDir, final File currentDir, final FilePredicate predicate) throws IOException { + for (final File next : currentDir.listFiles()) { + if (next.isDirectory()) { + final InputFile match = findFile(baseDir, next, predicate); + if (match != null) { + return match; + } + } + final InputFile mocked = mock(InputFile.class); + final String canonicalPath = next.getCanonicalPath(); + final Path pathRelative = calculateRelativePath(baseDir, canonicalPath); + when(mocked.absolutePath()).thenReturn(canonicalPath.replace("\\", "/")); + when(mocked.relativePath()).thenReturn(pathRelative.toString().replace("\\", "/")); + when(mocked.file()).thenReturn(next); + when(mocked.isFile()).thenReturn(true); + if (predicate.apply(mocked)) { + return mocked; + } + } + return null; + } - final Project module = new Project("hello2morrow:Foundation", "", "Foundation"); - module.setParent(project); - assertTrue(sensor.shouldExecuteOnProject(project)); - assertTrue(sensor.shouldExecuteOnProject(module)); + private Path calculateRelativePath(final File baseDir, final String canonicalPath) throws IOException { + final Path pathAbsolute = Paths.get(canonicalPath); + final Path pathBase = Paths.get(baseDir.getCanonicalPath()); + final Path pathRelative = pathBase.relativize(pathAbsolute); + return pathRelative; + } + + private class TestIssue implements NewIssue, Issue { + + private IssueLocation issueLocation; + private Double effort; + private Severity severity; + private NewIssueLocation location; + private final SensorStorage storage; + private RuleKey rule; + + public TestIssue(final InMemorySensorStorage storage) { + this.storage = storage; + } + + @Override + public RuleKey ruleKey() { + return rule; + } + + @Override + public Double effortToFix() { + return effort; + } + + @Override + public Severity overriddenSeverity() { + return severity; + } + + @Override + public IssueLocation primaryLocation() { + return issueLocation; + } + + @Override + public List flows() { + return Collections.emptyList(); + } + + @Override + public NewIssue forRule(final RuleKey ruleKey) { + this.rule = ruleKey; + return this; + } + + @Override + public NewIssue effortToFix(final Double effortToFix) { + effort = effortToFix; + return this; + } + + @Override + public NewIssue overrideSeverity(final Severity severity) { + this.severity = severity; + return this; + } + + @Override + public NewIssue at(final NewIssueLocation primaryLocation) { + this.location = primaryLocation; + return this; + } + + @Override + public NewIssue addLocation(final NewIssueLocation secondaryLocation) { + assert false : "Not to be used"; + return this; + } + + @Override + public NewIssue addFlow(final Iterable flowLocations) { + assert false : "Not to be used"; + return this; + } + + @Override + public NewIssueLocation newLocation() { + final NewIssueLocation location = mock(NewIssueLocation.class); + + when(location.on(any(InputComponent.class))).thenReturn(location); + when(location.message(any(String.class))).thenReturn(location); + return location; + } + + @Override + public void save() { + storage.store(this); + } + + @Override + public String toString() { + return "Issue for rule: " + ruleKey(); + } } } diff --git a/src/test/java/com/hello2morrow/sonarplugin/foundation/InMemorySensorStorage.java b/src/test/java/com/hello2morrow/sonarplugin/foundation/InMemorySensorStorage.java new file mode 100644 index 0000000..721549e --- /dev/null +++ b/src/test/java/com/hello2morrow/sonarplugin/foundation/InMemorySensorStorage.java @@ -0,0 +1,94 @@ +/* + * Sonar Sonargraph Plugin + * Copyright (C) 2009, 2010, 2011 hello2morrow GmbH + * mailto: info AT hello2morrow DOT com + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.hello2morrow.sonarplugin.foundation; + +import org.sonar.api.batch.sensor.coverage.CoverageType; +import org.sonar.api.batch.sensor.coverage.internal.DefaultCoverage; +import org.sonar.api.batch.sensor.duplication.Duplication; +import org.sonar.api.batch.sensor.highlighting.internal.DefaultHighlighting; +import org.sonar.api.batch.sensor.internal.SensorStorage; +import org.sonar.api.batch.sensor.issue.Issue; +import org.sonar.api.batch.sensor.measure.Measure; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.EnumMap; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class InMemorySensorStorage implements SensorStorage { + + private final Map> measures = new HashMap<>(); + private final Map> allIssues = new HashMap<>(); + private final Map highlightingByComponent = new HashMap<>(); + private final Map> coverageByComponent = new HashMap<>(); + + private final List duplications = new ArrayList<>(); + + private int issueCount = 0; + + @SuppressWarnings({"rawtypes", "unchecked"}) + @Override + public void store(final Measure measure) { + measures.put(measure.metric().key(), measure); + } + + @Override + public void store(final Issue issue) { + List issues = allIssues.get(issue.ruleKey().toString()); + if (issues == null) { + issues = new ArrayList<>(); + allIssues.put(issue.ruleKey().toString(), issues); + } + issues.add(issue); + issueCount++; + } + + @Override + public void store(final Duplication duplication) { + duplications.add(duplication); + } + + @Override + public void store(final DefaultHighlighting highlighting) { + highlightingByComponent.put(highlighting.inputFile().key(), highlighting); + } + + @Override + public void store(final DefaultCoverage defaultCoverage) { + final String key = defaultCoverage.inputFile().key(); + if (!coverageByComponent.containsKey(key)) { + coverageByComponent.put(key, new EnumMap(CoverageType.class)); + } + coverageByComponent.get(key).put(defaultCoverage.type(), defaultCoverage); + } + + public Measure getMeasure(final String key) { + return measures.get(key); + } + + public Map> getIssues() { + return Collections.unmodifiableMap(allIssues); + } + + public int getNumberOfIssues() { + return issueCount; + } +} diff --git a/src/test/java/com/hello2morrow/sonarplugin/foundation/ReportFileReaderTest.java b/src/test/java/com/hello2morrow/sonarplugin/foundation/ReportFileReaderTest.java index 321aae1..056b4c9 100644 --- a/src/test/java/com/hello2morrow/sonarplugin/foundation/ReportFileReaderTest.java +++ b/src/test/java/com/hello2morrow/sonarplugin/foundation/ReportFileReaderTest.java @@ -39,10 +39,9 @@ public class ReportFileReaderTest { @Test public void testReadSonargraphReport() { - Project project = new Project("test"); - IReportReader reader = new ReportFileReader(); - Settings settings = TestHelper.initSettings(); - settings.setProperty(SonargraphPluginBase.REPORT_PATH, reportFileName); + final Project project = new Project("test"); + final IReportReader reader = new ReportFileReader(); + final Settings settings = TestHelper.initSettings(reportFileName); reader.readSonargraphReport(project, null, settings); assertNotNull(reader.getReport()); @@ -54,12 +53,11 @@ public void testReadSonargraphReport() { reader.readSonargraphReport(project, null, settings); assertNull(reader.getReport()); - Project module = new Project("module"); + final Project module = new Project("module"); module.setParent(project); settings.setProperty(SonargraphPluginBase.REPORT_PATH, "fakeDir/ReporFileName.xml"); reader.readSonargraphReport(project, null, settings); assertNull(reader.getReport()); } - } diff --git a/src/test/java/com/hello2morrow/sonarplugin/foundation/TestHelper.java b/src/test/java/com/hello2morrow/sonarplugin/foundation/TestHelper.java index 304e672..1edd584 100644 --- a/src/test/java/com/hello2morrow/sonarplugin/foundation/TestHelper.java +++ b/src/test/java/com/hello2morrow/sonarplugin/foundation/TestHelper.java @@ -17,7 +17,6 @@ */ package com.hello2morrow.sonarplugin.foundation; -import com.hello2morrow.sonarplugin.api.SonargraphRulesRepository; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import org.sonar.api.batch.SensorContext; @@ -28,31 +27,16 @@ import org.sonar.api.batch.fs.InputFile.Status; import org.sonar.api.batch.fs.InputFile.Type; import org.sonar.api.batch.rule.internal.ActiveRulesBuilder; -import org.sonar.api.batch.sensor.coverage.CoverageType; -import org.sonar.api.batch.sensor.coverage.internal.DefaultCoverage; -import org.sonar.api.batch.sensor.duplication.Duplication; -import org.sonar.api.batch.sensor.highlighting.internal.DefaultHighlighting; import org.sonar.api.batch.sensor.internal.SensorStorage; -import org.sonar.api.batch.sensor.issue.Issue; import org.sonar.api.batch.sensor.measure.internal.DefaultMeasure; import org.sonar.api.config.Settings; -import org.sonar.api.measures.Measure; -import org.sonar.api.measures.Metric; -import org.sonar.api.profiles.RulesProfile; import org.sonar.api.resources.Resource; import org.sonar.api.rule.RuleKey; -import org.sonar.api.server.rule.RulesDefinition; -import org.sonar.api.server.rule.RulesDefinition.Repository; -import org.sonar.api.server.rule.RulesDefinition.Rule; import java.io.File; -import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; -import java.util.EnumMap; -import java.util.HashMap; import java.util.List; -import java.util.Map; import static org.mockito.Matchers.any; import static org.mockito.Mockito.mock; @@ -60,88 +44,30 @@ public class TestHelper { - private static class InMemorySensorStorage implements SensorStorage { - - private final Map> measures = new HashMap<>(); - - private final Collection allIssues = new ArrayList<>(); - - private final Map highlightingByComponent = new HashMap<>(); - private final Map> coverageByComponent = new HashMap<>(); - - private final List duplications = new ArrayList<>(); - - @Override - public void store(final org.sonar.api.batch.sensor.measure.Measure measure) { - measures.put(measure.metric().key(), measure); - } - - @Override - public void store(final Issue issue) { - allIssues.add(issue); - } - - @Override - public void store(final Duplication duplication) { - duplications.add(duplication); - } - - @Override - public void store(final DefaultHighlighting highlighting) { - highlightingByComponent.put(highlighting.inputFile().key(), highlighting); - } - - @Override - public void store(final DefaultCoverage defaultCoverage) { - final String key = defaultCoverage.inputFile().key(); - if (!coverageByComponent.containsKey(key)) { - coverageByComponent.put(key, new EnumMap(CoverageType.class)); - } - coverageByComponent.get(key).put(defaultCoverage.type(), defaultCoverage); - } - - } - - public static RulesProfile initRulesProfile() { - final RulesDefinition rulesDefinition = new SonargraphRulesRepository(); - final RulesDefinition.Context context = new RulesDefinition.Context(); - rulesDefinition.define(context); - - final Repository repository = context.repository(SonargraphPluginBase.PLUGIN_KEY); - final RulesProfile profile = RulesProfile.create(SonargraphPluginBase.PLUGIN_KEY, "JAVA"); - - activateRule(repository, profile, SonargraphPluginBase.TASK_RULE_KEY); - activateRule(repository, profile, SonargraphPluginBase.CYCLE_GROUP_RULE_KEY); - activateRule(repository, profile, SonargraphPluginBase.DUPLICATE_RULE_KEY); - activateRule(repository, profile, SonargraphPluginBase.ARCH_RULE_KEY); - activateRule(repository, profile, SonargraphPluginBase.THRESHOLD_RULE_KEY); - activateRule(repository, profile, SonargraphPluginBase.WORKSPACE_RULE_KEY); - return profile; - } - - private static void activateRule(final Repository repository, final RulesProfile profile, final String ruleKey) { - final Rule rule = repository.rule(ruleKey); - profile.activateRule(org.sonar.api.rules.Rule.create(repository.key(), rule.key(), rule.name()), null); - } + public static final String REPORT_PATH = "./src/test/resources/sonargraph-sonar-report.xml"; + public static final String REPORT_PATH2 = "./src/test/resources/sonargraph-sonar-report2.xml"; - public static Settings initSettings() { + public static Settings initSettings(final String reportPath) { final Settings settings = new Settings(); settings.setProperty(SonargraphPluginBase.COST_PER_INDEX_POINT, 7.0); + settings.setProperty(SonargraphPluginBase.REPORT_PATH, reportPath); return settings; } @SuppressWarnings("rawtypes") - public static SensorContext initSensorContext(final FileSystem moduleFileSystem) { - final InMemorySensorStorage sensorStorage = new InMemorySensorStorage(); + public static SensorContext initSensorContext(final FileSystem fileSystem, final SensorStorage sensorStorage) { final SensorContext sensorContext = mock(SensorContext.class); when(sensorContext.activeRules()).thenAnswer(new Answer() { - @Override public Object answer(final InvocationOnMock invocation) throws Throwable { final ActiveRulesBuilder builder = new ActiveRulesBuilder(); builder.create(RuleKey.of(SonargraphPluginBase.PLUGIN_KEY, SonargraphPluginBase.ARCH_RULE_KEY)).activate(); builder.create(RuleKey.of(SonargraphPluginBase.PLUGIN_KEY, SonargraphPluginBase.TASK_RULE_KEY)).activate(); + builder.create(RuleKey.of(SonargraphPluginBase.PLUGIN_KEY, SonargraphPluginBase.DUPLICATE_RULE_KEY)).activate(); + builder.create(RuleKey.of(SonargraphPluginBase.PLUGIN_KEY, SonargraphPluginBase.CYCLE_GROUP_RULE_KEY)).activate(); + builder.create(RuleKey.of(SonargraphPluginBase.PLUGIN_KEY, SonargraphPluginBase.THRESHOLD_RULE_KEY)).activate(); + builder.create(RuleKey.of(SonargraphPluginBase.PLUGIN_KEY, SonargraphPluginBase.WORKSPACE_RULE_KEY)).activate(); return builder.build(); } }); @@ -149,7 +75,7 @@ public Object answer(final InvocationOnMock invocation) throws Throwable { when(sensorContext.fileSystem()).thenAnswer(new Answer() { @Override public Object answer(final InvocationOnMock invocation) throws Throwable { - return moduleFileSystem; + return fileSystem; } }); @@ -168,21 +94,10 @@ public Object answer(final InvocationOnMock invocation) throws Throwable { } }); - when(sensorContext.getMeasure(any(Metric.class))).thenAnswer(new Answer() { - - @Override - public Object answer(final InvocationOnMock invocation) { - final Object arg = invocation.getArguments()[0]; - final Measure result = new Measure((Metric) arg); - result.setValue(0.0); - return result; - } - }); - return sensorContext; } - public static FileSystem initModuleFileSystem() { + public static FileSystem initFileSystem() { final FileSystem fileSystem = mock(FileSystem.class); when(fileSystem.hasFiles(any(FilePredicate.class))).thenAnswer(new Answer() { @@ -201,6 +116,8 @@ public List answer(final InvocationOnMock invocation) throws Throwable { } }); + when(fileSystem.workDir()).thenReturn(new File("./src/test").getAbsoluteFile()); + when(fileSystem.predicates()).thenAnswer(new Answer() { @Override public FilePredicates answer(final InvocationOnMock invocation) throws Throwable { @@ -325,7 +242,4 @@ public FilePredicate hasLanguages(final String... arg0) { }); return fileSystem; } - - public static final String REPORT_PATH = "./src/test/resources/sonargraph-sonar-report.xml"; - public static final String REPORT_PATH2 = "./src/test/resources/sonargraph-sonar-report2.xml"; } diff --git a/src/test/java/com/hello2morrow/sonarplugin/measurecomputer/SonargraphDerivedMeasureComputerTest.java b/src/test/java/com/hello2morrow/sonarplugin/measurecomputer/SonargraphDerivedMeasureComputerTest.java index 9c2d5a1..d3fc437 100644 --- a/src/test/java/com/hello2morrow/sonarplugin/measurecomputer/SonargraphDerivedMeasureComputerTest.java +++ b/src/test/java/com/hello2morrow/sonarplugin/measurecomputer/SonargraphDerivedMeasureComputerTest.java @@ -19,6 +19,7 @@ import com.hello2morrow.sonarplugin.metric.SonargraphDerivedMetrics; import com.hello2morrow.sonarplugin.metric.SonargraphSimpleMetrics; +import com.hello2morrow.sonarplugin.metric.internal.SonargraphInternalMetrics; import org.junit.Test; import org.sonar.api.ce.measure.Component; import org.sonar.api.ce.measure.Measure; @@ -48,7 +49,7 @@ public void testNeedsProcessing() { final Component project = mock(Component.class); when(project.getType()).thenReturn(Component.Type.PROJECT); - final MeasureComputerContext context = mock(MeasureComputerContext.class); + final MeasureComputerContext context = initMeasureComputerContext(); when(context.getComponent()).thenReturn(project); assertTrue(computer.needsProcessing(context)); @@ -56,7 +57,7 @@ public void testNeedsProcessing() { when(module.getType()).thenReturn(Component.Type.MODULE); final MeasureComputerContext context2 = mock(MeasureComputerContext.class); when(context2.getComponent()).thenReturn(module); - assertTrue(computer.needsProcessing(context2)); + assertFalse(computer.needsProcessing(context2)); final Component file = mock(Component.class); when(file.getType()).thenReturn(Component.Type.FILE); @@ -84,7 +85,7 @@ public void testComputedViolatingTypesPercent() { final Component project = mock(Component.class); when(project.getType()).thenReturn(Component.Type.PROJECT); - final MeasureComputerContext context = mock(MeasureComputerContext.class); + final MeasureComputerContext context = initMeasureComputerContext(); when(context.getComponent()).thenReturn(project); final Measure violatingTypes = mock(Measure.class); @@ -105,7 +106,7 @@ public void testUnassignedTypesPercent() { final Component project = mock(Component.class); when(project.getType()).thenReturn(Component.Type.PROJECT); - final MeasureComputerContext context = mock(MeasureComputerContext.class); + final MeasureComputerContext context = initMeasureComputerContext(); when(context.getComponent()).thenReturn(project); final Measure violatingTypes = mock(Measure.class); @@ -126,7 +127,7 @@ public void testCyclicityMeasures() { final Component project = mock(Component.class); when(project.getType()).thenReturn(Component.Type.PROJECT); - final MeasureComputerContext context = mock(MeasureComputerContext.class); + final MeasureComputerContext context = initMeasureComputerContext(); when(context.getComponent()).thenReturn(project); final Measure internalPackages = mock(Measure.class); @@ -146,4 +147,12 @@ public void testCyclicityMeasures() { verify(context).addMeasure(SonargraphDerivedMetrics.RELATIVE_CYCLICITY.key(), 4.0); verify(context).addMeasure(SonargraphDerivedMetrics.CYCLIC_PACKAGES_PERCENT.key(), 6.0); } + + public MeasureComputerContext initMeasureComputerContext() { + final MeasureComputerContext context = mock(MeasureComputerContext.class); + final Measure measure = mock(Measure.class); + when(measure.getBooleanValue()).thenReturn(Boolean.TRUE); + when(context.getMeasure(SonargraphInternalMetrics.ROOT_PROJECT_TO_BE_PROCESSED.key())).thenReturn(measure); + return context; + } } diff --git a/src/test/java/com/hello2morrow/sonarplugin/processor/CycleGroupProcessorTest.java b/src/test/java/com/hello2morrow/sonarplugin/processor/CycleGroupProcessorTest.java index 072c6a0..fb15778 100644 --- a/src/test/java/com/hello2morrow/sonarplugin/processor/CycleGroupProcessorTest.java +++ b/src/test/java/com/hello2morrow/sonarplugin/processor/CycleGroupProcessorTest.java @@ -48,7 +48,7 @@ public class CycleGroupProcessorTest { @Test public void testProcess() throws IOException { - final FileSystem fileSystem = TestHelper.initModuleFileSystem(); + final FileSystem fileSystem = TestHelper.initFileSystem(); final File currentDir = new File(".").getCanonicalFile(); final String modelPath = "AlarmClock/src/main/java/com/h2m/alarm/model"; @@ -60,8 +60,6 @@ public void testProcess() throws IOException { final File absoluteModelDir = new File(currentDir, "src/test/AlarmClockMain/" + modelPath); when(fileSystem.inputDir(absoluteModelDir)).thenReturn(modelDir); - // when(fileSystem.inputDir(absolutePresentationDir)).thenReturn(presentationDir); - final NewIssueLocation locationModel = mock(NewIssueLocation.class); when(locationModel.on(modelDir)).thenReturn(locationModel); diff --git a/src/test/java/com/hello2morrow/sonarplugin/processor/TaskProcessorTest.java b/src/test/java/com/hello2morrow/sonarplugin/processor/TaskProcessorTest.java index 3c63240..c82f8db 100644 --- a/src/test/java/com/hello2morrow/sonarplugin/processor/TaskProcessorTest.java +++ b/src/test/java/com/hello2morrow/sonarplugin/processor/TaskProcessorTest.java @@ -17,15 +17,18 @@ */ package com.hello2morrow.sonarplugin.processor; -import com.hello2morrow.sonarplugin.api.AbstractSonargraphTest; +import com.hello2morrow.sonarplugin.foundation.InMemorySensorStorage; +import com.hello2morrow.sonarplugin.foundation.TestHelper; import com.hello2morrow.sonarplugin.persistence.ReportFileReader; import org.junit.Test; import org.sonar.api.batch.SensorContext; import org.sonar.api.batch.fs.FilePredicate; import org.sonar.api.batch.fs.FileSystem; import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.sensor.internal.SensorStorage; import org.sonar.api.batch.sensor.issue.NewIssue; import org.sonar.api.batch.sensor.issue.NewIssueLocation; +import org.sonar.api.config.Settings; import org.sonar.api.resources.Project; import static org.mockito.Matchers.any; @@ -34,18 +37,10 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -public class TaskProcessorTest extends AbstractSonargraphTest { - - public static final String REPORT = "src/test/resources/sonargraph-sonar-report2.xml"; - - @Override - protected String getReport() { - return REPORT; - } +public class TaskProcessorTest { @Test public void test() { - final Project project = mock(Project.class); when(project.key()).thenReturn("hello2morrow:AlarmClock"); when(project.name()).thenReturn("AlarmClock"); @@ -53,7 +48,7 @@ public void test() { final InputFile file = mock(InputFile.class); when(file.relativePath()).thenReturn("com/h2m/alarm/model/AlarmClock.java"); when(file.isFile()).thenReturn(true); - final FileSystem fileSystem = getModuleFileSystem(); + final FileSystem fileSystem = TestHelper.initFileSystem(); when(fileSystem.inputFile(any(FilePredicate.class))).thenReturn(file); final NewIssueLocation location = mock(NewIssueLocation.class); @@ -63,13 +58,14 @@ public void test() { when(issue.newLocation()).thenReturn(location); when(issue.at(any(NewIssueLocation.class))).thenReturn(issue); - - final SensorContext context = getSensorContext(); + final SensorStorage sensorStorage = new InMemorySensorStorage(); + final SensorContext context = TestHelper.initSensorContext(fileSystem, sensorStorage); when(context.newIssue()).thenReturn(issue); - final TaskProcessor processor = new TaskProcessor(project, getSensorContext(), 2); + final TaskProcessor processor = new TaskProcessor(project, context, 2); final ReportFileReader reader = new ReportFileReader(); - reader.readSonargraphReport(project, getModuleFileSystem(), getSettings()); + final Settings settings = TestHelper.initSettings(TestHelper.REPORT_PATH2); + reader.readSonargraphReport(project, fileSystem, settings); processor.process(reader.getReport(), reader.retrieveBuildUnit(project)); verify(location, times(2)).message("Cut dependency to 'com.h2m.common.observer.Observable' [Tester]"); diff --git a/src/test/java/com/hello2morrow/sonarplugin/processor/WarningProcessorTest.java b/src/test/java/com/hello2morrow/sonarplugin/processor/WarningProcessorTest.java index b07efa5..35d911c 100644 --- a/src/test/java/com/hello2morrow/sonarplugin/processor/WarningProcessorTest.java +++ b/src/test/java/com/hello2morrow/sonarplugin/processor/WarningProcessorTest.java @@ -49,7 +49,7 @@ public class WarningProcessorTest { @Test public void testProcessDuplicates() { - final FileSystem fileSystem = TestHelper.initModuleFileSystem(); + final FileSystem fileSystem = TestHelper.initFileSystem(); final String alarmClock = "com/h2m/alarm/model/AlarmClock.java"; final InputFile alarmClockFile = mock(InputFile.class); when(alarmClockFile.absolutePath()).thenReturn(""); @@ -122,7 +122,7 @@ public InputFile answer(final InvocationOnMock invocation) throws Throwable { @Test public void testThresholdWarning() { - final FileSystem fileSystem = TestHelper.initModuleFileSystem(); + final FileSystem fileSystem = TestHelper.initFileSystem(); final String alarmClock = "com/h2m/alarm/model/AlarmClock.java"; final InputFile alarmClockFile = mock(InputFile.class); when(alarmClockFile.absolutePath()).thenReturn("");