Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make Pipeline jobs get dependency changes #6

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -81,12 +81,42 @@
<artifactId>workflow-job</artifactId>
<version>1.14</version>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-library</artifactId>
<version>1.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-core</artifactId>
<version>1.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>1.10.19</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>1.6.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito</artifactId>
<version>1.6.4</version>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,11 @@

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;
import hudson.model.Action;
import hudson.model.Run;
import hudson.scm.ChangeLogSet;
import org.jenkinsci.plugins.workflow.job.WorkflowJob;
import org.jenkinsci.plugins.workflow.job.WorkflowRun;
Expand All @@ -40,6 +41,7 @@ public class AllChangesWorkflowAction implements Action {

private WorkflowJob project;
private int numChanges = 0;
private DependencyChangesAggregator aggregator;

AllChangesWorkflowAction(WorkflowJob project) {
this.project = project;
Expand All @@ -63,40 +65,69 @@ public String getUrlName() {
}

/**
* Returns all changes which contribute to a build.
* Returns all changes which contribute to the given build.
*
* @param build
* @return
* @param build the build from which to get dependency changes
* @return a map of change log sets to builds or empty map if none were found
*/
public Multimap<ChangeLogSet.Entry, WorkflowRun> getAllChanges(WorkflowRun build) {
Set<WorkflowRun> builds = getContributingBuilds(build);
public Multimap<ChangeLogSet.Entry, Run> getAllChanges(WorkflowRun build) {

Set<Run> builds = getContributingBuilds(build);

Multimap<String, ChangeLogSet.Entry> changes = ArrayListMultimap.create();
for (WorkflowRun changedBuild : builds) {
for (ChangeLogSet changeLogSet : changedBuild.getChangeSets()) {
ChangeLogSet<ChangeLogSet.Entry> changeSet = (ChangeLogSet<ChangeLogSet.Entry>)changeLogSet;
for (Run changedBuild : builds) {
if (changedBuild instanceof WorkflowRun) {
for (ChangeLogSet changeLogSet : ((WorkflowRun) changedBuild).getChangeSets()) {
ChangeLogSet<ChangeLogSet.Entry> changeSet = (ChangeLogSet<ChangeLogSet.Entry>)changeLogSet;
for (ChangeLogSet.Entry entry : changeSet) {
changes.put(entry.getCommitId() + entry.getMsgAnnotated() + entry.getTimestamp(), entry);
}
}
} else if (changedBuild instanceof AbstractBuild) {
ChangeLogSet<ChangeLogSet.Entry> changeSet = ((AbstractBuild) changedBuild).getChangeSet();
for (ChangeLogSet.Entry entry : changeSet) {
changes.put(entry.getCommitId() + entry.getMsgAnnotated() + entry.getTimestamp(), entry);
}
}
}
Multimap<ChangeLogSet.Entry, WorkflowRun> change2Build = HashMultimap.create();

Multimap<ChangeLogSet.Entry, Run> change2Build = HashMultimap.create();
for (String changeKey : changes.keySet()) {
ChangeLogSet.Entry change = changes.get(changeKey).iterator().next();
for (ChangeLogSet.Entry entry : changes.get(changeKey)) {
change2Build.put(change, (WorkflowRun) entry.getParent().getRun());
change2Build.put(change, entry.getParent().getRun());
}
}

return change2Build;
}

/**
* Uses all ChangesAggregators to calculate the contributing builds
* Uses DependencyChangesAggregator to calculate the contributing builds.
*
* @return all changes which contribute to the given build
* @param build the workflow build to get dependencies for
* @return all changed builds which contribute to the given build or empty set if none were found
*/
public Set<WorkflowRun> getContributingBuilds(WorkflowRun build) {
Set<WorkflowRun> builds = Sets.newHashSet();
public Set<Run> getContributingBuilds(WorkflowRun build) {

Set<Run> builds = Sets.newHashSet();
builds.add(build);

if (aggregator == null) {
aggregator = new DependencyChangesAggregator();
}

int size = 0;
// Saturate the build Set
do {
size = builds.size();
Set<Run> newBuilds = Sets.newHashSet();
for (Run depBuild : builds) {
newBuilds.addAll(aggregator.aggregateBuildsWithChanges(depBuild));
}
builds.addAll(newBuilds);
} while (size < builds.size());

return builds;
}

Expand All @@ -107,4 +138,8 @@ public WorkflowJob getProject() {
public int getNumChanges() {
return numChanges;
}

public void setAggregator(DependencyChangesAggregator aggregator) {
this.aggregator = aggregator;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,15 @@

import hudson.ExtensionList;
import hudson.ExtensionPoint;
import hudson.model.AbstractBuild;
import jenkins.model.Jenkins;
import hudson.model.Run;

import java.util.Collection;

public abstract class ChangesAggregator implements ExtensionPoint {
public abstract Collection<AbstractBuild> aggregateBuildsWithChanges(AbstractBuild build);
public abstract class ChangesAggregator<T extends Run> implements ExtensionPoint {

public abstract Collection<T> aggregateBuildsWithChanges(T build);

public static ExtensionList<ChangesAggregator> all() {
return Util.getInstance().getExtensionList(ChangesAggregator.class);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -28,22 +28,69 @@
import hudson.Extension;
import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.model.Run;
import hudson.tasks.Fingerprinter;

import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

/**
* @author wolfs
*/
@Extension
public class DependencyChangesAggregator extends ChangesAggregator {
public class DependencyChangesAggregator extends ChangesAggregator<Run> {

@Override
public Collection<AbstractBuild> aggregateBuildsWithChanges(AbstractBuild build) {
ImmutableList.Builder<AbstractBuild> builder = ImmutableList.<AbstractBuild>builder();
Map<AbstractProject, AbstractBuild.DependencyChange> depChanges = build.getDependencyChanges((AbstractBuild) build.getPreviousBuild());
public Collection<Run> aggregateBuildsWithChanges(Run build) {
ImmutableList.Builder<Run> builder = ImmutableList.builder();
Map<AbstractProject, AbstractBuild.DependencyChange> depChanges = getDependencyChanges(build, build.getPreviousBuild());
for (AbstractBuild.DependencyChange depChange : depChanges.values()) {
builder.addAll(depChange.getBuilds());
}
return builder.build();
}

/**
* Gets the changes in the dependency between two given builds using {@link Fingerprinter.FingerprintAction}.
*
* <p>This implements the functionality from {@link AbstractBuild#getDependencyChanges(AbstractBuild)} using
* {@link Run} instead of {@link AbstractBuild} as input build parameters, so that this aggregator can be used by
* the {@link AllChangesWorkflowAction} as well.
*
* @param build the current build to find dependencies to
* @param otherBuild another build to find dependencies from
* @return a map of projects to dependency changes or an empty map if there are no fingerprint actions or the other job is null
* @see AbstractBuild#getDependencyChanges(AbstractBuild)
*/
private Map<AbstractProject, AbstractBuild.DependencyChange> getDependencyChanges(Run build, Run otherBuild) {

if (otherBuild == null) {
return Collections.emptyMap();
}

Fingerprinter.FingerprintAction currentBuildFingerprints = build.getAction(Fingerprinter.FingerprintAction.class);
Fingerprinter.FingerprintAction previousBuildFingerprints = otherBuild.getAction(Fingerprinter.FingerprintAction.class);

if (currentBuildFingerprints == null || previousBuildFingerprints == null) {
return Collections.emptyMap();
}

Map<AbstractProject,Integer> currentBuildDependencies = currentBuildFingerprints.getDependencies(true);
Map<AbstractProject,Integer> previousBuildDependencies = previousBuildFingerprints.getDependencies(true);

Map<AbstractProject, AbstractBuild.DependencyChange> changes = new HashMap<>();

for (Map.Entry<AbstractProject, Integer> entry : previousBuildDependencies.entrySet()) {
AbstractProject dependencyProject = entry.getKey();
Integer oldNumber = entry.getValue();
Integer newNumber = currentBuildDependencies.get(dependencyProject);
if (newNumber != null && oldNumber.compareTo(newNumber) < 0) {
changes.put(dependencyProject, new AbstractBuild.DependencyChange(dependencyProject, oldNumber, newNumber));
}
}

return changes;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@
* @author wolfs
*/
@Extension
public class SubProjectChangesAggregator extends ChangesAggregator {
public class SubProjectChangesAggregator extends ChangesAggregator<AbstractBuild> {

@Override
public Collection<AbstractBuild> aggregateBuildsWithChanges(AbstractBuild build) {
Plugin parameterizedTrigger = Util.getInstance().getPlugin("parameterized-trigger");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

import com.google.common.collect.Multimap
import hudson.Functions
import hudson.model.Run
import hudson.scm.ChangeLogSet
import org.jenkinsci.plugins.workflow.job.WorkflowRun
import org.jvnet.localizer.LocaleProvider
Expand Down Expand Up @@ -61,7 +62,7 @@ private showChanges(Collection<WorkflowRun> builds) {
def changedBuildCount = 1;
boolean hadChanges = false;
for (WorkflowRun build in builds) {
Multimap<ChangeLogSet.Entry, WorkflowRun> changes = my.getAllChanges(build);
Multimap<ChangeLogSet.Entry, Run> changes = my.getAllChanges(build);
if (changes.empty) {
continue
}
Expand Down Expand Up @@ -92,10 +93,10 @@ private showChanges(Collection<WorkflowRun> builds) {
}
}

private def showEntry(entry, WorkflowRun build, Collection<WorkflowRun> builds) {
private def showEntry(entry, WorkflowRun build, Collection<Run> builds) {
showChangeSet(entry)
boolean firstDrawn = false
for (WorkflowRun b in builds) {
for (Run b in builds) {
if (b != build) {
if (!firstDrawn) {
text(" (")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*
* The MIT License
*
* 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.jenkinsci.plugins.all_changes;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import hudson.model.AbstractBuild;
import hudson.model.Run;
import org.jenkinsci.plugins.workflow.job.WorkflowRun;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;

import java.util.Set;

import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

@RunWith(PowerMockRunner.class)
@PrepareForTest({
WorkflowRun.class
})
public class AllChangesWorkflowActionTest {

@Test
public void getContributingBuildsShouldWorkTransitively() throws Exception {

DependencyChangesAggregator aggregatorMock = mock(DependencyChangesAggregator.class);
WorkflowRun build = PowerMockito.mock(WorkflowRun.class);
AbstractBuild build2 = mock(AbstractBuild.class);
AbstractBuild build3 = mock(AbstractBuild.class);
when(aggregatorMock.aggregateBuildsWithChanges(build)).thenReturn(ImmutableList.<Run>of(build2));
when(aggregatorMock.aggregateBuildsWithChanges(build2)).thenReturn(ImmutableList.<Run>of(build3));

AllChangesWorkflowAction changesAction = new AllChangesWorkflowAction(null);
changesAction.setAggregator(aggregatorMock);

Set<Run> foundBuilds = changesAction.getContributingBuilds(build);

assertTrue(foundBuilds.equals(ImmutableSet.of(build, build2, build3)));
}

@Test
public void getContributingBuildsShouldWorkHandleCycles() throws Exception {

DependencyChangesAggregator aggregatorMock = mock(DependencyChangesAggregator.class);
WorkflowRun build = PowerMockito.mock(WorkflowRun.class);
AbstractBuild build2 = mock(AbstractBuild.class);
AbstractBuild build3 = mock(AbstractBuild.class);
when(aggregatorMock.aggregateBuildsWithChanges(build)).thenReturn(ImmutableList.<Run>of(build2));
when(aggregatorMock.aggregateBuildsWithChanges(build2)).thenReturn(ImmutableList.<Run>of(build3));
when(aggregatorMock.aggregateBuildsWithChanges(build3)).thenReturn(ImmutableList.<Run>of(build));

AllChangesWorkflowAction changesAction = new AllChangesWorkflowAction(null);
changesAction.setAggregator(aggregatorMock);

Set<Run> foundBuilds = changesAction.getContributingBuilds(build);

assertTrue(foundBuilds.equals(ImmutableSet.of(build, build2, build3)));
}
}
Loading