From 80ab9a5b973883cd41d758ea3dd01b9eab11a3bd Mon Sep 17 00:00:00 2001 From: Stefan Wolf Date: Sun, 21 Aug 2011 20:49:12 +0200 Subject: [PATCH] Refactoring, Unit test and localization --- .gitignore | 6 ++ pom.xml | 38 +++---- .../plugins/all_changes/AllChangesAction.java | 17 ++- .../all_changes/ChangesAggregator.java | 4 +- .../DependencyChangesAggregator.java | 4 +- .../SubProjectChangesAggregator.java | 74 ++++++++----- src/main/resources/index.jelly | 2 +- .../all_changes/AllChangesAction/index.groovy | 100 ++++++++++-------- .../AllChangesAction/index.properties | 25 +++++ .../AllChangesAction/index_de.properties | 8 +- .../AllChangesAction/index_es.properties | 29 +++++ .../all_changes/Messages_es.properties | 25 +++++ .../all_changes/AllChangesActionTest.java | 95 +++++++++++++++++ 13 files changed, 320 insertions(+), 107 deletions(-) create mode 100644 .gitignore create mode 100644 src/main/resources/org/jenkins/plugins/all_changes/AllChangesAction/index.properties create mode 100644 src/main/resources/org/jenkins/plugins/all_changes/AllChangesAction/index_es.properties create mode 100644 src/main/resources/org/jenkins/plugins/all_changes/Messages_es.properties create mode 100644 src/test/java/org/jenkins/plugins/all_changes/AllChangesActionTest.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fc09677 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +work/ +target/ +.project +.classpath +.settings/ +*.iml \ No newline at end of file diff --git a/pom.xml b/pom.xml index 5023685..27c3f23 100644 --- a/pom.xml +++ b/pom.xml @@ -63,9 +63,22 @@ true - org.codehaus.groovy.maven.runtime - gmaven-runtime-1.6 - 1.0 + org.jenkins-ci.plugins + token-macro + 1.4 + true + + + org.jenkins-ci.plugins + email-ext + 2.14 + true + + + org.mockito + mockito-core + 1.8.5 + test @@ -84,23 +97,4 @@ artifacts that we need --> http://maven.glassfish.org/content/groups/public/ - - - - org.codehaus.groovy.maven - gmaven-plugin - 1.0 - - - - generateStubs - compile - generateTestStubs - testCompile - - - - - - diff --git a/src/main/java/org/jenkins/plugins/all_changes/AllChangesAction.java b/src/main/java/org/jenkins/plugins/all_changes/AllChangesAction.java index f391bc4..3f3d1c0 100644 --- a/src/main/java/org/jenkins/plugins/all_changes/AllChangesAction.java +++ b/src/main/java/org/jenkins/plugins/all_changes/AllChangesAction.java @@ -26,6 +26,7 @@ import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.HashMultimap; +import com.google.common.collect.ImmutableList; import com.google.common.collect.Multimap; import com.google.common.collect.Sets; import hudson.model.AbstractBuild; @@ -33,6 +34,7 @@ import hudson.model.Action; import hudson.scm.ChangeLogSet; +import java.util.List; import java.util.Set; /** @@ -44,6 +46,8 @@ public class AllChangesAction implements Action { private AbstractProject project; + transient + List aggregators; AllChangesAction(AbstractProject project) { this.project = project; @@ -67,7 +71,7 @@ public String getUrlName() { * @param build * @return */ - public static Multimap getAllChanges(AbstractBuild build) { + public Multimap getAllChanges(AbstractBuild build) { Set builds = getContributingBuilds(build); Multimap changes = ArrayListMultimap.create(); for (AbstractBuild changedBuild : builds) { @@ -91,7 +95,10 @@ public static Multimap getAllChanges(Abstract * * @return all changes which contribute to the given build */ - public static Set getContributingBuilds(AbstractBuild build) { + public Set getContributingBuilds(AbstractBuild build) { + if (aggregators == null) { + aggregators = ImmutableList.copyOf(ChangesAggregator.all()); + } Set builds = Sets.newHashSet(); builds.add(build); int size = 0; @@ -99,9 +106,9 @@ public static Set getContributingBuilds(AbstractBuild build) { do { size = builds.size(); Set newBuilds = Sets.newHashSet(); - for (ChangesAggregator aggregator : ChangesAggregator.all()) { - for (AbstractBuild abstractBuild : builds) { - newBuilds.addAll(aggregator.aggregateBuildsWithChanges(build)); + for (ChangesAggregator aggregator : aggregators) { + for (AbstractBuild depBuild : builds) { + newBuilds.addAll(aggregator.aggregateBuildsWithChanges(depBuild)); } } builds.addAll(newBuilds); diff --git a/src/main/java/org/jenkins/plugins/all_changes/ChangesAggregator.java b/src/main/java/org/jenkins/plugins/all_changes/ChangesAggregator.java index 1576885..1024c7a 100644 --- a/src/main/java/org/jenkins/plugins/all_changes/ChangesAggregator.java +++ b/src/main/java/org/jenkins/plugins/all_changes/ChangesAggregator.java @@ -29,10 +29,10 @@ import hudson.model.AbstractBuild; import jenkins.model.Jenkins; -import java.util.List; +import java.util.Collection; public abstract class ChangesAggregator implements ExtensionPoint { - public abstract List aggregateBuildsWithChanges(AbstractBuild build); + public abstract Collection aggregateBuildsWithChanges(AbstractBuild build); public static ExtensionList all() { return Jenkins.getInstance().getExtensionList(ChangesAggregator.class); diff --git a/src/main/java/org/jenkins/plugins/all_changes/DependencyChangesAggregator.java b/src/main/java/org/jenkins/plugins/all_changes/DependencyChangesAggregator.java index b655dd6..564907f 100644 --- a/src/main/java/org/jenkins/plugins/all_changes/DependencyChangesAggregator.java +++ b/src/main/java/org/jenkins/plugins/all_changes/DependencyChangesAggregator.java @@ -29,7 +29,7 @@ import hudson.model.AbstractBuild; import hudson.model.AbstractProject; -import java.util.List; +import java.util.Collection; import java.util.Map; /** @@ -38,7 +38,7 @@ @Extension public class DependencyChangesAggregator extends ChangesAggregator { @Override - public List aggregateBuildsWithChanges(AbstractBuild build) { + public Collection aggregateBuildsWithChanges(AbstractBuild build) { ImmutableList.Builder builder = ImmutableList.builder(); Map depChanges = build.getDependencyChanges((AbstractBuild) build.getPreviousBuild()); for (AbstractBuild.DependencyChange depChange : depChanges.values()) { diff --git a/src/main/java/org/jenkins/plugins/all_changes/SubProjectChangesAggregator.java b/src/main/java/org/jenkins/plugins/all_changes/SubProjectChangesAggregator.java index 76be91b..b253857 100644 --- a/src/main/java/org/jenkins/plugins/all_changes/SubProjectChangesAggregator.java +++ b/src/main/java/org/jenkins/plugins/all_changes/SubProjectChangesAggregator.java @@ -24,6 +24,8 @@ package org.jenkins.plugins.all_changes; +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; import hudson.Extension; import hudson.model.AbstractBuild; import hudson.model.AbstractProject; @@ -34,8 +36,9 @@ import hudson.tasks.Builder; import hudson.util.RunList; -import java.util.ArrayList; +import java.util.Collection; import java.util.List; +import java.util.Set; /** * @author wolfs @@ -43,42 +46,59 @@ @Extension public class SubProjectChangesAggregator extends ChangesAggregator { @Override - public List aggregateBuildsWithChanges(AbstractBuild build) { + public Collection aggregateBuildsWithChanges(AbstractBuild build) { AbstractProject project = build.getProject(); - List> subProjects = new ArrayList>(); + Set> subProjects = Sets.newHashSet(); + Set builds = Sets.newHashSet(); if (project instanceof FreeStyleProject) { - FreeStyleProject proj = (FreeStyleProject) project; - List builders = proj.getBuilders(); - for (Builder builder : builders) { - if (builder instanceof TriggerBuilder) { - TriggerBuilder tBuilder = (TriggerBuilder) builder; - for (BlockableBuildTriggerConfig config : tBuilder.getConfigs()) { - for (AbstractProject abstractProject : config.getProjectList()) { - if (config.getBlock() != null) { - subProjects.add(abstractProject); - } - } - } - } - } + subProjects.addAll(getSubProjects((FreeStyleProject) project)); + builds.addAll(getTriggeredBuilds(build, subProjects)); } - ArrayList builds = new ArrayList(); + + return builds; + } + + private List getTriggeredBuilds(AbstractBuild build, Collection> subProjects) { + List builds = Lists.newArrayList(); for (AbstractProject subProject : subProjects) { RunList> subBuildsDuringBuild = subProject.getBuilds().byTimestamp(build.getTimeInMillis(), build.getTimeInMillis() + build.getDuration()); - for (AbstractBuild subBuild : subBuildsDuringBuild) { - List upstreamCauses = new ArrayList(); - List causes = subBuild.getCauses(); - for (Cause cause : causes) { - if (cause instanceof Cause.UpstreamCause) { - Cause.UpstreamCause upstreamCause = (Cause.UpstreamCause) cause; - if (upstreamCause.pointsTo(build)) { - builds.add(subBuild); + for (AbstractBuild candidate : subBuildsDuringBuild) { + if (isSubBuild(build, candidate)) { + builds.add(candidate); + } + } + } + return builds; + } + + private List> getSubProjects(FreeStyleProject project) { + List> subProjects = Lists.newArrayList(); + List builders = project.getBuilders(); + for (Builder builder : builders) { + if (builder instanceof TriggerBuilder) { + TriggerBuilder tBuilder = (TriggerBuilder) builder; + for (BlockableBuildTriggerConfig config : tBuilder.getConfigs()) { + for (AbstractProject abstractProject : config.getProjectList()) { + if (config.getBlock() != null) { + subProjects.add(abstractProject); } } } } } + return subProjects; + } - return builds; + private boolean isSubBuild(AbstractBuild build, AbstractBuild subBuild) { + List causes = subBuild.getCauses(); + for (Cause cause : causes) { + if (cause instanceof Cause.UpstreamCause) { + Cause.UpstreamCause upstreamCause = (Cause.UpstreamCause) cause; + if (upstreamCause.pointsTo(build)) { + return true; + } + } + } + return false; } } diff --git a/src/main/resources/index.jelly b/src/main/resources/index.jelly index b2b8903..4eff4f9 100644 --- a/src/main/resources/index.jelly +++ b/src/main/resources/index.jelly @@ -26,5 +26,5 @@ This view is used to render the installed plugins page. -->
- This plugin shows all changes (also from dependent projects, subprojects, ...) for a project + This plugin shows all changes (also from dependent projects, sub-projects, ...) for a project
diff --git a/src/main/resources/org/jenkins/plugins/all_changes/AllChangesAction/index.groovy b/src/main/resources/org/jenkins/plugins/all_changes/AllChangesAction/index.groovy index e3aaa8c..0e2a4c6 100644 --- a/src/main/resources/org/jenkins/plugins/all_changes/AllChangesAction/index.groovy +++ b/src/main/resources/org/jenkins/plugins/all_changes/AllChangesAction/index.groovy @@ -30,8 +30,6 @@ import hudson.model.AbstractBuild import hudson.model.AbstractBuild.DependencyChange import hudson.scm.ChangeLogSet import java.text.DateFormat -import org.apache.commons.jelly.XMLOutput -import org.dom4j.io.SAXContentHandler import org.jvnet.localizer.LocaleProvider f = namespace(lib.FormTagLib) @@ -39,15 +37,7 @@ l = namespace(lib.LayoutTagLib) t = namespace("/lib/hudson") st = namespace("jelly:stapler") -private def wrapOutput(Closure viewInstructions) { - def sc = new SAXContentHandler() - def old = setOutput(new XMLOutput(sc)) - viewInstructions(); - setOutput(old); - return sc -} - -l.layout(title: _("All Changes")) { +l.layout(title: _("all.changes.title", my.project.name)) { st.include(page: "sidepanel.jelly", it: my.project) l.main_panel() { def from = request.getParameter('from') @@ -55,45 +45,65 @@ l.layout(title: _("All Changes")) { h1(_("All Changes")) def builds = Functions.filter(my.project.buildsAsMap, from, to).values() - for (build in builds) { - Multimap changes = my.getAllChanges(build); - if (changes.empty) { - continue - } - h2() { - a(href: "${my.project.absoluteUrl}/${build.number}/changes", - "${build.displayName} (${DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM, LocaleProvider.locale).format(build.timestamp.time)})") - } - ol() { - for (entry in changes.keySet()) { - li() { - showChangeSet(entry) - boolean firstDrawn = false - for (AbstractBuild b in changes.get(entry)) { - if (b != build) { - if (!firstDrawn) { - text(" (") - firstDrawn = true - } - else { - text(", ") - } - a(href: "${rootURL}/${b.project.url}") {text(b.project.displayName)} - st.nbsp() - a(href: "${rootURL}/${b.url}") { - text(b.displayName) - } - } - } - if (firstDrawn) { - text(")") - } + if (builds.empty) { + text(_("No builds.")) + } else { + showChanges(builds) + } + } +} - } +private showChanges(Collection builds) { + boolean hadChanges = false; + for (AbstractBuild build in builds) { + Multimap changes = my.getAllChanges(build); + if (changes.empty) { + continue + } + hadChanges = true + h2() { + a(href: "${my.project.absoluteUrl}/${build.number}/changes", + """${build.displayName} (${ + DateFormat.getDateTimeInstance( + DateFormat.MEDIUM, + DateFormat.MEDIUM, + LocaleProvider.locale).format(build.timestamp.time)})""") + } + ol() { + for (entry in changes.keySet()) { + li() { + showEntry(entry, build, changes.get(entry)) } } } } + if (!hadChanges) { + text(_("No changes in any of the builds.")) + } +} + +private def showEntry(entry, AbstractBuild build, Collection builds) { + showChangeSet(entry) + boolean firstDrawn = false + for (AbstractBuild b in builds) { + if (b != build) { + if (!firstDrawn) { + text(" (") + firstDrawn = true + } + else { + text(", ") + } + a(href: "${rootURL}/${b.project.url}") {text(b.project.displayName)} + st.nbsp() + a(href: "${rootURL}/${b.url}") { + text(b.displayName) + } + } + } + if (firstDrawn) { + text(")") + } } private def showChangeSet(ChangeLogSet.Entry c) { diff --git a/src/main/resources/org/jenkins/plugins/all_changes/AllChangesAction/index.properties b/src/main/resources/org/jenkins/plugins/all_changes/AllChangesAction/index.properties new file mode 100644 index 0000000..34c3a83 --- /dev/null +++ b/src/main/resources/org/jenkins/plugins/all_changes/AllChangesAction/index.properties @@ -0,0 +1,25 @@ +# +# The MIT License +# +# Copyright (c) 2011, Stefan Wolf +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# + +all.changes.title={0} All Changes diff --git a/src/main/resources/org/jenkins/plugins/all_changes/AllChangesAction/index_de.properties b/src/main/resources/org/jenkins/plugins/all_changes/AllChangesAction/index_de.properties index 9a3168c..b2ba0b0 100644 --- a/src/main/resources/org/jenkins/plugins/all_changes/AllChangesAction/index_de.properties +++ b/src/main/resources/org/jenkins/plugins/all_changes/AllChangesAction/index_de.properties @@ -22,6 +22,8 @@ # THE SOFTWARE. # -Changes=Ānderungen -detail=Detail -All\ Changes=Alle Ānderungen +all.changes.title=Alle \u00c4nderungen in {0} +detail=Details +All\ Changes=Alle \u00c4nderungen +No\ builds.=Keine Builds. +No\ changes\ in\ any\ of\ the\ builds.=Keine \u00c4nderungen in den Builds. diff --git a/src/main/resources/org/jenkins/plugins/all_changes/AllChangesAction/index_es.properties b/src/main/resources/org/jenkins/plugins/all_changes/AllChangesAction/index_es.properties new file mode 100644 index 0000000..4fb4d82 --- /dev/null +++ b/src/main/resources/org/jenkins/plugins/all_changes/AllChangesAction/index_es.properties @@ -0,0 +1,29 @@ +# +# The MIT License +# +# Copyright (c) 2011, Stefan Wolf +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# + +all.changes.title=Todos los cambios de {0} +All\ Changes=Todos los cambios +No\ builds.=Sin ejecuciones. +detail=detalles +No\ changes\ in\ any\ of\ the\ builds.=No hay nuevos cambios en ninguna ejecuci\u00f3n. \ No newline at end of file diff --git a/src/main/resources/org/jenkins/plugins/all_changes/Messages_es.properties b/src/main/resources/org/jenkins/plugins/all_changes/Messages_es.properties new file mode 100644 index 0000000..6c3b02b --- /dev/null +++ b/src/main/resources/org/jenkins/plugins/all_changes/Messages_es.properties @@ -0,0 +1,25 @@ +# +# The MIT License +# +# Copyright (c) 2011, Stefan Wolf +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# + +AllChanges.allChanges=Todos los cambios \ No newline at end of file diff --git a/src/test/java/org/jenkins/plugins/all_changes/AllChangesActionTest.java b/src/test/java/org/jenkins/plugins/all_changes/AllChangesActionTest.java new file mode 100644 index 0000000..2be925a --- /dev/null +++ b/src/test/java/org/jenkins/plugins/all_changes/AllChangesActionTest.java @@ -0,0 +1,95 @@ +/* + * The MIT License + * + * Copyright (c) 2011, Stefan Wolf + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package org.jenkins.plugins.all_changes; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Lists; +import hudson.model.AbstractBuild; +import org.junit.Test; + +import java.util.Set; + +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.*; + +/** + * @author wolfs + */ +public class AllChangesActionTest { + + @Test + public void getContributingBuildsShouldWorkTransitively() throws Exception { + AllChangesAction changesAction = new AllChangesAction(null); + ChangesAggregator aggregatorMock = mock(ChangesAggregator.class); + AbstractBuild build = mock(AbstractBuild.class); + AbstractBuild build2 = mock(AbstractBuild.class); + AbstractBuild build3 = mock(AbstractBuild.class); + when(aggregatorMock.aggregateBuildsWithChanges(build)).thenReturn(ImmutableList.of(build2)); + when(aggregatorMock.aggregateBuildsWithChanges(build2)).thenReturn(ImmutableList.of(build3)); + + changesAction.aggregators = Lists.newArrayList(aggregatorMock); + + Set foundBuilds = changesAction.getContributingBuilds(build); + + assertTrue(foundBuilds.equals(ImmutableSet.of(build, build2, build3))); + } + + @Test + public void getContributingBuildsShouldWorkHandleCycles() throws Exception { + AllChangesAction changesAction = new AllChangesAction(null); + ChangesAggregator aggregatorMock = mock(ChangesAggregator.class); + AbstractBuild build = mock(AbstractBuild.class); + AbstractBuild build2 = mock(AbstractBuild.class); + AbstractBuild build3 = mock(AbstractBuild.class); + when(aggregatorMock.aggregateBuildsWithChanges(build)).thenReturn(ImmutableList.of(build2)); + when(aggregatorMock.aggregateBuildsWithChanges(build2)).thenReturn(ImmutableList.of(build3)); + when(aggregatorMock.aggregateBuildsWithChanges(build3)).thenReturn(ImmutableList.of(build)); + + changesAction.aggregators = Lists.newArrayList(aggregatorMock); + + Set foundBuilds = changesAction.getContributingBuilds(build); + + assertTrue(foundBuilds.equals(ImmutableSet.of(build, build2, build3))); + } + + @Test + public void getContributingBuildsWorksWithMoreThanOneAggregator() throws Exception { + AllChangesAction changesAction = new AllChangesAction(null); + ChangesAggregator aggregatorMock = mock(ChangesAggregator.class); + ChangesAggregator aggregatorMock2 = mock(ChangesAggregator.class); + AbstractBuild build = mock(AbstractBuild.class); + AbstractBuild build2 = mock(AbstractBuild.class); + AbstractBuild build3 = mock(AbstractBuild.class); + when(aggregatorMock.aggregateBuildsWithChanges(build)).thenReturn(ImmutableList.of(build2)); + when(aggregatorMock2.aggregateBuildsWithChanges(build2)).thenReturn(ImmutableList.of(build3)); + + changesAction.aggregators = Lists.newArrayList(aggregatorMock, aggregatorMock2); + + Set foundBuilds = changesAction.getContributingBuilds(build); + + assertTrue(foundBuilds.equals(ImmutableSet.of(build, build2, build3))); + } +}