diff options
author | Duarte Meneses <duarte.meneses@sonarsource.com> | 2015-08-10 15:04:49 +0200 |
---|---|---|
committer | Duarte Meneses <duarte.meneses@sonarsource.com> | 2015-08-12 16:12:50 +0200 |
commit | 3be4a0c53385a59c40e87f7e8ba5283b1c5928d3 (patch) | |
tree | f49d582e482c3664281b2f60d9214a4e8223812d /sonar-batch | |
parent | 3dfd88128803a86d614d4085a19c004e230f73ef (diff) | |
download | sonarqube-3be4a0c53385a59c40e87f7e8ba5283b1c5928d3.tar.gz sonarqube-3be4a0c53385a59c40e87f7e8ba5283b1c5928d3.zip |
SONAR-6777 Project cache sync
Diffstat (limited to 'sonar-batch')
33 files changed, 727 insertions, 135 deletions
diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/WSLoader.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/WSLoader.java index 5d9930630bd..1b03acd1cea 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/WSLoader.java +++ b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/WSLoader.java @@ -126,7 +126,7 @@ public class WSLoader { throw new IllegalStateException(FAIL_MSG, serverNotAvailable.getCause()); } } - throw new IllegalStateException(FAIL_MSG, cacheNotAvailable.getCause()); + throw new IllegalStateException("Data is not cached", cacheNotAvailable.getCause()); } } @@ -142,7 +142,7 @@ public class WSLoader { throw new IllegalStateException(FAIL_MSG, serverNotAvailable.getCause()); } } - throw new IllegalStateException(FAIL_MSG, serverNotAvailable.getCause()); + throw new IllegalStateException("Server is not available", serverNotAvailable.getCause()); } } diff --git a/sonar-batch/src/main/java/org/sonar/batch/cache/ProjectCacheSynchronizer.java b/sonar-batch/src/main/java/org/sonar/batch/cache/ProjectCacheSynchronizer.java index 2854dce0985..9f0c0f4250a 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/cache/ProjectCacheSynchronizer.java +++ b/sonar-batch/src/main/java/org/sonar/batch/cache/ProjectCacheSynchronizer.java @@ -19,11 +19,14 @@ */ package org.sonar.batch.cache; -import org.sonar.api.batch.bootstrap.ProjectReactor; +import org.apache.commons.lang.StringUtils; -import org.sonar.batch.bootstrap.AnalysisProperties; -import org.sonar.api.utils.log.Logger; import org.sonar.api.utils.log.Loggers; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.sonar.api.utils.log.Profiler; +import org.sonar.api.batch.bootstrap.ProjectReactor; +import org.sonar.batch.bootstrap.AnalysisProperties; import org.sonar.batch.protocol.input.BatchInput.ServerIssue; import com.google.common.base.Function; import org.sonar.batch.protocol.input.FileData; @@ -42,7 +45,7 @@ import org.sonar.batch.repository.ServerIssuesLoader; import org.sonar.batch.repository.ProjectRepositoriesLoader; public class ProjectCacheSynchronizer { - private static final Logger LOG = Loggers.get(ProjectCacheSynchronizer.class); + private static final Logger LOG = LoggerFactory.getLogger(ProjectCacheSynchronizer.class); private ProjectDefinition project; private AnalysisProperties properties; private ProjectRepositoriesLoader projectRepositoryLoader; @@ -63,39 +66,62 @@ public class ProjectCacheSynchronizer { } public void load(boolean force) { + Profiler profiler = Profiler.create(Loggers.get(ProjectCacheSynchronizer.class)); Date lastSync = cacheStatus.getSyncStatus(project.getKeyWithBranch()); if (lastSync != null) { - LOG.debug("Found project [" + project.getKeyWithBranch() + " ] cache [" + lastSync + "]"); - if (!force) { + LOG.info("Found project [{}] cache [{}]", project.getKeyWithBranch(), lastSync); return; + } else { + LOG.info("Found project [{}] cache [{}], refreshing data..", project.getKeyWithBranch(), lastSync); } + cacheStatus.delete(project.getKeyWithBranch()); + } else { + LOG.info("Cache for project [{}] not found, fetching data..", project.getKeyWithBranch()); } - cacheStatus.delete(project.getKeyWithBranch()); + profiler.startInfo("Load project repository"); ProjectRepositories projectRepo = projectRepositoryLoader.load(project, properties); + profiler.stopInfo(projectRepositoryLoader.loadedFromCache()); if (projectRepo.lastAnalysisDate() == null) { + LOG.debug("No previous analysis found"); + LOG.info("Succesfully synchronized project cache"); return; } - IssueAccumulator consumer = new IssueAccumulator(); - issuesLoader.load(project.getKeyWithBranch(), consumer, false); + profiler.startInfo("Load server issues"); + UserLoginAccumulator consumer = new UserLoginAccumulator(); + boolean fromCache = issuesLoader.load(project.getKeyWithBranch(), consumer); + profiler.stopInfo(fromCache); + profiler.startInfo("Load user information (" + consumer.loginSet.size() + " users)"); for (String login : consumer.loginSet) { userRepository.load(login); } + stopInfo(profiler, "Load user information", fromCache); - loadLineHashes(projectRepo.fileDataByModuleAndPath()); + loadLineHashes(projectRepo.fileDataByModuleAndPath(), profiler); + cacheStatus.save(project.getKeyWithBranch()); + + LOG.info("Succesfully synchronized project cache"); } private String getComponentKey(String moduleKey, String filePath) { return moduleKey + ":" + filePath; } - private void loadLineHashes(Map<String, Map<String, FileData>> fileDataByModuleAndPath) { + private void loadLineHashes(Map<String, Map<String, FileData>> fileDataByModuleAndPath, Profiler profiler) { + int numFiles = 0; + + for (Map<String, FileData> fileDataByPath : fileDataByModuleAndPath.values()) { + numFiles += fileDataByPath.size(); + } + + profiler.startInfo("Load line file hashes (" + numFiles + " files)"); + for (Entry<String, Map<String, FileData>> e1 : fileDataByModuleAndPath.entrySet()) { String moduleKey = e1.getKey(); @@ -104,15 +130,27 @@ public class ProjectCacheSynchronizer { lineHashesLoader.getLineHashes(getComponentKey(moduleKey, filePath)); } } + + profiler.stopInfo("Load line file hashes (done)"); } - private static class IssueAccumulator implements Function<ServerIssue, Void> { + private static class UserLoginAccumulator implements Function<ServerIssue, Void> { Set<String> loginSet = new HashSet<>(); @Override public Void apply(ServerIssue input) { - loginSet.add(input.getAssigneeLogin()); + if (!StringUtils.isEmpty(input.getAssigneeLogin())) { + loginSet.add(input.getAssigneeLogin()); + } return null; } } + + private static void stopInfo(Profiler profiler, String msg, boolean fromCache) { + if (fromCache) { + profiler.stopInfo(msg + " (done from cache)"); + } else { + profiler.stopInfo(msg + " (done)"); + } + } } diff --git a/sonar-batch/src/main/java/org/sonar/batch/issue/DefaultIssueCallback.java b/sonar-batch/src/main/java/org/sonar/batch/issue/DefaultIssueCallback.java index 9973b50f612..05caaec7f3c 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/issue/DefaultIssueCallback.java +++ b/sonar-batch/src/main/java/org/sonar/batch/issue/DefaultIssueCallback.java @@ -19,13 +19,13 @@ */ package org.sonar.batch.issue; +import org.apache.commons.lang.StringUtils; + import org.sonar.api.rule.RuleKey; import org.sonar.api.batch.rule.Rule; import org.sonar.api.batch.rule.Rules; import org.sonar.batch.protocol.input.BatchInput.User; -import java.util.ArrayList; -import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Map; @@ -91,7 +91,7 @@ public class DefaultIssueCallback implements IssueCallback { } private void collectInfo(DefaultIssue issue) { - if (issue.assignee() != null) { + if (!StringUtils.isEmpty(issue.assignee())) { userLoginNames.add(issue.assignee()); } if (issue.getRuleKey() != null) { @@ -104,9 +104,11 @@ public class DefaultIssueCallback implements IssueCallback { } private void getUsers() { - Collection<User> users = userRepository.load(new ArrayList<>(userLoginNames)); - for (User user : users) { - userMap.put(user.getLogin(), user.getName()); + for (String loginName : userLoginNames) { + User user = userRepository.load(loginName); + if (user != null) { + userMap.put(user.getLogin(), user.getName()); + } } } diff --git a/sonar-batch/src/main/java/org/sonar/batch/issue/tracking/DefaultServerLineHashesLoader.java b/sonar-batch/src/main/java/org/sonar/batch/issue/tracking/DefaultServerLineHashesLoader.java index e704b5ee621..c6a405d0d36 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/issue/tracking/DefaultServerLineHashesLoader.java +++ b/sonar-batch/src/main/java/org/sonar/batch/issue/tracking/DefaultServerLineHashesLoader.java @@ -51,9 +51,9 @@ public class DefaultServerLineHashesLoader implements ServerLineHashesLoader { return result.get(); } finally { if (result.isFromCache()) { - profiler.stopDebug(); - } else { profiler.stopDebug("Load line hashes (done from cache)"); + } else { + profiler.stopDebug(); } } } diff --git a/sonar-batch/src/main/java/org/sonar/batch/issue/tracking/ServerIssueRepository.java b/sonar-batch/src/main/java/org/sonar/batch/issue/tracking/ServerIssueRepository.java index e2d03e28ec9..6b78815f6b6 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/issue/tracking/ServerIssueRepository.java +++ b/sonar-batch/src/main/java/org/sonar/batch/issue/tracking/ServerIssueRepository.java @@ -40,6 +40,7 @@ import org.sonar.core.component.ComponentKeys; public class ServerIssueRepository { private static final Logger LOG = Loggers.get(ServerIssueRepository.class); + private static final String LOG_MSG = "Load server issues"; private final Caches caches; private Cache<ServerIssue> issuesCache; @@ -55,25 +56,17 @@ public class ServerIssueRepository { } public void load() { - Profiler profiler = Profiler.create(LOG).startInfo("Load server issues"); + Profiler profiler = Profiler.create(LOG).startInfo(LOG_MSG); this.issuesCache = caches.createCache("previousIssues"); caches.registerValueCoder(ServerIssue.class, new ServerIssueValueCoder()); - boolean fromCache = previousIssuesLoader.load(reactor.getRoot().getKeyWithBranch(), new SaveIssueConsumer(), false); - stopDebug(profiler, "Load server issues", fromCache); + boolean fromCache = previousIssuesLoader.load(reactor.getRoot().getKeyWithBranch(), new SaveIssueConsumer()); + profiler.stopInfo(fromCache); } public Iterable<ServerIssue> byComponent(BatchComponent component) { return issuesCache.values(component.batchId()); } - private static void stopDebug(Profiler profiler, String msg, boolean fromCache) { - if (fromCache) { - profiler.stopDebug(msg + " (done from cache)"); - } else { - profiler.stopDebug(msg + " (done)"); - } - } - private class SaveIssueConsumer implements Function<ServerIssue, Void> { @Override diff --git a/sonar-batch/src/main/java/org/sonar/batch/repository/DefaultServerIssuesLoader.java b/sonar-batch/src/main/java/org/sonar/batch/repository/DefaultServerIssuesLoader.java index 63fc7578df9..048f869e316 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/repository/DefaultServerIssuesLoader.java +++ b/sonar-batch/src/main/java/org/sonar/batch/repository/DefaultServerIssuesLoader.java @@ -39,7 +39,7 @@ public class DefaultServerIssuesLoader implements ServerIssuesLoader { } @Override - public boolean load(String componentKey, Function<ServerIssue, Void> consumer, boolean incremental) { + public boolean load(String componentKey, Function<ServerIssue, Void> consumer) { WSLoaderResult<ByteSource> result = wsLoader.loadSource("/batch/issues?key=" + BatchUtils.encodeForUrl(componentKey)); parseIssues(result.get(), consumer); return result.isFromCache(); diff --git a/sonar-batch/src/main/java/org/sonar/batch/repository/GlobalRepositoriesProvider.java b/sonar-batch/src/main/java/org/sonar/batch/repository/GlobalRepositoriesProvider.java index 3bdab15c126..bad8be3dcde 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/repository/GlobalRepositoriesProvider.java +++ b/sonar-batch/src/main/java/org/sonar/batch/repository/GlobalRepositoriesProvider.java @@ -35,12 +35,7 @@ public class GlobalRepositoriesProvider extends ProviderAdapter { if (globalReferentials == null) { Profiler profiler = Profiler.create(LOG).startInfo(LOG_MSG); globalReferentials = loader.load(); - - if (loader.loadedFromCache()) { - profiler.stopInfo(LOG_MSG + " (done from cache)"); - } else { - profiler.stopInfo(); - } + profiler.stopInfo(loader.loadedFromCache()); } return globalReferentials; } diff --git a/sonar-batch/src/main/java/org/sonar/batch/repository/ProjectRepositoriesProvider.java b/sonar-batch/src/main/java/org/sonar/batch/repository/ProjectRepositoriesProvider.java index 27be9a46b80..adf4e8b1d30 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/repository/ProjectRepositoriesProvider.java +++ b/sonar-batch/src/main/java/org/sonar/batch/repository/ProjectRepositoriesProvider.java @@ -39,12 +39,7 @@ public class ProjectRepositoriesProvider extends ProviderAdapter { if (projectReferentials == null) { Profiler profiler = Profiler.create(LOG).startInfo(LOG_MSG); projectReferentials = loader.load(reactor.getRoot(), taskProps); - - if (loader.loadedFromCache()) { - profiler.stopInfo(LOG_MSG + " (done from cache)"); - } else { - profiler.stopInfo(); - } + profiler.stopInfo(loader.loadedFromCache()); if (analysisMode.isIssues() && projectReferentials.lastAnalysisDate() == null) { LOG.warn("No analysis has been found on the server for this project. All issues will be marked as 'new'."); diff --git a/sonar-batch/src/main/java/org/sonar/batch/repository/ServerIssuesLoader.java b/sonar-batch/src/main/java/org/sonar/batch/repository/ServerIssuesLoader.java index 250648b3bcf..21681c87136 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/repository/ServerIssuesLoader.java +++ b/sonar-batch/src/main/java/org/sonar/batch/repository/ServerIssuesLoader.java @@ -24,6 +24,6 @@ import org.sonar.batch.protocol.input.BatchInput.ServerIssue; public interface ServerIssuesLoader { - boolean load(String componentKey, Function<ServerIssue, Void> consumer, boolean incremental); + boolean load(String componentKey, Function<ServerIssue, Void> consumer); } diff --git a/sonar-batch/src/main/java/org/sonar/batch/repository/user/UserRepositoryLoader.java b/sonar-batch/src/main/java/org/sonar/batch/repository/user/UserRepositoryLoader.java index 43d2383b50d..a6193dc278a 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/repository/user/UserRepositoryLoader.java +++ b/sonar-batch/src/main/java/org/sonar/batch/repository/user/UserRepositoryLoader.java @@ -19,17 +19,15 @@ */ package org.sonar.batch.repository.user; -import org.sonar.batch.bootstrap.WSLoaderResult; +import com.google.common.collect.Lists; -import org.sonar.api.utils.log.Profiler; -import org.sonar.api.utils.log.Logger; -import org.sonar.api.utils.log.Loggers; +import com.google.common.base.Joiner; +import org.sonar.batch.bootstrap.AbstractServerLoader; +import org.sonar.batch.bootstrap.WSLoaderResult; import org.sonar.batch.util.BatchUtils; import org.sonar.batch.bootstrap.WSLoader; import com.google.common.io.ByteSource; import com.google.common.base.Function; -import com.google.common.base.Joiner; -import com.google.common.collect.Lists; import org.sonar.batch.protocol.input.BatchInput; import java.io.IOException; @@ -39,8 +37,7 @@ import java.util.Collection; import java.util.Collections; import java.util.List; -public class UserRepositoryLoader { - private static final Logger LOG = Loggers.get(UserRepositoryLoader.class); +public class UserRepositoryLoader extends AbstractServerLoader { private final WSLoader wsLoader; public UserRepositoryLoader(WSLoader wsLoader) { @@ -48,28 +45,25 @@ public class UserRepositoryLoader { } public BatchInput.User load(String userLogin) { - ByteSource byteSource = loadUsers(new UserEncodingFunction().apply(userLogin)); + ByteSource byteSource = loadQuery(new UserEncodingFunction().apply(userLogin)); return parseUser(byteSource); } + /** + * Not cache friendly. Should not be used if a cache hit is expected. + */ public Collection<BatchInput.User> load(List<String> userLogins) { if (userLogins.isEmpty()) { return Collections.emptyList(); } - ByteSource byteSource = loadUsers(Joiner.on(',').join(Lists.transform(userLogins, new UserEncodingFunction()))); + ByteSource byteSource = loadQuery(Joiner.on(',').join(Lists.transform(userLogins, new UserEncodingFunction()))); return parseUsers(byteSource); } - private ByteSource loadUsers(String loginsQuery) { - Profiler profiler = Profiler.create(LOG).startDebug("Load user repository"); + private ByteSource loadQuery(String loginsQuery) { WSLoaderResult<ByteSource> result = wsLoader.loadSource("/batch/users?logins=" + loginsQuery); - if (result.isFromCache()) { - profiler.stopInfo("Load user repository (done from cache)"); - } else { - profiler.stopInfo(); - } - + super.loadedFromCache = result.isFromCache(); return result.get(); } @@ -82,7 +76,7 @@ public class UserRepositoryLoader { private static BatchInput.User parseUser(ByteSource input) { try (InputStream is = input.openStream()) { - return BatchInput.User.parseFrom(is); + return BatchInput.User.parseDelimitedFrom(is); } catch (IOException e) { throw new IllegalStateException("Unable to get user details from server", e); } diff --git a/sonar-batch/src/main/java/org/sonar/batch/rule/RulesProvider.java b/sonar-batch/src/main/java/org/sonar/batch/rule/RulesProvider.java index df0c231ed3d..866bcdcb73b 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/rule/RulesProvider.java +++ b/sonar-batch/src/main/java/org/sonar/batch/rule/RulesProvider.java @@ -56,11 +56,7 @@ public class RulesProvider extends ProviderAdapter { newRule.setInternalKey(r.getInternalKey()); } - if (ref.loadedFromCache()) { - profiler.stopInfo(LOG_MSG + " (done from cache)"); - } else { - profiler.stopInfo(); - } + profiler.stopInfo(ref.loadedFromCache()); return builder.build(); } diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/report/JSONReport.java b/sonar-batch/src/main/java/org/sonar/batch/scan/report/JSONReport.java index 30c56af99d7..68c2f5f94db 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/scan/report/JSONReport.java +++ b/sonar-batch/src/main/java/org/sonar/batch/scan/report/JSONReport.java @@ -19,7 +19,10 @@ */ package org.sonar.batch.scan.report; +import org.sonar.batch.protocol.input.BatchInput.User; + import com.google.common.annotations.VisibleForTesting; + import java.io.BufferedWriter; import java.io.File; import java.io.FileOutputStream; @@ -27,9 +30,11 @@ import java.io.IOException; import java.io.OutputStreamWriter; import java.io.Writer; import java.nio.charset.StandardCharsets; -import java.util.ArrayList; import java.util.Collection; +import java.util.LinkedList; +import java.util.List; import java.util.Set; + import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -47,14 +52,12 @@ import org.sonar.api.config.Settings; import org.sonar.api.platform.Server; import org.sonar.api.resources.Project; import org.sonar.api.rule.RuleKey; -import org.sonar.api.utils.SonarException; import org.sonar.api.utils.text.JsonWriter; import org.sonar.batch.issue.IssueCache; import org.sonar.batch.protocol.input.BatchInput; import org.sonar.batch.repository.user.UserRepositoryLoader; import org.sonar.batch.scan.filesystem.InputPathCache; import org.sonar.core.issue.DefaultIssue; - import static com.google.common.collect.Sets.newHashSet; @Properties({ @@ -120,12 +123,11 @@ public class JSONReport implements Reporter { writeJsonIssues(json, ruleKeys, userLogins); writeJsonComponents(json); writeJsonRules(json, ruleKeys); - Collection<BatchInput.User> users = userRepository.load(new ArrayList<>(userLogins)); - writeUsers(json, users); + writeUsers(json, userLogins); json.endObject().close(); } catch (IOException e) { - throw new SonarException("Unable to write JSON report", e); + throw new IllegalStateException("Unable to write JSON report", e); } } @@ -147,10 +149,10 @@ public class JSONReport implements Reporter { .prop("assignee", issue.assignee()) .prop("effortToFix", issue.effortToFix()) .propDateTime("creationDate", issue.creationDate()); - if (issue.reporter() != null) { + if (!StringUtils.isEmpty(issue.reporter())) { logins.add(issue.reporter()); } - if (issue.assignee() != null) { + if (!StringUtils.isEmpty(issue.assignee())) { logins.add(issue.assignee()); } json.endObject(); @@ -212,7 +214,15 @@ public class JSONReport implements Reporter { json.endArray(); } - private static void writeUsers(JsonWriter json, Collection<BatchInput.User> users) throws IOException { + private void writeUsers(JsonWriter json, Collection<String> userLogins) throws IOException { + List<BatchInput.User> users = new LinkedList<BatchInput.User>(); + for (String userLogin : userLogins) { + User user = userRepository.load(userLogin); + if (user != null) { + users.add(user); + } + } + json.name("users").beginArray(); for (BatchInput.User user : users) { json diff --git a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginJarExploderTest.java b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginJarExploderTest.java index 631d78634a0..a7c1c8d4e60 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginJarExploderTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginJarExploderTest.java @@ -71,7 +71,7 @@ public class BatchPluginJarExploderTest { } File getFileFromCache(String filename) throws IOException { - File src = FileUtils.toFile(BatchPluginJarExploderTest.class.getResource("/org/sonar/batch/bootstrap/BatchPluginUnzipperTest/" + filename)); + File src = FileUtils.toFile(BatchPluginJarExploderTest.class.getResource("/org/sonar/batch/bootstrap/BatchPluginJarExploderTest/" + filename)); File destFile = new File(new File(userHome, "" + filename.hashCode()), filename); FileUtils.copyFile(src, destFile); return destFile; diff --git a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginPredicateTest.java b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginPredicateTest.java index 66429da2715..104b8ad74a1 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginPredicateTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginPredicateTest.java @@ -70,6 +70,18 @@ public class BatchPluginPredicateTest { } @Test + public void verify_both_inclusions_and_exclusions_issues() { + when(mode.isIssues()).thenReturn(true); + settings + .setProperty(CoreProperties.PREVIEW_INCLUDE_PLUGINS, "checkstyle,pmd,findbugs") + .setProperty(CoreProperties.PREVIEW_EXCLUDE_PLUGINS, "cobertura"); + BatchPluginPredicate predicate = new BatchPluginPredicate(settings, mode); + assertThat(predicate.apply("checkstyle")).isTrue(); + assertThat(predicate.apply("pmd")).isTrue(); + assertThat(predicate.apply("cobertura")).isFalse(); + } + + @Test public void test_exclusions_without_any_inclusions() { when(mode.isPreview()).thenReturn(true); settings.setProperty(CoreProperties.PREVIEW_EXCLUDE_PLUGINS, "checkstyle,pmd,findbugs"); diff --git a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/WSLoaderTest.java b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/WSLoaderTest.java index 781c1ad255c..766d6603ece 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/WSLoaderTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/WSLoaderTest.java @@ -19,6 +19,10 @@ */ package org.sonar.batch.bootstrap; +import org.hamcrest.Matchers; + +import org.junit.rules.ExpectedException; +import org.junit.Rule; import org.sonar.api.utils.HttpDownloader; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; @@ -29,6 +33,7 @@ import org.mockito.InOrder; import java.io.IOException; import java.net.URI; +import static org.junit.Assert.*; import static org.mockito.Mockito.mock; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Matchers.anyBoolean; @@ -55,6 +60,8 @@ public class WSLoaderTest { private ServerClient client; @Mock private PersistentCache cache; + @Rule + public ExpectedException exception = ExpectedException.none(); @Before public void setUp() throws IOException { @@ -77,14 +84,13 @@ public class WSLoaderTest { assertResult(loader.loadString(ID), cacheValue, true); assertResult(loader.loadString(ID), cacheValue, true); - // only try once the server - verify(client, times(1)).load(anyString(), anyString(), anyBoolean(), anyInt(), anyInt()); - verify(cache, times(2)).get(ID, null); + assertUsedServer(1); + assertUsedCache(2); } @Test public void test_cache_strategy_fallback() throws IOException { - when(cache.get(ID, null)).thenReturn(null); + turnCacheEmpty(); WSLoader loader = new WSLoader(LoadStrategy.CACHE_FIRST, cache, client); assertResult(loader.load(ID), serverValue.getBytes(), false); @@ -113,23 +119,38 @@ public class WSLoaderTest { verify(cache).put(ID, serverValue.getBytes()); } - @Test(expected = NullPointerException.class) + @Test public void test_throw_cache_exception_fallback() throws IOException { turnServerOffline(); + when(cache.get(ID, null)).thenThrow(new NullPointerException()); WSLoader loader = new WSLoader(LoadStrategy.SERVER_FIRST, cache, client); - loader.load(ID); + + try { + loader.load(ID); + fail("NPE expected"); + } catch (NullPointerException e) { + assertUsedServer(1); + assertUsedCache(1); + } } - @Test(expected = IllegalStateException.class) + @Test public void test_throw_cache_exception() throws IOException { when(cache.get(ID, null)).thenThrow(new IllegalStateException()); WSLoader loader = new WSLoader(LoadStrategy.CACHE_FIRST, cache, client); - loader.load(ID); + + try { + loader.load(ID); + fail("IllegalStateException expected"); + } catch (IllegalStateException e) { + assertUsedServer(0); + assertUsedCache(1); + } } - @Test(expected = IllegalStateException.class) + @Test public void test_throw_http_exceptions() { HttpDownloader.HttpException httpException = mock(HttpDownloader.HttpException.class); IllegalStateException wrapperException = new IllegalStateException(httpException); @@ -140,14 +161,48 @@ public class WSLoaderTest { try { loader.load(ID); + fail("IllegalStateException expected"); } catch (IllegalStateException e) { // cache should not be used verifyNoMoreInteractions(cache); - throw e; } } @Test + public void test_server_only_not_available() { + turnServerOffline(); + + exception.expect(IllegalStateException.class); + exception.expectMessage(Matchers.is("Server is not available")); + + WSLoader loader = new WSLoader(LoadStrategy.SERVER_ONLY, cache, client); + loader.load(ID); + } + + @Test + public void test_server_cache_not_available() throws IOException { + turnServerOffline(); + turnCacheEmpty(); + + exception.expect(IllegalStateException.class); + exception.expectMessage(Matchers.is("Server is not accessible and data is not cached")); + + WSLoader loader = new WSLoader(LoadStrategy.SERVER_FIRST, cache, client); + loader.load(ID); + } + + @Test + public void test_cache_only_available() throws IOException { + turnCacheEmpty(); + + exception.expect(IllegalStateException.class); + exception.expectMessage(Matchers.is("Data is not cached")); + + WSLoader loader = new WSLoader(LoadStrategy.CACHE_ONLY, cache, client); + loader.load(ID); + } + + @Test public void test_server_strategy() throws IOException { WSLoader loader = new WSLoader(LoadStrategy.SERVER_FIRST, cache, client); assertResult(loader.load(ID), serverValue.getBytes(), false); @@ -170,6 +225,14 @@ public class WSLoaderTest { assertResult(loader.loadString(ID), serverValue, false); } + private void assertUsedCache(int times) throws IOException { + verify(cache, times(times)).get(ID, null); + } + + private void assertUsedServer(int times) { + verify(client, times(times)).load(anyString(), anyString(), anyBoolean(), anyInt(), anyInt()); + } + private <T> void assertResult(WSLoaderResult<T> result, T expected, boolean fromCache) { assertThat(result).isNotNull(); assertThat(result.get()).isEqualTo(expected); @@ -179,4 +242,8 @@ public class WSLoaderTest { private void turnServerOffline() { when(client.load(anyString(), anyString(), anyBoolean(), anyInt(), anyInt())).thenThrow(new IllegalStateException()); } + + private void turnCacheEmpty() throws IOException { + when(cache.get(ID, null)).thenReturn(null); + } } diff --git a/sonar-batch/src/test/java/org/sonar/batch/cache/ProjectCacheSynchronizerTest.java b/sonar-batch/src/test/java/org/sonar/batch/cache/ProjectCacheSynchronizerTest.java index 7e4ce898915..3cf688897f0 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/cache/ProjectCacheSynchronizerTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/cache/ProjectCacheSynchronizerTest.java @@ -19,6 +19,120 @@ */ package org.sonar.batch.cache; +import static org.mockito.Mockito.when; +import org.sonar.batch.issue.tracking.DefaultServerLineHashesLoader; +import org.sonar.batch.repository.DefaultServerIssuesLoader; +import org.sonar.batch.scan.ProjectAnalysisMode; +import org.sonar.batch.repository.DefaultProjectRepositoriesLoader; +import org.sonar.api.batch.bootstrap.ProjectReactor; +import org.sonar.batch.bootstrap.WSLoaderResult; +import org.sonar.batch.bootstrap.WSLoader; +import com.google.common.io.ByteSource; + +import java.io.IOException; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.Date; +import java.util.HashMap; + +import static org.mockito.Matchers.anyString; + +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import com.google.common.io.Resources; +import org.junit.Test; +import org.junit.Before; +import org.mockito.MockitoAnnotations; +import org.mockito.Mock; +import org.sonar.api.batch.bootstrap.ProjectDefinition; +import org.sonar.batch.bootstrap.AnalysisProperties; +import org.sonar.batch.issue.tracking.ServerLineHashesLoader; +import org.sonar.batch.repository.ProjectRepositoriesLoader; +import org.sonar.batch.repository.ServerIssuesLoader; +import org.sonar.batch.repository.user.UserRepositoryLoader; + public class ProjectCacheSynchronizerTest { + private static final String BATCH_PROJECT = "/batch/project?key=org.codehaus.sonar-plugins%3Asonar-scm-git-plugin&preview=true"; + private static final String ISSUES = "/batch/issues?key=org.codehaus.sonar-plugins%3Asonar-scm-git-plugin"; + private static final String LINE_HASHES1 = "/api/sources/hash?key=org.codehaus.sonar-plugins%3Asonar-scm-git-plugin%3Asrc%2Ftest%2Fjava%2Forg%2Fsonar%2Fplugins%2Fscm%2Fgit%2FJGitBlameCommandTest.java"; + private static final String LINE_HASHES2 = "/api/sources/hash?key=org.codehaus.sonar-plugins%3Asonar-scm-git-plugin%3Asrc%2Fmain%2Fjava%2Forg%2Fsonar%2Fplugins%2Fscm%2Fgit%2FGitScmProvider.java"; + + @Mock + private ProjectDefinition project; + @Mock + private ProjectReactor projectReactor; + @Mock + private ProjectCacheStatus cacheStatus; + @Mock + private ProjectAnalysisMode analysisMode; + @Mock + private AnalysisProperties properties; + @Mock + private WSLoader ws; + + private ProjectRepositoriesLoader projectRepositoryLoader; + private ServerIssuesLoader issuesLoader; + private ServerLineHashesLoader lineHashesLoader; + private UserRepositoryLoader userRepositoryLoader; + + @Before + public void setUp() throws IOException { + MockitoAnnotations.initMocks(this); + + String batchProject = getResourceAsString("batch_project.json"); + ByteSource issues = getResourceAsByteSource("batch_issues.protobuf"); + String lineHashes2 = getResourceAsString("api_sources_hash_GitScmProvider.text"); + String lineHashes1 = getResourceAsString("api_sources_hash_JGitBlameCommand.text"); + + when(ws.loadString(BATCH_PROJECT)).thenReturn(new WSLoaderResult<>(batchProject, false)); + when(ws.loadSource(ISSUES)).thenReturn(new WSLoaderResult<>(issues, false)); + when(ws.loadString(LINE_HASHES1)).thenReturn(new WSLoaderResult<>(lineHashes1, false)); + when(ws.loadString(LINE_HASHES2)).thenReturn(new WSLoaderResult<>(lineHashes2, false)); + + when(analysisMode.isIssues()).thenReturn(true); + when(project.getKeyWithBranch()).thenReturn("org.codehaus.sonar-plugins:sonar-scm-git-plugin"); + when(projectReactor.getRoot()).thenReturn(project); + when(properties.properties()).thenReturn(new HashMap<String, String>()); + + projectRepositoryLoader = new DefaultProjectRepositoriesLoader(ws, analysisMode); + issuesLoader = new DefaultServerIssuesLoader(ws); + lineHashesLoader = new DefaultServerLineHashesLoader(ws); + userRepositoryLoader = new UserRepositoryLoader(ws); + } + + @Test + public void testSync() { + ProjectCacheSynchronizer sync = new ProjectCacheSynchronizer(projectReactor, projectRepositoryLoader, properties, issuesLoader, lineHashesLoader, userRepositoryLoader, + cacheStatus); + sync.load(false); + + verify(ws).loadString(BATCH_PROJECT); + verify(ws).loadSource(ISSUES); + verify(ws).loadString(LINE_HASHES1); + verify(ws).loadString(LINE_HASHES2); + verifyNoMoreInteractions(ws); + + verify(cacheStatus).save(anyString()); + } + + @Test + public void testDontSyncIfNotForce() { + when(cacheStatus.getSyncStatus("org.codehaus.sonar-plugins:sonar-scm-git-plugin")).thenReturn(new Date()); + + ProjectCacheSynchronizer sync = new ProjectCacheSynchronizer(projectReactor, projectRepositoryLoader, properties, issuesLoader, lineHashesLoader, userRepositoryLoader, + cacheStatus); + sync.load(false); + + verifyNoMoreInteractions(ws); + } + + private String getResourceAsString(String name) throws IOException { + URL resource = this.getClass().getResource(getClass().getSimpleName() + "/" + name); + return Resources.toString(resource, StandardCharsets.UTF_8); + } + private ByteSource getResourceAsByteSource(String name) throws IOException { + URL resource = this.getClass().getResource(getClass().getSimpleName() + "/" + name); + return Resources.asByteSource(resource); + } } diff --git a/sonar-batch/src/test/java/org/sonar/batch/issue/DefaultIssueCallbackTest.java b/sonar-batch/src/test/java/org/sonar/batch/issue/DefaultIssueCallbackTest.java index 41d5bddfbca..de1b59fed36 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/issue/DefaultIssueCallbackTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/issue/DefaultIssueCallbackTest.java @@ -39,7 +39,6 @@ import java.util.List; import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyListOf; import static org.assertj.core.api.Assertions.assertThat; import org.junit.Test; @@ -68,7 +67,7 @@ public class DefaultIssueCallbackTest { BatchInput.User.Builder userBuilder = BatchInput.User.newBuilder(); userBuilder.setLogin("user"); userBuilder.setName("name"); - when(userRepository.load(anyListOf(String.class))).thenReturn(ImmutableList.of(userBuilder.build())); + when(userRepository.load("user")).thenReturn(userBuilder.build()); Rule r = mock(Rule.class); when(r.name()).thenReturn("rule name"); @@ -128,7 +127,7 @@ public class DefaultIssueCallbackTest { } }; - when(userRepository.load(anyListOf(String.class))).thenReturn(new LinkedList<BatchInput.User>()); + when(userRepository.load(any(String.class))).thenReturn(null); when(rules.find(any(RuleKey.class))).thenReturn(null); DefaultIssueCallback issueCallback = new DefaultIssueCallback(issueCache, listener, userRepository, rules); diff --git a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/BatchMediumTester.java b/sonar-batch/src/test/java/org/sonar/batch/mediumtest/BatchMediumTester.java index 522d8b5fa1a..3e4ddedacf5 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/BatchMediumTester.java +++ b/sonar-batch/src/test/java/org/sonar/batch/mediumtest/BatchMediumTester.java @@ -60,7 +60,6 @@ import org.sonar.batch.report.ReportPublisher; import org.sonar.batch.repository.GlobalRepositoriesLoader; import org.sonar.batch.repository.ProjectRepositoriesLoader; import org.sonar.batch.repository.ServerIssuesLoader; -import org.sonar.core.component.ComponentKeys; /** * Main utility class for writing batch medium tests. @@ -336,7 +335,7 @@ public class BatchMediumTester { @Override public boolean loadedFromCache() { - return true; + return false; } } @@ -385,13 +384,11 @@ public class BatchMediumTester { } @Override - public boolean load(String componentKey, Function<ServerIssue, Void> consumer, boolean incremental) { + public boolean load(String componentKey, Function<ServerIssue, Void> consumer) { for (ServerIssue serverIssue : serverIssues) { - if (!incremental || ComponentKeys.createEffectiveKey(serverIssue.getModuleKey(), serverIssue.hasPath() ? serverIssue.getPath() : null).equals(componentKey)) { - consumer.apply(serverIssue); - } + consumer.apply(serverIssue); } - return false; + return true; } } diff --git a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/issuesmode/IssueModeAndReportsMediumTest.java b/sonar-batch/src/test/java/org/sonar/batch/mediumtest/issuesmode/IssueModeAndReportsMediumTest.java index 9b0a4a7a643..02e9a6a718d 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/issuesmode/IssueModeAndReportsMediumTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/mediumtest/issuesmode/IssueModeAndReportsMediumTest.java @@ -19,6 +19,8 @@ */ package org.sonar.batch.mediumtest.issuesmode; +import org.sonar.batch.protocol.input.BatchInput.ServerIssue; + import com.google.common.collect.ImmutableMap; import java.io.File; @@ -80,7 +82,7 @@ public class IssueModeAndReportsMediumTest { .activateRule(new ActiveRule("manual", "MyManualIssue", null, "My manual issue", "MAJOR", null, null)) .setPreviousAnalysisDate(new Date()) // Existing issue that is still detected - .mockServerIssue(org.sonar.batch.protocol.input.BatchInput.ServerIssue.newBuilder().setKey("xyz") + .mockServerIssue(ServerIssue.newBuilder().setKey("xyz") .setModuleKey("sample") .setPath("xources/hello/HelloJava.xoo") .setRuleRepository("xoo") @@ -92,7 +94,7 @@ public class IssueModeAndReportsMediumTest { .setStatus("OPEN") .build()) // Existing issue that is no more detected (will be closed) - .mockServerIssue(org.sonar.batch.protocol.input.BatchInput.ServerIssue.newBuilder().setKey("resolved") + .mockServerIssue(ServerIssue.newBuilder().setKey("resolved") .setModuleKey("sample") .setPath("xources/hello/HelloJava.xoo") .setRuleRepository("xoo") @@ -104,7 +106,7 @@ public class IssueModeAndReportsMediumTest { .setStatus("OPEN") .build()) // Existing issue on project that is no more detected - .mockServerIssue(org.sonar.batch.protocol.input.BatchInput.ServerIssue.newBuilder().setKey("resolved-on-project") + .mockServerIssue(ServerIssue.newBuilder().setKey("resolved-on-project") .setModuleKey("sample") .setRuleRepository("xoo") .setRuleKey("OneIssuePerModule") @@ -113,7 +115,7 @@ public class IssueModeAndReportsMediumTest { .setStatus("OPEN") .build()) // Manual issue - .mockServerIssue(org.sonar.batch.protocol.input.BatchInput.ServerIssue.newBuilder().setKey("manual") + .mockServerIssue(ServerIssue.newBuilder().setKey("manual") .setModuleKey("sample") .setPath("xources/hello/HelloJava.xoo") .setRuleRepository("manual") @@ -155,6 +157,7 @@ public class IssueModeAndReportsMediumTest { int openIssues = 0; int resolvedIssue = 0; for (Issue issue : result.trackedIssues()) { + System.out.println(issue.message() + " " + issue.key() + " " + issue.ruleKey()); if (issue.isNew()) { newIssues++; } else if (issue.resolution() != null) { @@ -239,13 +242,11 @@ public class IssueModeAndReportsMediumTest { @Test public void testIssueCallback() throws Exception { - File projectDir = new File(IssuesMediumTest.class.getResource("/mediumtest/xoo/sample").toURI()); - File tmpDir = temp.newFolder(); - FileUtils.copyDirectory(projectDir, tmpDir); + File projectDir = copyProject("/mediumtest/xoo/sample"); IssueRecorder issueListener = new IssueRecorder(); TaskResult result = tester - .newScanTask(new File(tmpDir, "sonar-project.properties")) + .newScanTask(new File(projectDir, "sonar-project.properties")) .setIssueListener(issueListener) .start(); diff --git a/sonar-batch/src/test/java/org/sonar/batch/repository/DefaultServerIssuesLoaderTest.java b/sonar-batch/src/test/java/org/sonar/batch/repository/DefaultServerIssuesLoaderTest.java index e9381790ad7..f318c27363e 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/repository/DefaultServerIssuesLoaderTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/repository/DefaultServerIssuesLoaderTest.java @@ -51,7 +51,7 @@ public class DefaultServerIssuesLoaderTest { @Test public void loadFromWs() throws Exception { ByteSource bs = mock(ByteSource.class); - when(wsLoader.loadSource("/batch/issues?key=foo")).thenReturn(new WSLoaderResult(bs, true)); + when(wsLoader.loadSource("/batch/issues?key=foo")).thenReturn(new WSLoaderResult<>(bs, true)); ByteArrayOutputStream bos = new ByteArrayOutputStream(); @@ -70,7 +70,7 @@ public class DefaultServerIssuesLoaderTest { result.add(input); return null; } - }, false); + }); assertThat(result).extracting("key").containsExactly("ab1", "ab2"); } @@ -80,6 +80,6 @@ public class DefaultServerIssuesLoaderTest { ByteSource source = mock(ByteSource.class); when(source.openBufferedStream()).thenThrow(IOException.class); when(wsLoader.loadSource("/batch/issues?key=foo")).thenReturn(new WSLoaderResult<ByteSource>(source, true)); - loader.load("foo", mock(Function.class), false); + loader.load("foo", mock(Function.class)); } } diff --git a/sonar-batch/src/test/java/org/sonar/batch/repository/user/UserRepositoryLoaderTest.java b/sonar-batch/src/test/java/org/sonar/batch/repository/user/UserRepositoryLoaderTest.java index bd0b3e7af88..8f583afba41 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/repository/user/UserRepositoryLoaderTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/repository/user/UserRepositoryLoaderTest.java @@ -19,8 +19,9 @@ */ package org.sonar.batch.repository.user; -import org.junit.rules.ExpectedException; +import com.google.common.collect.ImmutableMap; +import org.junit.rules.ExpectedException; import org.junit.Rule; import org.mockito.Mockito; import org.sonar.batch.bootstrap.WSLoaderResult; @@ -34,6 +35,7 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.Arrays; +import java.util.Map; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.tuple; @@ -49,16 +51,34 @@ public class UserRepositoryLoaderTest { WSLoader wsLoader = mock(WSLoader.class); UserRepositoryLoader userRepo = new UserRepositoryLoader(wsLoader); + Map<String, String> userMap = ImmutableMap.of("fmallet", "Freddy Mallet", "sbrandhof", "Simon"); + WSLoaderResult<ByteSource> res = new WSLoaderResult<>(createUsersMock(userMap), true); + when(wsLoader.loadSource("/batch/users?logins=fmallet,sbrandhof")).thenReturn(res); + + assertThat(userRepo.load(Arrays.asList("fmallet", "sbrandhof"))).extracting("login", "name").containsOnly(tuple("fmallet", "Freddy Mallet"), tuple("sbrandhof", "Simon")); + } + + @Test + public void testLoadSingleUser() throws IOException { + WSLoader wsLoader = mock(WSLoader.class); + UserRepositoryLoader userRepo = new UserRepositoryLoader(wsLoader); + + WSLoaderResult<ByteSource> res = new WSLoaderResult<>(createUsersMock(ImmutableMap.of("fmallet", "Freddy Mallet")), true); + when(wsLoader.loadSource("/batch/users?logins=fmallet")).thenReturn(res); + + assertThat(userRepo.load("fmallet").getName()).isEqualTo("Freddy Mallet"); + } + + private ByteSource createUsersMock(Map<String, String> users) throws IOException { ByteArrayOutputStream out = new ByteArrayOutputStream(); - BatchInput.User.Builder builder = BatchInput.User.newBuilder(); - builder.setLogin("fmallet").setName("Freddy Mallet").build().writeDelimitedTo(out); - builder.setLogin("sbrandhof").setName("Simon").build().writeDelimitedTo(out); + for (Map.Entry<String, String> user : users.entrySet()) { + BatchInput.User.Builder builder = BatchInput.User.newBuilder(); + builder.setLogin(user.getKey()).setName(user.getValue()).build().writeDelimitedTo(out); + } ByteSource source = mock(ByteSource.class); - when(wsLoader.loadSource("/batch/users?logins=fmallet,sbrandhof")).thenReturn(new WSLoaderResult<>(source, true)); when(source.openStream()).thenReturn(new ByteArrayInputStream(out.toByteArray())); - - assertThat(userRepo.load(Arrays.asList("fmallet", "sbrandhof"))).extracting("login", "name").containsOnly(tuple("fmallet", "Freddy Mallet"), tuple("sbrandhof", "Simon")); + return source; } @Test @@ -66,7 +86,10 @@ public class UserRepositoryLoaderTest { WSLoader wsLoader = mock(WSLoader.class); UserRepositoryLoader userRepo = new UserRepositoryLoader(wsLoader); ByteSource source = mock(ByteSource.class); - when(wsLoader.loadSource("/batch/users?logins=fmallet,sbrandhof")).thenReturn(new WSLoaderResult<>(source, true)); + + WSLoaderResult<ByteSource> res = new WSLoaderResult<>(source, true); + + when(wsLoader.loadSource("/batch/users?logins=fmallet,sbrandhof")).thenReturn(res); InputStream errorInputStream = mock(InputStream.class); Mockito.doThrow(IOException.class).when(errorInputStream).read(); @@ -75,6 +98,6 @@ public class UserRepositoryLoaderTest { exception.expect(IllegalStateException.class); exception.expectMessage("Unable to get user details from server"); - assertThat(userRepo.load(Arrays.asList("fmallet", "sbrandhof"))).extracting("login", "name").containsOnly(tuple("fmallet", "Freddy Mallet"), tuple("sbrandhof", "Simon")); + userRepo.load(Arrays.asList("fmallet", "sbrandhof")); } } diff --git a/sonar-batch/src/test/java/org/sonar/batch/rule/DefaultRulesLoaderTest.java b/sonar-batch/src/test/java/org/sonar/batch/rule/DefaultRulesLoaderTest.java index 7b213f0dbbc..bd1a2e65da9 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/rule/DefaultRulesLoaderTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/rule/DefaultRulesLoaderTest.java @@ -41,7 +41,7 @@ public class DefaultRulesLoaderTest { public void testParseServerResponse() throws IOException { WSLoader wsLoader = mock(WSLoader.class); ByteSource source = Resources.asByteSource(this.getClass().getResource("DefaultRulesLoader/response.protobuf")); - when(wsLoader.loadSource(anyString())).thenReturn(new WSLoaderResult(source, true)); + when(wsLoader.loadSource(anyString())).thenReturn(new WSLoaderResult<>(source, true)); DefaultRulesLoader loader = new DefaultRulesLoader(wsLoader); List<Rule> ruleList = loader.load(); assertThat(ruleList).hasSize(318); @@ -51,13 +51,13 @@ public class DefaultRulesLoaderTest { public void testLoadedFromCache() { WSLoader wsLoader = mock(WSLoader.class); ByteSource source = Resources.asByteSource(this.getClass().getResource("DefaultRulesLoader/response.protobuf")); - when(wsLoader.loadSource(anyString())).thenReturn(new WSLoaderResult(source, true)); + when(wsLoader.loadSource(anyString())).thenReturn(new WSLoaderResult<>(source, true)); DefaultRulesLoader loader = new DefaultRulesLoader(wsLoader); loader.load(); assertThat(loader.loadedFromCache()).isTrue(); } - + @Test(expected = IllegalStateException.class) public void testGetLoadedFromCacheBefore() { DefaultRulesLoader loader = new DefaultRulesLoader(mock(WSLoader.class)); diff --git a/sonar-batch/src/test/java/org/sonar/batch/scan/report/JSONReportTest.java b/sonar-batch/src/test/java/org/sonar/batch/scan/report/JSONReportTest.java index a5392d36288..c2ccdd2458e 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/scan/report/JSONReportTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/scan/report/JSONReportTest.java @@ -21,6 +21,7 @@ package org.sonar.batch.scan.report; import com.google.common.collect.Lists; import com.google.common.io.Resources; + import java.io.File; import java.io.IOException; import java.io.StringWriter; @@ -28,6 +29,7 @@ import java.text.SimpleDateFormat; import java.util.Arrays; import java.util.Collections; import java.util.TimeZone; + import org.junit.Before; import org.junit.Test; import org.junit.rules.TemporaryFolder; @@ -50,9 +52,7 @@ import org.sonar.batch.repository.user.UserRepositoryLoader; import org.sonar.batch.scan.filesystem.InputPathCache; import org.sonar.core.issue.DefaultIssue; import org.sonar.test.JsonAssert; - import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Matchers.anyListOf; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; @@ -118,7 +118,8 @@ public class JSONReportTest { when(issueCache.all()).thenReturn(Lists.newArrayList(issue)); BatchInput.User user1 = BatchInput.User.newBuilder().setLogin("julien").setName("Julien").build(); BatchInput.User user2 = BatchInput.User.newBuilder().setLogin("simon").setName("Simon").build(); - when(userRepository.load(anyListOf(String.class))).thenReturn(Lists.newArrayList(user1, user2)); + when(userRepository.load("julien")).thenReturn(user1); + when(userRepository.load("simon")).thenReturn(user2); StringWriter writer = new StringWriter(); jsonReport.writeJson(writer); diff --git a/sonar-batch/src/test/resources/org/sonar/batch/bootstrap/BatchPluginUnzipperTest/sonar-checkstyle-plugin-2.8.jar b/sonar-batch/src/test/resources/org/sonar/batch/bootstrap/BatchPluginJarExploderTest/sonar-checkstyle-plugin-2.8.jar Binary files differindex f937399bec5..f937399bec5 100644 --- a/sonar-batch/src/test/resources/org/sonar/batch/bootstrap/BatchPluginUnzipperTest/sonar-checkstyle-plugin-2.8.jar +++ b/sonar-batch/src/test/resources/org/sonar/batch/bootstrap/BatchPluginJarExploderTest/sonar-checkstyle-plugin-2.8.jar diff --git a/sonar-batch/src/test/resources/org/sonar/batch/bootstrap/JdbcDriverHolderTest/jdbc-driver.jar b/sonar-batch/src/test/resources/org/sonar/batch/bootstrap/JdbcDriverHolderTest/jdbc-driver.jar Binary files differdeleted file mode 100644 index c2bde4e5fff..00000000000 --- a/sonar-batch/src/test/resources/org/sonar/batch/bootstrap/JdbcDriverHolderTest/jdbc-driver.jar +++ /dev/null diff --git a/sonar-batch/src/test/resources/org/sonar/batch/cache/ProjectCacheSynchronizerTest/api_rules_list.protobuf b/sonar-batch/src/test/resources/org/sonar/batch/cache/ProjectCacheSynchronizerTest/api_rules_list.protobuf Binary files differnew file mode 100644 index 00000000000..1d417ce2880 --- /dev/null +++ b/sonar-batch/src/test/resources/org/sonar/batch/cache/ProjectCacheSynchronizerTest/api_rules_list.protobuf diff --git a/sonar-batch/src/test/resources/org/sonar/batch/cache/ProjectCacheSynchronizerTest/api_sources_hash_GitScmProvider.text b/sonar-batch/src/test/resources/org/sonar/batch/cache/ProjectCacheSynchronizerTest/api_sources_hash_GitScmProvider.text new file mode 100644 index 00000000000..a9ad538e80a --- /dev/null +++ b/sonar-batch/src/test/resources/org/sonar/batch/cache/ProjectCacheSynchronizerTest/api_sources_hash_GitScmProvider.text @@ -0,0 +1,49 @@ +523048e7f5ca9550505f2d8ea6d587e7 +50ff1975ec4309da19591231c6b5104b +eba1d423f8632818ce94c4eac1b90713 +6112a40c70ed55453a0753030d5564a4 +3389dae361af79b04c9c8e7057f60cc6 +eac5fc1130394e7268b1cfbc54cd7e4d +c0b153d8c08365f2de343e278d3b54c7 +eb4521cb5d193e1d37ecac25b0ffea43 +9210ed0dec59ed663c744d7fb68f0275 +3389dae361af79b04c9c8e7057f60cc6 +cd0fbdfa49d32525ecbdb8dab19dafe6 +ea12a10f5b7730daa639fe133867e088 +69739b9bc9312dfb1a6b8625a08c652a +ec21e054f7f5748d0161fe27cdad6462 +3389dae361af79b04c9c8e7057f60cc6 +951a83e8074813100da0cba92092b385 +c93caecd79a332773cfb06cd5d3b8895 +5832d52d5fcb22a3350f62c856993f0d +c4c9bdd47ee05028cb84873da0ebf2b5 +f89e422b117e518acef69df33f199d10 + +90aa2aae2384f6412c3b86d085d5ffa5 +647f262205ad09f32b0091df388992ed + +943d54ba3e8812437c4d26ef8aa263f8 + +340385b760d1441d3b74e5e39399cc0c + +a94613fd32125cd63160b0c1cf2bd078 + +3415664f5f4a608772e6a4c73a993804 +597b7f5598c56e77bd28b9ff15a30802 +cbb184dd8e05c9709e5dcaedaa0495cf + +2c953c12d2eb6ea958b7f3045ecf8e81 +864d4d5a0cd65f52d791700443cec75e +1b2437750694bba602fedc0a568c65de +cbb184dd8e05c9709e5dcaedaa0495cf + +2c953c12d2eb6ea958b7f3045ecf8e81 +d18a921e891f6f9af8564a882efea289 +8bc5ef2851a7dcf1cf096a68e2a47ba6 +cbb184dd8e05c9709e5dcaedaa0495cf + +2c953c12d2eb6ea958b7f3045ecf8e81 +9cf0f8aa69740d88788fb437589ed33f +0df2777822bbc7799716a10478ca58d4 +cbb184dd8e05c9709e5dcaedaa0495cf +cbb184dd8e05c9709e5dcaedaa0495cf diff --git a/sonar-batch/src/test/resources/org/sonar/batch/cache/ProjectCacheSynchronizerTest/api_sources_hash_JGitBlameCommand.text b/sonar-batch/src/test/resources/org/sonar/batch/cache/ProjectCacheSynchronizerTest/api_sources_hash_JGitBlameCommand.text new file mode 100644 index 00000000000..e21a25bd7b6 --- /dev/null +++ b/sonar-batch/src/test/resources/org/sonar/batch/cache/ProjectCacheSynchronizerTest/api_sources_hash_JGitBlameCommand.text @@ -0,0 +1,149 @@ +523048e7f5ca9550505f2d8ea6d587e7 +50ff1975ec4309da19591231c6b5104b +eba1d423f8632818ce94c4eac1b90713 +6112a40c70ed55453a0753030d5564a4 +3389dae361af79b04c9c8e7057f60cc6 +eac5fc1130394e7268b1cfbc54cd7e4d +c0b153d8c08365f2de343e278d3b54c7 +eb4521cb5d193e1d37ecac25b0ffea43 +9210ed0dec59ed663c744d7fb68f0275 +3389dae361af79b04c9c8e7057f60cc6 +cd0fbdfa49d32525ecbdb8dab19dafe6 +ea12a10f5b7730daa639fe133867e088 +69739b9bc9312dfb1a6b8625a08c652a +ec21e054f7f5748d0161fe27cdad6462 +3389dae361af79b04c9c8e7057f60cc6 +951a83e8074813100da0cba92092b385 +c93caecd79a332773cfb06cd5d3b8895 +5832d52d5fcb22a3350f62c856993f0d +c4c9bdd47ee05028cb84873da0ebf2b5 +f89e422b117e518acef69df33f199d10 + +9e0ae10d6ada18721c856844d765b465 +ea3c894506f93b88c9fc6c9790da9008 +c5c303a0f47f5f15f22b6776fc1c8c93 +4f592acdcfc11c97e7f19231de9d69b0 +6aea6951956275cb62d01063a1e695fe +293f7a3f08e54359c17d5e984f721665 +18d24bd6a2c2c15d3914502e2776e372 +107e08f15be7e18888da7e69948ac3ba +90aa2aae2384f6412c3b86d085d5ffa5 +ef76944333105582ae8d3a51d29b3b8a +2a592c3d07126847ae4cbbed4a2b4d46 +6f382821d6f35beb6ae4080607046898 + +943d54ba3e8812437c4d26ef8aa263f8 +6fa05171389dfbeda44181c98e580d18 +391715e38dad3a13b75205d527e82c8a +bb3900a63a8cecc1e79592e054915c97 +7c8d40302b1200413bc859331a4f241d +0e2ad2ad1ad56b970e4348a3967d1e81 +049ace1ff1516be8a5fd7cddc0ab2f30 +d2c44db3922004ac2ae41fb402d005b1 +b0ba7766e9e1fddaf40e86b62a5c2900 + +62e220c0092e8ae61f5937f18e4b03bc + +a5eeb3bd06bd4499f8a9ad20ba426bbd + +2bb44a6b46970b3efd87cc8a68848fae + +5b9e24bad64529f3e35ba4f1aed892f2 +78f4e1ffbbf29629025b20f6b1be36f5 +cbb184dd8e05c9709e5dcaedaa0495cf + +2c953c12d2eb6ea958b7f3045ecf8e81 +0f253056876c021c4fd3f3e5ddc4d5b6 +520e98566046a173f9250aa3b7a40ec9 +c31394024dee65cba7e5c526c69af278 +80d5b17efc16ace990c07580fc3e85eb +d28bc6d296024d650b16efe1369128b0 +38ba857d93d3a54a6b7f1bfc1b8fb090 +d41f14cd3267e8d9c17b47ddcb71b0c1 +54f339c05c18199eca937a31fdc07857 +7efc34bf8e2ba01cec26c50875ac8acd +5a29aee8cfe3a5110dc892ab8adfc17f +d271e10accbea4cb6365b85150505b0d +cbb184dd8e05c9709e5dcaedaa0495cf +cbb184dd8e05c9709e5dcaedaa0495cf + +319f70c2d340b01002c3539d09dabbec +b225a5ae163bfc56684d172522993825 +791c95e71dd996b4d723f96df3f37ca4 +80d5b17efc16ace990c07580fc3e85eb +2a8af480cbdcc0ee9b44187a078c8fe7 +4340b548cda0dbc6043cbf4cf49d1b12 +2df758e8d85494e7ee23a02d4c3aa6a7 +e44feb14b61ab99767239eaded464459 +eb375774c265dedeefeb29283ceea9bc +c87c662bc284c5f9c01d3551957ee32e +cbb184dd8e05c9709e5dcaedaa0495cf +cbb184dd8e05c9709e5dcaedaa0495cf +cbb184dd8e05c9709e5dcaedaa0495cf + +1fcefdfae441ded6478da69428a30f12 +8fcd1ffa896d0e214539b1bfa179f3e8 +7a64b5f6e57cc9f13d5a1be99b9f7c23 +8884d72adb8d93474c6af620a7d6fdba +cbb184dd8e05c9709e5dcaedaa0495cf +7e53b1d2085d8c7ae88417eccc5a0893 +cbb184dd8e05c9709e5dcaedaa0495cf + +5f68d8059a28922be107658c3890c230 +6ded45bc62f1d535345e67001dac69d5 +34165242e2230ffca31d7942ec577e6b +02f5ec563dbaaef9ef402c6941a36c98 +784b65e21ca0539f3cefb1710ccc7768 +982e86cf85e0f121ba1a2f0ef462aa6b +cbb184dd8e05c9709e5dcaedaa0495cf +80d5b17efc16ace990c07580fc3e85eb +34c0d764eb79cc7c6dccb53e711fd4be +a5b2391dd7127292b7240c7c8c1ee92a +54d7949984c901073fffda9956190c12 +da38c234aa3fdca9ec1e0e2b991d3568 +6b2decb38be3882440910fd75ec508cd +ce51581950deb12616108c0e909f9c53 +cbb184dd8e05c9709e5dcaedaa0495cf +cbb184dd8e05c9709e5dcaedaa0495cf + +e8165f1ae4cf11035542d4b60ac7b14d +c8272ad357e7feaf2671a0612e52d3b5 +2c953c12d2eb6ea958b7f3045ecf8e81 +209935c0f7c635d91164fe2b14314a3d +448b1c0a64288dbeac516ba67c9f2574 +540c13e9e156b687226421b24f2df178 +cbb184dd8e05c9709e5dcaedaa0495cf +c0c97e22db5055def551d1cef15fb251 +cbb184dd8e05c9709e5dcaedaa0495cf + +8983f749749a87599ea48c8bc08cac86 +f298d78afda5708f64ded32af0e7f541 +248e216059cad9c12bc8ff8b3b6289e2 +80d5b17efc16ace990c07580fc3e85eb +318ddd60a422c3d0435acd5f7aa6923c +7c3bca9656325d15227ffac4ce5dfde9 +7d992ab2241888573c1c7bdfe5d33a35 +cd620097a91b074942d29535e822ebb0 +a50e423de97896afb97123424a960664 +35bb4ed816814a612f9605aec97c69fb +cbb184dd8e05c9709e5dcaedaa0495cf +280db1f26dcc577de8fc62d40627112b +e5b027822061ae041ced3d958b6f7f37 +11a0dde589dec02e8d95c4ea6d83780f +e5acab4c66ae60432ccc5a45d718d152 +3c4b4570f7e4c7037693b1aa1fd5a9ca +420d9af4c91e3248bb2a4e72a683a03a +d69bf14adaf9ea45b48c0fdfefa4f69d +505b97969baa28c3f607a38ee02f4f2d +cbb184dd8e05c9709e5dcaedaa0495cf +da94121a62e940229bd622927ad7702f +9d2a20a55b185c4a864efd3484a870fa +cbb184dd8e05c9709e5dcaedaa0495cf +daada2d57491a2d0b4d237f29fc039dc +53220ad5b69915dec696a01167d5237b +2fb05fbd558fb020eacca16faa325246 +cbb184dd8e05c9709e5dcaedaa0495cf +423c485e2882c1fd9a1b19983b812f50 +cbb184dd8e05c9709e5dcaedaa0495cf + +cbb184dd8e05c9709e5dcaedaa0495cf diff --git a/sonar-batch/src/test/resources/org/sonar/batch/cache/ProjectCacheSynchronizerTest/batch_issues.protobuf b/sonar-batch/src/test/resources/org/sonar/batch/cache/ProjectCacheSynchronizerTest/batch_issues.protobuf Binary files differnew file mode 100644 index 00000000000..8b610d8f73c --- /dev/null +++ b/sonar-batch/src/test/resources/org/sonar/batch/cache/ProjectCacheSynchronizerTest/batch_issues.protobuf diff --git a/sonar-batch/src/test/resources/org/sonar/batch/cache/ProjectCacheSynchronizerTest/batch_project.json b/sonar-batch/src/test/resources/org/sonar/batch/cache/ProjectCacheSynchronizerTest/batch_project.json new file mode 100644 index 00000000000..2887ce18d10 --- /dev/null +++ b/sonar-batch/src/test/resources/org/sonar/batch/cache/ProjectCacheSynchronizerTest/batch_project.json @@ -0,0 +1,164 @@ +{ + "timestamp": 0, + "qprofilesByLanguage": { + "java": { + "key": "java-sonar-way-72608", + "name": "Sonar way", + "language": "java", + "rulesUpdatedAt": "2015-08-10T12:06:53+0200" + } + }, + "activeRules": [ + { + "repositoryKey": "common-java", + "ruleKey": "DuplicatedBlocks", + "name": "Source files should not have any duplicated blocks", + "severity": "MAJOR", + "language": "java", + "params": {} + }, + { + "repositoryKey": "common-java", + "ruleKey": "InsufficientBranchCoverage", + "name": "Branches should have sufficient coverage by unit tests", + "severity": "MAJOR", + "language": "java", + "params": { + "minimumBranchCoverageRatio": "65.0" + } + }, + { + "repositoryKey": "squid", + "ruleKey": "RightCurlyBraceStartLineCheck", + "name": "A close curly brace should be located at the beginning of a line", + "severity": "MINOR", + "internalKey": "RightCurlyBraceStartLineCheck", + "language": "java", + "params": {} + }, + { + "repositoryKey": "squid", + "ruleKey": "UselessParenthesesCheck", + "name": "Useless parentheses around expressions should be removed to prevent any misunderstanding", + "severity": "MAJOR", + "internalKey": "UselessParenthesesCheck", + "language": "java", + "params": {} + }, + { + "repositoryKey": "squid", + "ruleKey": "ObjectFinalizeCheck", + "name": "The Object.finalize() method should not be called", + "severity": "CRITICAL", + "internalKey": "ObjectFinalizeCheck", + "language": "java", + "params": {} + }, + { + "repositoryKey": "squid", + "ruleKey": "ObjectFinalizeOverridenCheck", + "name": "The Object.finalize() method should not be overriden", + "severity": "CRITICAL", + "internalKey": "ObjectFinalizeOverridenCheck", + "language": "java", + "params": {} + }, + { + "repositoryKey": "squid", + "ruleKey": "ObjectFinalizeOverridenCallsSuperFinalizeCheck", + "name": "super.finalize() should be called at the end of Object.finalize() implementations", + "severity": "BLOCKER", + "internalKey": "ObjectFinalizeOverridenCallsSuperFinalizeCheck", + "language": "java", + "params": {} + }, + { + "repositoryKey": "squid", + "ruleKey": "ClassVariableVisibilityCheck", + "name": "Class variable fields should not have public accessibility", + "severity": "MAJOR", + "internalKey": "ClassVariableVisibilityCheck", + "language": "java", + "params": {} + }, + { + "repositoryKey": "squid", + "ruleKey": "S2188", + "name": "JUnit test cases should call super methods", + "severity": "CRITICAL", + "internalKey": "S2188", + "language": "java", + "params": {} + }, + { + "repositoryKey": "squid", + "ruleKey": "S2186", + "name": "JUnit assertions should not be used in \"run\" methods", + "severity": "CRITICAL", + "internalKey": "S2186", + "language": "java", + "params": {} + }, + { + "repositoryKey": "squid", + "ruleKey": "S2187", + "name": "TestCases should contain tests", + "severity": "MAJOR", + "internalKey": "S2187", + "language": "java", + "params": {} + }, + { + "repositoryKey": "squid", + "ruleKey": "S2391", + "name": "JUnit framework methods should be declared properly", + "severity": "CRITICAL", + "internalKey": "S2391", + "language": "java", + "params": {} + }, + { + "repositoryKey": "squid", + "ruleKey": "S2325", + "name": "\"private\" methods that don\u0027t access instance data should be \"static\"", + "severity": "MINOR", + "internalKey": "S2325", + "language": "java", + "params": {} + }, + { + "repositoryKey": "squid", + "ruleKey": "S1166", + "name": "Exception handlers should preserve the original exception", + "severity": "CRITICAL", + "internalKey": "S1166", + "language": "java", + "params": { + "exceptions": "java.lang.InterruptedException, java.lang.NumberFormatException, java.text.ParseException, java.net.MalformedURLException" + } + }, + { + "repositoryKey": "squid", + "ruleKey": "S2970", + "name": "Assertions should be complete", + "severity": "CRITICAL", + "internalKey": "S2970", + "language": "java", + "params": {} + } + + ], + "settingsByModule": {}, + "fileDataByModuleAndPath": { + "org.codehaus.sonar-plugins:sonar-scm-git-plugin": { + "src/test/java/org/sonar/plugins/scm/git/JGitBlameCommandTest.java": { + "needBlame": true + }, + "src/main/java/org/sonar/plugins/scm/git/GitScmProvider.java": { + "hash": "90082117d0dc0f1189ab7e4990a20667", + "needBlame": true + } + } + }, + "lastAnalysisDate": "2015-08-10T13:20:09+0200" +}
\ No newline at end of file diff --git a/sonar-batch/src/test/resources/org/sonar/batch/cache/ProjectCacheSynchronizerTest/batch_users.protobuf b/sonar-batch/src/test/resources/org/sonar/batch/cache/ProjectCacheSynchronizerTest/batch_users.protobuf new file mode 100644 index 00000000000..e69de29bb2d --- /dev/null +++ b/sonar-batch/src/test/resources/org/sonar/batch/cache/ProjectCacheSynchronizerTest/batch_users.protobuf diff --git a/sonar-batch/src/test/resources/org/sonar/batch/index/FileHashesPersisterTest/should_persist_component_data-result.xml b/sonar-batch/src/test/resources/org/sonar/batch/index/FileHashesPersisterTest/should_persist_component_data-result.xml deleted file mode 100644 index b710ad3c272..00000000000 --- a/sonar-batch/src/test/resources/org/sonar/batch/index/FileHashesPersisterTest/should_persist_component_data-result.xml +++ /dev/null @@ -1,4 +0,0 @@ -<dataset> - <snapshots id="100" project_id="200" islast="[true]"/> - <snapshot_data id="1" snapshot_id="100" resource_id="200" snapshot_data="org/struts/Action.java=123ABC" data_type="file_hashes" created_at="[null]" updated_at="[null]"/> -</dataset>
\ No newline at end of file diff --git a/sonar-batch/src/test/resources/org/sonar/batch/index/FileHashesPersisterTest/should_persist_component_data.xml b/sonar-batch/src/test/resources/org/sonar/batch/index/FileHashesPersisterTest/should_persist_component_data.xml deleted file mode 100644 index a83b8aad101..00000000000 --- a/sonar-batch/src/test/resources/org/sonar/batch/index/FileHashesPersisterTest/should_persist_component_data.xml +++ /dev/null @@ -1,3 +0,0 @@ -<dataset> - <snapshots id="100" project_id="200" islast="[true]"/> -</dataset>
\ No newline at end of file |