-
Notifications
You must be signed in to change notification settings - Fork 72
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
Save information about child jobs in parent job #13
Changes from 5 commits
46b0391
755b480
05a8806
66aa284
42aec90
9b0afd5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
package org.jenkinsci.plugins.workflow.support.steps.build; | ||
|
||
import hudson.model.Job; | ||
import hudson.model.Run; | ||
import jenkins.model.Jenkins; | ||
import hudson.model.InvisibleAction; | ||
|
||
import java.util.ArrayList; | ||
import java.util.Collections; | ||
import java.util.List; | ||
import java.util.Queue; | ||
import java.util.concurrent.ConcurrentLinkedQueue; | ||
|
||
|
||
public class BuildInfoAction extends InvisibleAction { | ||
private final Queue<BuildInfo> buildInfos = new ConcurrentLinkedQueue<>(); | ||
|
||
BuildInfoAction() { | ||
} | ||
|
||
BuildInfoAction(String projectName, int buildNumber) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Useless constructor, better deleted. |
||
buildInfos.add(new BuildInfo(projectName, buildNumber)); | ||
} | ||
|
||
public List<Run<?, ?>> getChildBuilds() { | ||
if (buildInfos.isEmpty()) { | ||
return Collections.emptyList(); | ||
} | ||
|
||
List<Run<?, ?>> builds = new ArrayList<>(buildInfos.size()); | ||
|
||
for (BuildInfo buildInfo : buildInfos) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Minor optimization: Return There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure if There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
if (buildInfo != null) { | ||
Job job = Jenkins.getActiveInstance().getItemByFullName(buildInfo.getProjectName(), Job.class); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unclear whether this should be impersonating |
||
if (buildInfo.getBuildNumber() != 0) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Huh? Why would the build number be zero? |
||
builds.add((job != null) ? job.getBuildByNumber(buildInfo.getBuildNumber()) : null); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If the job is missing, do not add null. And |
||
} | ||
} | ||
} | ||
|
||
return builds; | ||
} | ||
|
||
public void addBuildInfo(String projectName, int buildNumber) { | ||
buildInfos.add(new BuildInfo(projectName, buildNumber)); | ||
} | ||
|
||
private static class BuildInfo { | ||
private final String projectName; | ||
private final int buildNumber; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Easier just to use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It does not look easier for me, in this case I would need one String concatenation + |
||
|
||
BuildInfo(String projectName, int buildNumber) { | ||
this.projectName = projectName; | ||
this.buildNumber = buildNumber; | ||
} | ||
|
||
String getProjectName() { | ||
return projectName; | ||
} | ||
|
||
int getBuildNumber() { | ||
return buildNumber; | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,6 +8,7 @@ | |
import hudson.model.TaskListener; | ||
import hudson.model.listeners.RunListener; | ||
import java.util.logging.Level; | ||
import org.jenkinsci.plugins.workflow.graph.FlowNode; | ||
import org.jenkinsci.plugins.workflow.steps.StepContext; | ||
|
||
import java.util.logging.Logger; | ||
|
@@ -29,6 +30,11 @@ public void onStarted(Run<?, ?> run, TaskListener listener) { | |
TaskListener taskListener = stepContext.get(TaskListener.class); | ||
// encodeTo(Run) calls getDisplayName, which does not include the project name. | ||
taskListener.getLogger().println("Starting building: " + ModelHyperlinkNote.encodeTo("/" + run.getUrl(), run.getFullDisplayName())); | ||
FlowNode parentNode = stepContext.get(FlowNode.class); | ||
if (parentNode != null) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @oleg-nenashev, I suppose that this code is not thread-safe as well. Is it correct? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No need to check for null if defined via |
||
BuildInfoAction buildInfoAction = ensureBuildInfoAction(parentNode); | ||
buildInfoAction.addBuildInfo(run.getParent().getFullName(), run.getNumber()); | ||
} | ||
} catch (Exception e) { | ||
LOGGER.log(WARNING, null, e); | ||
} | ||
|
@@ -38,6 +44,20 @@ public void onStarted(Run<?, ?> run, TaskListener listener) { | |
} | ||
} | ||
|
||
private BuildInfoAction ensureBuildInfoAction(final FlowNode parentNode) { | ||
BuildInfoAction buildInfoAction = parentNode.getAction(BuildInfoAction.class); | ||
if (buildInfoAction == null) { | ||
synchronized (this) { | ||
buildInfoAction = parentNode.getAction(BuildInfoAction.class); | ||
if (buildInfoAction == null) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Double-checked locking, unsafe. |
||
buildInfoAction = new BuildInfoAction(); | ||
parentNode.addAction(buildInfoAction); | ||
} | ||
} | ||
} | ||
return buildInfoAction; | ||
} | ||
|
||
@Override | ||
@SuppressWarnings("deprecation") // TODO 2.30+ use removeAction | ||
public void onCompleted(Run<?,?> run, @Nonnull TaskListener listener) { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
package org.jenkinsci.plugins.workflow.support.steps.build; | ||
|
||
import hudson.model.Job; | ||
import jenkins.model.Jenkins; | ||
import org.junit.Before; | ||
import org.junit.Test; | ||
import org.junit.runner.RunWith; | ||
import org.mockito.Mockito; | ||
|
||
import java.util.concurrent.ExecutorService; | ||
import java.util.concurrent.Executors; | ||
import java.util.concurrent.Future; | ||
|
||
import static org.mockito.Matchers.any; | ||
import static org.mockito.Matchers.same; | ||
import static org.junit.Assert.assertEquals; | ||
import static org.powermock.api.mockito.PowerMockito.when; | ||
|
||
import org.powermock.api.mockito.PowerMockito; | ||
import org.powermock.core.classloader.annotations.PrepareForTest; | ||
import org.powermock.modules.junit4.PowerMockRunner; | ||
|
||
@RunWith(PowerMockRunner.class) | ||
@PrepareForTest(Jenkins.class) | ||
public class BuildInfoActionTest { | ||
private Jenkins jenkins; | ||
|
||
@Before | ||
public void setUp() { | ||
PowerMockito.mockStatic(Jenkins.class); | ||
jenkins = PowerMockito.mock(Jenkins.class); | ||
PowerMockito.when(Jenkins.getInstance()).thenReturn(jenkins); | ||
PowerMockito.when(Jenkins.getActiveInstance()).thenReturn(jenkins); | ||
} | ||
|
||
@Test | ||
public void testConcurrentAccess() throws Exception { | ||
Job item = Mockito.mock(Job.class); | ||
when(jenkins.getItemByFullName(any(String.class), same(Job.class))).thenReturn(item); | ||
|
||
BuildInfoAction buildInfoAction = new BuildInfoAction("firstOne", 1); | ||
|
||
ExecutorService executors = Executors.newFixedThreadPool(5); | ||
Runnable task1 = createRunnable("A", buildInfoAction); | ||
Runnable task2 = createRunnable("B", buildInfoAction); | ||
Runnable task3 = createRunnable("C", buildInfoAction); | ||
Runnable task4 = createRunnable("D", buildInfoAction); | ||
Runnable task5 = createRunnable("E", buildInfoAction); | ||
|
||
Future future1 = executors.submit(task1); | ||
Future future2 = executors.submit(task2); | ||
Future future3 = executors.submit(task3); | ||
Future future4 = executors.submit(task4); | ||
Future future5 = executors.submit(task5); | ||
|
||
while (true) { | ||
if (future1.isDone() && future2.isDone() && future3.isDone() && future4.isDone() && future5.isDone() ) { | ||
break; | ||
} | ||
|
||
Thread.sleep(100); | ||
} | ||
|
||
assertEquals(501, buildInfoAction.getChildBuilds().size()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This test does not seem to be proving anything very interesting. I would just delete it. (PowerMock is a pain in the butt to maintain for Jenkins plugins anyway.) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Right now I used |
||
} | ||
|
||
private Runnable createRunnable(final String name, final BuildInfoAction action) { | ||
return new Runnable() { | ||
@Override | ||
public void run() { | ||
for (int i = 0; i < 100; i++) { | ||
action.addBuildInfo(name, i + 1); | ||
try { | ||
Thread.sleep(10); | ||
} catch (InterruptedException ignored) { | ||
} | ||
} | ||
} | ||
}; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,11 +15,11 @@ | |
import hudson.model.ParametersDefinitionProperty; | ||
import hudson.model.Queue; | ||
import hudson.model.Result; | ||
import hudson.model.Run; | ||
import hudson.model.StringParameterDefinition; | ||
import hudson.model.TaskListener; | ||
import hudson.model.User; | ||
import hudson.model.queue.QueueTaskFuture; | ||
import hudson.security.AuthorizationStrategy; | ||
import java.io.IOException; | ||
import java.util.Arrays; | ||
import java.util.Collections; | ||
|
@@ -40,6 +40,8 @@ | |
import static org.hamcrest.Matchers.nullValue; | ||
import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition; | ||
import org.jenkinsci.plugins.workflow.cps.CpsFlowExecution; | ||
import org.jenkinsci.plugins.workflow.graph.FlowGraphWalker; | ||
import org.jenkinsci.plugins.workflow.graph.FlowNode; | ||
import org.jenkinsci.plugins.workflow.job.WorkflowJob; | ||
import org.jenkinsci.plugins.workflow.job.WorkflowRun; | ||
import org.jenkinsci.plugins.workflow.test.steps.SemaphoreStep; | ||
|
@@ -78,20 +80,31 @@ public class BuildTriggerStepTest { | |
us.setDefinition(new CpsFlowDefinition( | ||
"def ds = build 'ds'\n" + | ||
"echo \"ds.result=${ds.result} ds.number=${ds.number}\"", true)); | ||
j.assertLogContains("ds.result=SUCCESS ds.number=1", j.buildAndAssertSuccess(us)); | ||
WorkflowRun usRun = j.buildAndAssertSuccess(us); | ||
j.assertLogContains("ds.result=SUCCESS ds.number=1", usRun); | ||
assertBuildInfoAction(usRun, "ds", 1); | ||
|
||
// TODO JENKINS-28673 assert no warnings, as in StartupTest.noWarnings | ||
// (but first need to deal with `WARNING: Failed to instantiate optional component org.jenkinsci.plugins.workflow.steps.scm.SubversionStep$DescriptorImpl; skipping`) | ||
ds.getBuildByNumber(1).delete(); | ||
|
||
} | ||
|
||
@Issue("JENKINS-25851") | ||
@Test public void failingBuild() throws Exception { | ||
j.createFreeStyleProject("ds").getBuildersList().add(new FailureBuilder()); | ||
FreeStyleProject ds = j.createFreeStyleProject("ds"); | ||
ds.getBuildersList().add(new FailureBuilder()); | ||
WorkflowJob us = j.jenkins.createProject(WorkflowJob.class, "us"); | ||
us.setDefinition(new CpsFlowDefinition("build 'ds'", true)); | ||
j.assertBuildStatus(Result.FAILURE, us.scheduleBuild2(0)); | ||
WorkflowRun usRun = j.assertBuildStatus(Result.FAILURE, us.scheduleBuild2(0)); | ||
assertBuildInfoAction(usRun, "ds", ds.getLastBuild().getNumber()); | ||
|
||
us.setDefinition(new CpsFlowDefinition("echo \"ds.result=${build(job: 'ds', propagate: false).result}\"", true)); | ||
j.assertLogContains("ds.result=FAILURE", j.buildAndAssertSuccess(us)); | ||
|
||
usRun = j.buildAndAssertSuccess(us); | ||
j.assertLogContains("ds.result=FAILURE", usRun); | ||
|
||
assertBuildInfoAction(usRun, "ds", ds.getLastBuild().getNumber()); | ||
} | ||
|
||
@SuppressWarnings("deprecation") | ||
|
@@ -124,7 +137,10 @@ public void buildParallelTests() throws Exception { | |
" build('test2');\n" + | ||
" })"), "\n"), true)); | ||
|
||
j.buildAndAssertSuccess(foo); | ||
WorkflowRun usRun = j.buildAndAssertSuccess(foo); | ||
|
||
assertBuildInfoAction(usRun, "test1", p1.getLastBuild().getNumber()); | ||
assertBuildInfoAction(usRun, "test2", p2.getLastBuild().getNumber()); | ||
} | ||
|
||
|
||
|
@@ -150,7 +166,11 @@ public void abortBuild() throws Exception { | |
fb.getExecutor().interrupt(); | ||
|
||
j.assertBuildStatus(Result.ABORTED, j.waitForCompletion(fb)); | ||
j.assertBuildStatus(Result.FAILURE,q.get()); | ||
|
||
WorkflowRun usRun = q.get(); | ||
j.assertBuildStatus(Result.FAILURE, q.get()); | ||
|
||
assertBuildInfoAction(usRun, "test1", p.getLastBuild().getNumber()); | ||
} | ||
|
||
@Test | ||
|
@@ -430,6 +450,23 @@ public MultiBranchProjectFactory newInstance() { | |
} | ||
} | ||
|
||
private void assertBuildInfoAction(WorkflowRun workflowRun, String projectName, int buildNumber) { | ||
FlowGraphWalker walker = new FlowGraphWalker(workflowRun.getExecution()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🐜 Deprecated, use DepthFirstScanner instead There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It needs @svanoort, @jglick, @oleg-nenashev So, what should I do? |
||
|
||
for(FlowNode step : walker) { | ||
BuildInfoAction buildInfoAction = step.getAction(BuildInfoAction.class); | ||
if (buildInfoAction != null) { | ||
for (Run child : buildInfoAction.getChildBuilds()) { | ||
if (child.getNumber() == buildNumber && child.getParent().getFullName().equals(projectName)) { | ||
return; | ||
} | ||
} | ||
} | ||
} | ||
|
||
throw new AssertionError("Didn't find expected buildInfoAction"); | ||
} | ||
|
||
@Issue("SECURITY-433") | ||
@Test public void permissions() throws Exception { | ||
WorkflowJob us = j.jenkins.createProject(WorkflowJob.class, "us"); | ||
|
@@ -451,5 +488,4 @@ public MultiBranchProjectFactory newInstance() { | |
j.jenkins.setAuthorizationStrategy(new MockAuthorizationStrategy().grant(Jenkins.READ, Computer.BUILD).everywhere().to("dev").grant(Item.DISCOVER).onItems(ds).to("dev")); | ||
j.assertLogContains("Please login to access job ds", j.assertBuildStatus(Result.FAILURE, us.scheduleBuild2(0))); | ||
} | ||
|
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see no problem with making it visible, but maybe it's outside the scope
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can put this comment to the code ;)