From ff2992ed6e7f51ddc7072c42472474967995e5a7 Mon Sep 17 00:00:00 2001 From: "Ing. Pavel Janousek" Date: Wed, 10 Feb 2016 10:30:34 +0100 Subject: [PATCH 01/20] [JENKINS-32777] Cover 'delete-builds' CLI by tests --- .../hudson/cli/DeleteBuildsCommandTest.java | 325 ++++++++++++++++++ 1 file changed, 325 insertions(+) create mode 100644 test/src/test/java/hudson/cli/DeleteBuildsCommandTest.java diff --git a/test/src/test/java/hudson/cli/DeleteBuildsCommandTest.java b/test/src/test/java/hudson/cli/DeleteBuildsCommandTest.java new file mode 100644 index 0000000000..899dd0cea0 --- /dev/null +++ b/test/src/test/java/hudson/cli/DeleteBuildsCommandTest.java @@ -0,0 +1,325 @@ +/* + * The MIT License + * + * Copyright 2016 Red Hat, Inc. + * + * 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 hudson.cli; + +import hudson.model.FreeStyleProject; +import hudson.model.Job; +import hudson.model.Run; +import hudson.model.labels.LabelAtom; +import hudson.tasks.Shell; +import jenkins.model.Jenkins; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.jvnet.hudson.test.JenkinsRule; + +import static hudson.cli.CLICommandInvoker.Matcher.failedWith; +import static hudson.cli.CLICommandInvoker.Matcher.hasNoStandardOutput; +import static hudson.cli.CLICommandInvoker.Matcher.succeeded; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.equalTo; + +/** + * @author pjanouse + */ +public class DeleteBuildsCommandTest { + + private CLICommandInvoker command; + + @Rule public final JenkinsRule j = new JenkinsRule(); + + @Before public void setUp() { + command = new CLICommandInvoker(j, "delete-builds"); + } + + @Test public void deleteBuildsShouldFailWithoutJobReadPermission() throws Exception { + j.createFreeStyleProject("aProject").scheduleBuild2(0).get(); + + final CLICommandInvoker.Result result = command + .authorizedTo(Jenkins.READ) + .invokeWithArgs("aProject", "1"); + assertThat(result, failedWith(3)); + assertThat(result, hasNoStandardOutput()); + assertThat(result.stderr(), containsString("ERROR: No such job 'aProject'")); + } + + @Test public void deleteBuildsShouldFailWithoutRunDeletePermission() throws Exception { + j.createFreeStyleProject("aProject").scheduleBuild2(0).get(); + + final CLICommandInvoker.Result result = command + .authorizedTo(Jenkins.READ, Job.READ) + .invokeWithArgs("aProject", "1"); + assertThat(result, failedWith(6)); + assertThat(result, hasNoStandardOutput()); + assertThat(result.stderr(), containsString("ERROR: user is missing the Run/Delete permission")); + } + + @Test public void deleteBuildsShouldFailIfJobDoesNotExist() throws Exception { + final CLICommandInvoker.Result result = command + .authorizedTo(Jenkins.READ, Job.READ, Run.DELETE) + .invokeWithArgs("never_created", "1"); + assertThat(result, failedWith(3)); + assertThat(result, hasNoStandardOutput()); + assertThat(result.stderr(), containsString("ERROR: No such job 'never_created'")); + } + + @Test public void deleteBuildsShouldFailIfJobNameIsEmpty() throws Exception { + j.createFreeStyleProject("aProject").scheduleBuild2(0).get(); + assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).getBuilds().size(), equalTo(1)); + + final CLICommandInvoker.Result result = command + .authorizedTo(Jenkins.READ, Job.READ, Run.DELETE) + .invokeWithArgs("", "1"); + assertThat(result, failedWith(3)); + assertThat(result, hasNoStandardOutput()); + assertThat(result.stderr(), containsString("ERROR: No such job ''; perhaps you meant 'aProject'?")); + } + + @Test public void deleteBuildsShouldSuccess() throws Exception { + j.createFreeStyleProject("aProject").scheduleBuild2(0).get(); + assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).getBuilds().size(), equalTo(1)); + + final CLICommandInvoker.Result result = command + .authorizedTo(Jenkins.READ, Job.READ, Run.DELETE) + .invokeWithArgs("aProject", "1"); + assertThat(result, succeeded()); + assertThat(result.stdout(), containsString("Deleted 1 builds")); + assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).getBuilds().size(), equalTo(0)); + } + + @Test public void deleteBuildsShouldSuccessIfBuildDoesNotExist() throws Exception { + j.createFreeStyleProject("aProject").scheduleBuild2(0).get(); + assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).getBuilds().size(), equalTo(1)); + + final CLICommandInvoker.Result result = command + .authorizedTo(Jenkins.READ, Job.READ, Run.DELETE) + .invokeWithArgs("aProject", "2"); + assertThat(result, succeeded()); + assertThat(result.stdout(), containsString("Deleted 0 builds")); + } + + @Test public void deleteBuildsShouldSuccessIfBuildNumberZeroSpecified() throws Exception { + j.createFreeStyleProject("aProject").scheduleBuild2(0).get(); + assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).getBuilds().size(), equalTo(1)); + + final CLICommandInvoker.Result result = command + .authorizedTo(Jenkins.READ, Job.READ, Run.DELETE) + .invokeWithArgs("aProject", "0"); + assertThat(result, succeeded()); + assertThat(result.stdout(), containsString("Deleted 0 builds")); + } + + @Test public void deleteBuildsShouldSuccessEvenTheBuildIsRunning() throws Exception { + FreeStyleProject project = j.createFreeStyleProject("aProject"); + project.getBuildersList().add(new Shell("echo 1\nsleep 10s")); + assertThat("Job wasn't scheduled properly", project.scheduleBuild(0), equalTo(true)); + + // Wait until project is started (at least 1s) + while(!project.isBuilding()) { + System.out.println("Waiting for build to start and sleep 1s..."); + Thread.sleep(1000); + } + // Wait for the first sleep + if(!project.getBuildByNumber(1).getLog().contains("echo 1")) { + Thread.sleep(1000); + } + + final CLICommandInvoker.Result result = command + .authorizedTo(Jenkins.READ, Job.READ, Run.DELETE) + .invokeWithArgs("aProject", "1"); + assertThat(result, succeeded()); + assertThat(result.stdout(), containsString("Deleted 1 builds")); + assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).getBuilds().size(), equalTo(0)); + assertThat(project.isBuilding(), equalTo(false)); + } + + @Test public void deleteBuildsShouldSuccessEvenTheBuildIsStuckInTheQueue() throws Exception { + FreeStyleProject project = j.createFreeStyleProject("aProject"); + project.getBuildersList().add(new Shell("echo 1\nsleep 10s")); + project.setAssignedLabel(new LabelAtom("never_created")); + assertThat("Job wasn't scheduled properly", project.scheduleBuild(0), equalTo(true)); + Thread.sleep(1000); + assertThat("Job wasn't scheduled properly - it isn't in the queue", project.isInQueue(), equalTo(true)); + assertThat("Job wasn't scheduled properly - it is running on non-exist node", project.isBuilding(), equalTo(false)); + + final CLICommandInvoker.Result result = command + .authorizedTo(Jenkins.READ, Job.READ, Run.DELETE) + .invokeWithArgs("aProject", "1"); + assertThat(result, succeeded()); + assertThat(result.stdout(), containsString("Deleted 0 builds")); + assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).getBuilds().size(), equalTo(0)); + assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).isBuilding(), equalTo(false)); + assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).isInQueue(), equalTo(true)); + + Jenkins.getInstance().getQueue().cancel(project); + assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).getBuilds().size(), equalTo(0)); + assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).isBuilding(), equalTo(false)); + assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).isInQueue(), equalTo(false)); + } + + @Test public void deleteBuildsManyShouldSuccess() throws Exception { + FreeStyleProject project = j.createFreeStyleProject("aProject"); + project.scheduleBuild2(0).get(); + project.scheduleBuild2(0).get(); + project.scheduleBuild2(0).get(); + project.scheduleBuild2(0).get(); + project.scheduleBuild2(0).get(); + assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).getBuilds().size(), equalTo(5)); + + CLICommandInvoker.Result result = command + .authorizedTo(Jenkins.READ, Job.READ, Run.DELETE) + .invokeWithArgs("aProject", "1,2"); + assertThat(result, succeeded()); + assertThat(result.stdout(), containsString("Deleted 2 builds")); + assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).getBuilds().size(), equalTo(3)); + + result = command + .authorizedTo(Jenkins.READ, Job.READ, Run.DELETE) + .invokeWithArgs("aProject", "3-5"); + assertThat(result, succeeded()); + assertThat(result.stdout(), containsString("Deleted 3 builds")); + assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).getBuilds().size(), equalTo(0)); + } + + @Test public void deleteBuildsManyShouldSuccessEvenABuildIsSpecifiedTwice() throws Exception { + FreeStyleProject project = j.createFreeStyleProject("aProject"); + project.scheduleBuild2(0).get(); + project.scheduleBuild2(0).get(); + assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).getBuilds().size(), equalTo(2)); + + CLICommandInvoker.Result result = command + .authorizedTo(Jenkins.READ, Job.READ, Run.DELETE) + .invokeWithArgs("aProject", "1,1"); + assertThat(result, succeeded()); + assertThat(result.stdout(), containsString("Deleted 1 builds")); + assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).getBuilds().size(), equalTo(1)); + + result = command + .authorizedTo(Jenkins.READ, Job.READ, Run.DELETE) + .invokeWithArgs("aProject", "1-1,1-2,2-2"); + assertThat(result, succeeded()); + assertThat(result.stdout(), containsString("Deleted 1 builds")); + assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).getBuilds().size(), equalTo(0)); + } + + @Test public void deleteBuildsManyShouldSuccessEvenLastBuildDoesNotExist() throws Exception { + FreeStyleProject project = j.createFreeStyleProject("aProject"); + project.scheduleBuild2(0).get(); + project.scheduleBuild2(0).get(); + assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).getBuilds().size(), equalTo(2)); + + CLICommandInvoker.Result result = command + .authorizedTo(Jenkins.READ, Job.READ, Run.DELETE) + .invokeWithArgs("aProject", "1,3"); + assertThat(result, succeeded()); + assertThat(result.stdout(), containsString("Deleted 1 builds")); + assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).getBuilds().size(), equalTo(1)); + + result = command + .authorizedTo(Jenkins.READ, Job.READ, Run.DELETE) + .invokeWithArgs("aProject", "2-3"); + assertThat(result, succeeded()); + assertThat(result.stdout(), containsString("Deleted 1 builds")); + assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).getBuilds().size(), equalTo(0)); + } + + @Test public void deleteBuildsManyShouldSuccessEvenMiddleBuildDoesNotExist() throws Exception { + FreeStyleProject project = j.createFreeStyleProject("aProject"); + project.scheduleBuild2(0).get(); + project.scheduleBuild2(0).get(); + project.scheduleBuild2(0).get(); + project.scheduleBuild2(0).get(); + project.scheduleBuild2(0).get(); + project.scheduleBuild2(0).get(); + project.getBuildByNumber(2).delete(); + project.getBuildByNumber(5).delete(); + assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).getBuilds().size(), equalTo(4)); + + CLICommandInvoker.Result result = command + .authorizedTo(Jenkins.READ, Job.READ, Run.DELETE) + .invokeWithArgs("aProject", "1,2,3"); + assertThat(result, succeeded()); + assertThat(result.stdout(), containsString("Deleted 2 builds")); + assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).getBuilds().size(), equalTo(2)); + + result = command + .authorizedTo(Jenkins.READ, Job.READ, Run.DELETE) + .invokeWithArgs("aProject", "4-6"); + assertThat(result, succeeded()); + assertThat(result.stdout(), containsString("Deleted 2 builds")); + assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).getBuilds().size(), equalTo(0)); + } + + @Test public void deleteBuildsManyShouldSuccessEvenFirstBuildDoesNotExist() throws Exception { + FreeStyleProject project = j.createFreeStyleProject("aProject"); + project.scheduleBuild2(0).get(); + project.scheduleBuild2(0).get(); + project.scheduleBuild2(0).get(); + project.getBuildByNumber(1).delete(); + assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).getBuilds().size(), equalTo(2)); + + CLICommandInvoker.Result result = command + .authorizedTo(Jenkins.READ, Job.READ, Run.DELETE) + .invokeWithArgs("aProject", "1,2"); + assertThat(result, succeeded()); + assertThat(result.stdout(), containsString("Deleted 1 builds")); + assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).getBuilds().size(), equalTo(1)); + + result = command + .authorizedTo(Jenkins.READ, Job.READ, Run.DELETE) + .invokeWithArgs("aProject", "2-3"); + assertThat(result, succeeded()); + assertThat(result.stdout(), containsString("Deleted 1 builds")); + assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).getBuilds().size(), equalTo(0)); + } + + @Test public void deleteBuildsManyShouldSuccessEvenTheFirstAndLastBuildDoesNotExist() throws Exception { + FreeStyleProject project = j.createFreeStyleProject("aProject"); + project.scheduleBuild2(0).get(); + project.scheduleBuild2(0).get(); + project.scheduleBuild2(0).get(); + project.scheduleBuild2(0).get(); + project.scheduleBuild2(0).get(); + project.getBuildByNumber(1).delete(); + project.getBuildByNumber(3).delete(); + project.getBuildByNumber(5).delete(); + assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).getBuilds().size(), equalTo(2)); + + CLICommandInvoker.Result result = command + .authorizedTo(Jenkins.READ, Job.READ, Run.DELETE) + .invokeWithArgs("aProject", "1,2,3"); + assertThat(result, succeeded()); + assertThat(result.stdout(), containsString("Deleted 1 builds")); + assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).getBuilds().size(), equalTo(1)); + + result = command + .authorizedTo(Jenkins.READ, Job.READ, Run.DELETE) + .invokeWithArgs("aProject", "3-5"); + assertThat(result, succeeded()); + assertThat(result.stdout(), containsString("Deleted 1 builds")); + assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).getBuilds().size(), equalTo(0)); + } +} From e4643cf1e3caaf24d88b6e02d27956e6ea736fb9 Mon Sep 17 00:00:00 2001 From: "Ing. Pavel Janousek" Date: Wed, 11 May 2016 14:54:12 +0200 Subject: [PATCH 02/20] Modified based on Oliver's feedback Slighty optimized and better legible asserts --- .../hudson/cli/DeleteBuildsCommandTest.java | 70 ++++++++----------- .../test/java/hudson/model/ExecutorTest.java | 15 ++-- 2 files changed, 42 insertions(+), 43 deletions(-) diff --git a/test/src/test/java/hudson/cli/DeleteBuildsCommandTest.java b/test/src/test/java/hudson/cli/DeleteBuildsCommandTest.java index 899dd0cea0..9177d5cb9e 100644 --- a/test/src/test/java/hudson/cli/DeleteBuildsCommandTest.java +++ b/test/src/test/java/hudson/cli/DeleteBuildsCommandTest.java @@ -24,6 +24,7 @@ package hudson.cli; +import hudson.model.ExecutorTest; import hudson.model.FreeStyleProject; import hudson.model.Job; import hudson.model.Run; @@ -41,6 +42,7 @@ import static hudson.cli.CLICommandInvoker.Matcher.succeeded; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasSize; /** * @author pjanouse @@ -88,7 +90,7 @@ public class DeleteBuildsCommandTest { @Test public void deleteBuildsShouldFailIfJobNameIsEmpty() throws Exception { j.createFreeStyleProject("aProject").scheduleBuild2(0).get(); - assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).getBuilds().size(), equalTo(1)); + assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).getBuilds(), hasSize(1)); final CLICommandInvoker.Result result = command .authorizedTo(Jenkins.READ, Job.READ, Run.DELETE) @@ -100,19 +102,19 @@ public class DeleteBuildsCommandTest { @Test public void deleteBuildsShouldSuccess() throws Exception { j.createFreeStyleProject("aProject").scheduleBuild2(0).get(); - assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).getBuilds().size(), equalTo(1)); + assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).getBuilds(), hasSize(1)); final CLICommandInvoker.Result result = command .authorizedTo(Jenkins.READ, Job.READ, Run.DELETE) .invokeWithArgs("aProject", "1"); assertThat(result, succeeded()); assertThat(result.stdout(), containsString("Deleted 1 builds")); - assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).getBuilds().size(), equalTo(0)); + assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).getBuilds(), hasSize(0)); } @Test public void deleteBuildsShouldSuccessIfBuildDoesNotExist() throws Exception { j.createFreeStyleProject("aProject").scheduleBuild2(0).get(); - assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).getBuilds().size(), equalTo(1)); + assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).getBuilds(), hasSize(1)); final CLICommandInvoker.Result result = command .authorizedTo(Jenkins.READ, Job.READ, Run.DELETE) @@ -123,7 +125,7 @@ public class DeleteBuildsCommandTest { @Test public void deleteBuildsShouldSuccessIfBuildNumberZeroSpecified() throws Exception { j.createFreeStyleProject("aProject").scheduleBuild2(0).get(); - assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).getBuilds().size(), equalTo(1)); + assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).getBuilds(), hasSize(1)); final CLICommandInvoker.Result result = command .authorizedTo(Jenkins.READ, Job.READ, Run.DELETE) @@ -134,31 +136,21 @@ public class DeleteBuildsCommandTest { @Test public void deleteBuildsShouldSuccessEvenTheBuildIsRunning() throws Exception { FreeStyleProject project = j.createFreeStyleProject("aProject"); - project.getBuildersList().add(new Shell("echo 1\nsleep 10s")); - assertThat("Job wasn't scheduled properly", project.scheduleBuild(0), equalTo(true)); - - // Wait until project is started (at least 1s) - while(!project.isBuilding()) { - System.out.println("Waiting for build to start and sleep 1s..."); - Thread.sleep(1000); - } - // Wait for the first sleep - if(!project.getBuildByNumber(1).getLog().contains("echo 1")) { - Thread.sleep(1000); - } + ExecutorTest.startBlockingBuild(project); + assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).getBuilds(), hasSize(1)); final CLICommandInvoker.Result result = command .authorizedTo(Jenkins.READ, Job.READ, Run.DELETE) .invokeWithArgs("aProject", "1"); assertThat(result, succeeded()); assertThat(result.stdout(), containsString("Deleted 1 builds")); - assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).getBuilds().size(), equalTo(0)); + assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).getBuilds(), hasSize(0)); assertThat(project.isBuilding(), equalTo(false)); } @Test public void deleteBuildsShouldSuccessEvenTheBuildIsStuckInTheQueue() throws Exception { FreeStyleProject project = j.createFreeStyleProject("aProject"); - project.getBuildersList().add(new Shell("echo 1\nsleep 10s")); + project.getBuildersList().add(new Shell("echo 1")); project.setAssignedLabel(new LabelAtom("never_created")); assertThat("Job wasn't scheduled properly", project.scheduleBuild(0), equalTo(true)); Thread.sleep(1000); @@ -170,12 +162,12 @@ public class DeleteBuildsCommandTest { .invokeWithArgs("aProject", "1"); assertThat(result, succeeded()); assertThat(result.stdout(), containsString("Deleted 0 builds")); - assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).getBuilds().size(), equalTo(0)); + assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).getBuilds(), hasSize(0)); assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).isBuilding(), equalTo(false)); assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).isInQueue(), equalTo(true)); Jenkins.getInstance().getQueue().cancel(project); - assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).getBuilds().size(), equalTo(0)); + assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).getBuilds(), hasSize(0)); assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).isBuilding(), equalTo(false)); assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).isInQueue(), equalTo(false)); } @@ -187,63 +179,63 @@ public class DeleteBuildsCommandTest { project.scheduleBuild2(0).get(); project.scheduleBuild2(0).get(); project.scheduleBuild2(0).get(); - assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).getBuilds().size(), equalTo(5)); + assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).getBuilds(), hasSize(5)); CLICommandInvoker.Result result = command .authorizedTo(Jenkins.READ, Job.READ, Run.DELETE) .invokeWithArgs("aProject", "1,2"); assertThat(result, succeeded()); assertThat(result.stdout(), containsString("Deleted 2 builds")); - assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).getBuilds().size(), equalTo(3)); + assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).getBuilds(), hasSize(3)); result = command .authorizedTo(Jenkins.READ, Job.READ, Run.DELETE) .invokeWithArgs("aProject", "3-5"); assertThat(result, succeeded()); assertThat(result.stdout(), containsString("Deleted 3 builds")); - assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).getBuilds().size(), equalTo(0)); + assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).getBuilds(), hasSize(0)); } @Test public void deleteBuildsManyShouldSuccessEvenABuildIsSpecifiedTwice() throws Exception { FreeStyleProject project = j.createFreeStyleProject("aProject"); project.scheduleBuild2(0).get(); project.scheduleBuild2(0).get(); - assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).getBuilds().size(), equalTo(2)); + assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).getBuilds(), hasSize(2)); CLICommandInvoker.Result result = command .authorizedTo(Jenkins.READ, Job.READ, Run.DELETE) .invokeWithArgs("aProject", "1,1"); assertThat(result, succeeded()); assertThat(result.stdout(), containsString("Deleted 1 builds")); - assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).getBuilds().size(), equalTo(1)); + assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).getBuilds(), hasSize(1)); result = command .authorizedTo(Jenkins.READ, Job.READ, Run.DELETE) .invokeWithArgs("aProject", "1-1,1-2,2-2"); assertThat(result, succeeded()); assertThat(result.stdout(), containsString("Deleted 1 builds")); - assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).getBuilds().size(), equalTo(0)); + assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).getBuilds(), hasSize(0)); } @Test public void deleteBuildsManyShouldSuccessEvenLastBuildDoesNotExist() throws Exception { FreeStyleProject project = j.createFreeStyleProject("aProject"); project.scheduleBuild2(0).get(); project.scheduleBuild2(0).get(); - assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).getBuilds().size(), equalTo(2)); + assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).getBuilds(), hasSize(2)); CLICommandInvoker.Result result = command .authorizedTo(Jenkins.READ, Job.READ, Run.DELETE) .invokeWithArgs("aProject", "1,3"); assertThat(result, succeeded()); assertThat(result.stdout(), containsString("Deleted 1 builds")); - assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).getBuilds().size(), equalTo(1)); + assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).getBuilds(), hasSize(1)); result = command .authorizedTo(Jenkins.READ, Job.READ, Run.DELETE) .invokeWithArgs("aProject", "2-3"); assertThat(result, succeeded()); assertThat(result.stdout(), containsString("Deleted 1 builds")); - assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).getBuilds().size(), equalTo(0)); + assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).getBuilds(), hasSize(0)); } @Test public void deleteBuildsManyShouldSuccessEvenMiddleBuildDoesNotExist() throws Exception { @@ -256,21 +248,21 @@ public class DeleteBuildsCommandTest { project.scheduleBuild2(0).get(); project.getBuildByNumber(2).delete(); project.getBuildByNumber(5).delete(); - assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).getBuilds().size(), equalTo(4)); + assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).getBuilds(), hasSize(4)); CLICommandInvoker.Result result = command .authorizedTo(Jenkins.READ, Job.READ, Run.DELETE) .invokeWithArgs("aProject", "1,2,3"); assertThat(result, succeeded()); assertThat(result.stdout(), containsString("Deleted 2 builds")); - assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).getBuilds().size(), equalTo(2)); + assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).getBuilds(), hasSize(2)); result = command .authorizedTo(Jenkins.READ, Job.READ, Run.DELETE) .invokeWithArgs("aProject", "4-6"); assertThat(result, succeeded()); assertThat(result.stdout(), containsString("Deleted 2 builds")); - assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).getBuilds().size(), equalTo(0)); + assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).getBuilds(), hasSize(0)); } @Test public void deleteBuildsManyShouldSuccessEvenFirstBuildDoesNotExist() throws Exception { @@ -279,21 +271,21 @@ public class DeleteBuildsCommandTest { project.scheduleBuild2(0).get(); project.scheduleBuild2(0).get(); project.getBuildByNumber(1).delete(); - assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).getBuilds().size(), equalTo(2)); + assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).getBuilds(), hasSize(2)); CLICommandInvoker.Result result = command .authorizedTo(Jenkins.READ, Job.READ, Run.DELETE) .invokeWithArgs("aProject", "1,2"); assertThat(result, succeeded()); assertThat(result.stdout(), containsString("Deleted 1 builds")); - assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).getBuilds().size(), equalTo(1)); + assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).getBuilds(), hasSize(1)); result = command .authorizedTo(Jenkins.READ, Job.READ, Run.DELETE) .invokeWithArgs("aProject", "2-3"); assertThat(result, succeeded()); assertThat(result.stdout(), containsString("Deleted 1 builds")); - assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).getBuilds().size(), equalTo(0)); + assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).getBuilds(), hasSize(0)); } @Test public void deleteBuildsManyShouldSuccessEvenTheFirstAndLastBuildDoesNotExist() throws Exception { @@ -306,20 +298,20 @@ public class DeleteBuildsCommandTest { project.getBuildByNumber(1).delete(); project.getBuildByNumber(3).delete(); project.getBuildByNumber(5).delete(); - assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).getBuilds().size(), equalTo(2)); + assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).getBuilds(), hasSize(2)); CLICommandInvoker.Result result = command .authorizedTo(Jenkins.READ, Job.READ, Run.DELETE) .invokeWithArgs("aProject", "1,2,3"); assertThat(result, succeeded()); assertThat(result.stdout(), containsString("Deleted 1 builds")); - assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).getBuilds().size(), equalTo(1)); + assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).getBuilds(), hasSize(1)); result = command .authorizedTo(Jenkins.READ, Job.READ, Run.DELETE) .invokeWithArgs("aProject", "3-5"); assertThat(result, succeeded()); assertThat(result.stdout(), containsString("Deleted 1 builds")); - assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).getBuilds().size(), equalTo(0)); + assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).getBuilds(), hasSize(0)); } } diff --git a/test/src/test/java/hudson/model/ExecutorTest.java b/test/src/test/java/hudson/model/ExecutorTest.java index 5d5b034fcc..86bdebbe6e 100644 --- a/test/src/test/java/hudson/model/ExecutorTest.java +++ b/test/src/test/java/hudson/model/ExecutorTest.java @@ -144,14 +144,21 @@ public class ExecutorTest { assertThat(log, containsString("Disconnected by Johnny : Taking offline to break your buil")); } - private Future startBlockingBuild(FreeStyleProject p) throws Exception { + /** + * Start a project with an infinite build step + * + * @param project {@link FreeStyleProject} to start + * @return A {@link Future} object represents the started build + * @throws Exception if somethink wrong happened + */ + public static Future startBlockingBuild(FreeStyleProject project) throws Exception { final OneShotEvent e = new OneShotEvent(); - p.getBuildersList().add(new BlockingBuilder(e)); + project.getBuildersList().add(new BlockingBuilder(e)); - Future r = p.scheduleBuild2(0); + Future r = project.scheduleBuild2(0); e.block(); // wait until we are safe to interrupt - assertTrue(p.getLastBuild().isBuilding()); + assertTrue(project.getLastBuild().isBuilding()); return r; } From 107aaf90001284a0790b6b14059bd8075d969dbc Mon Sep 17 00:00:00 2001 From: "Ing. Pavel Janousek" Date: Wed, 11 May 2016 15:01:06 +0200 Subject: [PATCH 03/20] Modified per Oleg's comment --- .../hudson/cli/DeleteBuildsCommandTest.java | 46 +++++++++---------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/test/src/test/java/hudson/cli/DeleteBuildsCommandTest.java b/test/src/test/java/hudson/cli/DeleteBuildsCommandTest.java index 9177d5cb9e..59d338c2d6 100644 --- a/test/src/test/java/hudson/cli/DeleteBuildsCommandTest.java +++ b/test/src/test/java/hudson/cli/DeleteBuildsCommandTest.java @@ -174,11 +174,11 @@ public class DeleteBuildsCommandTest { @Test public void deleteBuildsManyShouldSuccess() throws Exception { FreeStyleProject project = j.createFreeStyleProject("aProject"); - project.scheduleBuild2(0).get(); - project.scheduleBuild2(0).get(); - project.scheduleBuild2(0).get(); - project.scheduleBuild2(0).get(); - project.scheduleBuild2(0).get(); + j.buildAndAssertSuccess(project); + j.buildAndAssertSuccess(project); + j.buildAndAssertSuccess(project); + j.buildAndAssertSuccess(project); + j.buildAndAssertSuccess(project); assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).getBuilds(), hasSize(5)); CLICommandInvoker.Result result = command @@ -198,8 +198,8 @@ public class DeleteBuildsCommandTest { @Test public void deleteBuildsManyShouldSuccessEvenABuildIsSpecifiedTwice() throws Exception { FreeStyleProject project = j.createFreeStyleProject("aProject"); - project.scheduleBuild2(0).get(); - project.scheduleBuild2(0).get(); + j.buildAndAssertSuccess(project); + j.buildAndAssertSuccess(project); assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).getBuilds(), hasSize(2)); CLICommandInvoker.Result result = command @@ -219,8 +219,8 @@ public class DeleteBuildsCommandTest { @Test public void deleteBuildsManyShouldSuccessEvenLastBuildDoesNotExist() throws Exception { FreeStyleProject project = j.createFreeStyleProject("aProject"); - project.scheduleBuild2(0).get(); - project.scheduleBuild2(0).get(); + j.buildAndAssertSuccess(project); + j.buildAndAssertSuccess(project); assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).getBuilds(), hasSize(2)); CLICommandInvoker.Result result = command @@ -240,12 +240,12 @@ public class DeleteBuildsCommandTest { @Test public void deleteBuildsManyShouldSuccessEvenMiddleBuildDoesNotExist() throws Exception { FreeStyleProject project = j.createFreeStyleProject("aProject"); - project.scheduleBuild2(0).get(); - project.scheduleBuild2(0).get(); - project.scheduleBuild2(0).get(); - project.scheduleBuild2(0).get(); - project.scheduleBuild2(0).get(); - project.scheduleBuild2(0).get(); + j.buildAndAssertSuccess(project); + j.buildAndAssertSuccess(project); + j.buildAndAssertSuccess(project); + j.buildAndAssertSuccess(project); + j.buildAndAssertSuccess(project); + j.buildAndAssertSuccess(project); project.getBuildByNumber(2).delete(); project.getBuildByNumber(5).delete(); assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).getBuilds(), hasSize(4)); @@ -267,9 +267,9 @@ public class DeleteBuildsCommandTest { @Test public void deleteBuildsManyShouldSuccessEvenFirstBuildDoesNotExist() throws Exception { FreeStyleProject project = j.createFreeStyleProject("aProject"); - project.scheduleBuild2(0).get(); - project.scheduleBuild2(0).get(); - project.scheduleBuild2(0).get(); + j.buildAndAssertSuccess(project); + j.buildAndAssertSuccess(project); + j.buildAndAssertSuccess(project); project.getBuildByNumber(1).delete(); assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).getBuilds(), hasSize(2)); @@ -290,11 +290,11 @@ public class DeleteBuildsCommandTest { @Test public void deleteBuildsManyShouldSuccessEvenTheFirstAndLastBuildDoesNotExist() throws Exception { FreeStyleProject project = j.createFreeStyleProject("aProject"); - project.scheduleBuild2(0).get(); - project.scheduleBuild2(0).get(); - project.scheduleBuild2(0).get(); - project.scheduleBuild2(0).get(); - project.scheduleBuild2(0).get(); + j.buildAndAssertSuccess(project); + j.buildAndAssertSuccess(project); + j.buildAndAssertSuccess(project); + j.buildAndAssertSuccess(project); + j.buildAndAssertSuccess(project); project.getBuildByNumber(1).delete(); project.getBuildByNumber(3).delete(); project.getBuildByNumber(5).delete(); From 04a5f92e6bc0762d9373455afd3d0308ca60d4e5 Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Wed, 11 May 2016 21:02:43 +0200 Subject: [PATCH 04/20] Update animal-sniffer-maven-plugin to 1.15. --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index b75d90ba61..e2e6d88d52 100644 --- a/pom.xml +++ b/pom.xml @@ -577,7 +577,7 @@ THE SOFTWARE. org.codehaus.mojo animal-sniffer-maven-plugin - 1.9 + 1.15 From d3b61b9cb76eff083510f2614d7c1ebbbe280c28 Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Thu, 12 May 2016 12:49:18 +0200 Subject: [PATCH 05/20] Update changelog for the 2.3 release (#2319, #2032, #2003, #1956, #2248, #2327 + edits) (#2330) --- changelog.html | 35 ++++++++++++++++++++++++++++++----- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/changelog.html b/changelog.html index 5b0149943b..cf78459346 100644 --- a/changelog.html +++ b/changelog.html @@ -60,17 +60,42 @@ Upcoming changes Internal/build: jenkins-ui (NPM module) is private, used only internally. (issue 34629)
  • - Do not print stack trace if a plugin is missing its dependencies. + Do not print stack trace during a plugin installation if it is missing its dependencies. (issue 34683)
  • Allow specifying custom AbortExceptions. (pull 2288)
  • - Internal: CLI command disconnect-node extracted from core to CLI. - (issue 34328) + Add a hudson.model.UpdateCenter.defaultUpdateSiteId system property, + which allows specifying an alternate default Update Site ID. + (issue 34674)
  • - Developer API: Switch Jenkins.getInstance() to @Nonnull - (new system property). + Remove the historical initialization of CVS changelog parser for jobs without explicit SCM definition. + Warning! This change may potentially cause a regression if a Jenkins plugin depends on this default behavior and injects changelogs without SCM. + (issue 4610) +
  • + Prevent hanging of the Installation Wizard if the default Update Site ID cannot be resolved. + In such case an error message will be displayed. + (issue 34675) +
  • + Do not fail with error when enabling a plugin, which has been already enabled. + It prevents errors in the new Installation Wizard, which installs plugins in parallel. + (issue 34710) +
  • + Disable JSESSIONID in URLs when running in the JBoss web container. + It prevents Error 404 due to invalid links starting from Jenkins 1.556. + More info: WFLY-4782 + (issue 34675) +
  • + Internal: CLI commands disconnect-node and reload-configuration were extracted from the core to CLI. + (issue 34328 and + issue 31900) +
  • + Internal: Support latest source version to avoid compile time warnings with JDK7. + (issue 32978) +
  • + Developer API: Switch Jenkins.getInstance() to @Nonnull. + (new system property) (pull 2297) From 6c6f34cd6495a481af8bc54119e2c658d88d1edb Mon Sep 17 00:00:00 2001 From: "Ing. Pavel Janousek" Date: Thu, 5 May 2016 14:39:36 +0200 Subject: [PATCH 06/20] [JENKINS-31618] Fixed NoSuchMethodException in loginLink.jelly The logic is moved from template back to Java and modified to fix the issue. The argorithm is rewritten for better legibility. --- .../java/hudson/security/SecurityRealm.java | 39 +++++++++++++++++++ .../security/SecurityRealm/loginLink.jelly | 4 -- 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/hudson/security/SecurityRealm.java b/core/src/main/java/hudson/security/SecurityRealm.java index 577f7f60f5..f652cf1d7e 100644 --- a/core/src/main/java/hudson/security/SecurityRealm.java +++ b/core/src/main/java/hudson/security/SecurityRealm.java @@ -62,6 +62,7 @@ import javax.servlet.ServletException; import javax.servlet.http.HttpSession; import javax.servlet.http.Cookie; import java.io.IOException; +import java.io.UnsupportedEncodingException; import java.util.List; import java.util.Map; import java.util.logging.Logger; @@ -484,6 +485,44 @@ public abstract class SecurityRealm extends AbstractDescribableImpl - - - - ${%login} From 2a489d3eeac4d4909862aac1b43ea891e427f416 Mon Sep 17 00:00:00 2001 From: "Ing. Pavel Janousek" Date: Wed, 11 May 2016 09:43:45 +0200 Subject: [PATCH 07/20] Better handled or fixed identified problematic parts of the code. --- .../java/hudson/security/SecurityRealm.java | 36 ++++++++++++------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/core/src/main/java/hudson/security/SecurityRealm.java b/core/src/main/java/hudson/security/SecurityRealm.java index f652cf1d7e..d8d74c2689 100644 --- a/core/src/main/java/hudson/security/SecurityRealm.java +++ b/core/src/main/java/hudson/security/SecurityRealm.java @@ -48,6 +48,7 @@ import static org.acegisecurity.ui.rememberme.TokenBasedRememberMeServices.ACEGI import org.acegisecurity.userdetails.UserDetailsService; import org.acegisecurity.userdetails.UserDetails; import org.acegisecurity.userdetails.UsernameNotFoundException; +import org.apache.commons.lang.StringUtils; import org.kohsuke.stapler.HttpResponse; import org.kohsuke.stapler.Stapler; import org.kohsuke.stapler.StaplerRequest; @@ -488,7 +489,8 @@ public abstract class SecurityRealm extends AbstractDescribableImpl Date: Wed, 11 May 2016 12:51:07 +0200 Subject: [PATCH 08/20] Modified for being a bit more legible --- .../java/hudson/security/SecurityRealm.java | 48 ++++++++++--------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/core/src/main/java/hudson/security/SecurityRealm.java b/core/src/main/java/hudson/security/SecurityRealm.java index d8d74c2689..f0afc62ae7 100644 --- a/core/src/main/java/hudson/security/SecurityRealm.java +++ b/core/src/main/java/hudson/security/SecurityRealm.java @@ -495,42 +495,46 @@ public abstract class SecurityRealm extends AbstractDescribableImpl Date: Thu, 12 May 2016 14:43:54 +0200 Subject: [PATCH 09/20] Fixed per Oliver's comments --- .../main/java/hudson/security/SecurityRealm.java | 15 ++++++++------- .../hudson/security/SecurityRealm/loginLink.jelly | 2 +- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/core/src/main/java/hudson/security/SecurityRealm.java b/core/src/main/java/hudson/security/SecurityRealm.java index f0afc62ae7..1c91520d3c 100644 --- a/core/src/main/java/hudson/security/SecurityRealm.java +++ b/core/src/main/java/hudson/security/SecurityRealm.java @@ -49,6 +49,8 @@ import org.acegisecurity.userdetails.UserDetailsService; import org.acegisecurity.userdetails.UserDetails; import org.acegisecurity.userdetails.UsernameNotFoundException; import org.apache.commons.lang.StringUtils; +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.DoNotUse; import org.kohsuke.stapler.HttpResponse; import org.kohsuke.stapler.Stapler; import org.kohsuke.stapler.StaplerRequest; @@ -494,6 +496,7 @@ public abstract class SecurityRealm extends AbstractDescribableImpl - ${%login} + ${%login} From 930f6308a5e0b29a45b88b626f51b246107e2830 Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Thu, 12 May 2016 15:11:45 +0200 Subject: [PATCH 10/20] [JENKINS-34705] - Prevent hanging of InstallationWizard if internet check is skipped (#2328) --- core/src/main/java/hudson/model/UpdateCenter.java | 10 ++++++++++ core/src/main/java/hudson/model/UpdateSite.java | 4 +++- war/src/main/js/util/jenkins.js | 7 ++++++- 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/hudson/model/UpdateCenter.java b/core/src/main/java/hudson/model/UpdateCenter.java index b1a308ab80..b2d69a629c 100644 --- a/core/src/main/java/hudson/model/UpdateCenter.java +++ b/core/src/main/java/hudson/model/UpdateCenter.java @@ -197,6 +197,12 @@ public class UpdateCenter extends AbstractModelObject implements Saveable, OnMas * Connection status has not started yet. */ PRECHECK, + /** + * Connection status check has been skipped. + * As example, it may happen if there is no connection check URL defined for the site. + * @since TODO + */ + SKIPPED, /** * Connection status is being checked at this time. */ @@ -1378,6 +1384,10 @@ public class UpdateCenter extends AbstractModelObject implements Saveable, OnMas connectionStates.put(ConnectionStatus.INTERNET, ConnectionStatus.OK); } }); + } else { + LOGGER.log(WARNING, "Update site '{0}' does not declare the connection check URL. " + + "Skipping the network availability check.", site.getId()); + connectionStates.put(ConnectionStatus.INTERNET, ConnectionStatus.SKIPPED); } connectionStates.put(ConnectionStatus.UPDATE_SITE, ConnectionStatus.CHECKING); diff --git a/core/src/main/java/hudson/model/UpdateSite.java b/core/src/main/java/hudson/model/UpdateSite.java index 53b3226f4e..72c111a24d 100644 --- a/core/src/main/java/hudson/model/UpdateSite.java +++ b/core/src/main/java/hudson/model/UpdateSite.java @@ -341,9 +341,11 @@ public class UpdateSite { } /** - * Returns an "always up" server for Internet connectivity testing, or null if we are going to skip the test. + * Gets a URL for the Internet connection check. + * @return an "always up" server for Internet connectivity testing, or {@code null} if we are going to skip the test. */ @Exported + @CheckForNull public String getConnectionCheckUrl() { Data dt = getData(); if(dt==null) return "http://www.google.com/"; diff --git a/war/src/main/js/util/jenkins.js b/war/src/main/js/util/jenkins.js index e0681dc7a5..bd3b748424 100644 --- a/war/src/main/js/util/jenkins.js +++ b/war/src/main/js/util/jenkins.js @@ -200,12 +200,17 @@ exports.testConnectivity = function(siteId, handler) { handler(false, true, response.message); } + // Define statuses, which need additional check iteration via async job on the Jenkins master + // Statuses like "OK" or "SKIPPED" are considered as fine. var uncheckedStatuses = ['PRECHECK', 'CHECKING', 'UNCHECKED']; if(uncheckedStatuses.indexOf(response.data.updatesite) >= 0 || uncheckedStatuses.indexOf(response.data.internet) >= 0) { setTimeout(testConnectivity, 100); } else { - if(response.status !== 'ok' || response.data.updatesite !== 'OK' || response.data.internet !== 'OK') { + // Update site should be always reachable, but we do not require the internet connection + // if it's explicitly skipped by the update center + if(response.status !== 'ok' || response.data.updatesite !== 'OK' || + (response.data.internet !== 'OK' && response.data.internet !== 'SKIPPED')) { // no connectivity, but not fatal handler(false, false); } From 732696bd89ac42c02b5c76d4e1cfc1796ca02bb6 Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Thu, 12 May 2016 15:21:26 +0200 Subject: [PATCH 11/20] Update annotation-indexer from 1.7 to 1.11 (#2329) Diff: https://github.com/jenkinsci/lib-annotation-indexer/compare/e90dade9a07b04b64ac1b08d539d30ad69ddf7a8...2eec43c72b3cd040d2057e6bbbf639f5e030453e Major changes: * 1.11 - [JENKINS-32978] support latest source version to avoid compile time warnings with JDK7 * 1.10 - Get ready for JDK 9 * 1.10 - Be tolerant against line separators on different operating systems * 1.9 - Support of third-party annotations * 1.8 - Annotation processor can be intercepted --- core/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/pom.xml b/core/pom.xml index 5d0b5a5887..2b1e864e19 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -195,7 +195,7 @@ THE SOFTWARE. org.jenkins-ci annotation-indexer - 1.7 + 1.11 org.jenkins-ci From cac3a2e955c6b5aa5ecaf50ad6bed29adcaf928d Mon Sep 17 00:00:00 2001 From: "Ing. Pavel Janousek" Date: Thu, 12 May 2016 15:38:55 +0200 Subject: [PATCH 12/20] Fixed condition --- core/src/main/java/hudson/security/SecurityRealm.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/hudson/security/SecurityRealm.java b/core/src/main/java/hudson/security/SecurityRealm.java index 1c91520d3c..b93a1f6372 100644 --- a/core/src/main/java/hudson/security/SecurityRealm.java +++ b/core/src/main/java/hudson/security/SecurityRealm.java @@ -515,8 +515,8 @@ public abstract class SecurityRealm extends AbstractDescribableImpl Date: Thu, 12 May 2016 16:33:02 +0200 Subject: [PATCH 13/20] [FIXED JENKINS-25164] Add JOB_BASE_NAME env var (#2321) * [JENKINS-25164] Add SHORT_JOB_NAME env var * Delete strip off * [JENKINS-25164] Change SHORT_JOB_NAME for LEAF_JOB_NAME * [JENKINS-25164] Change LEAF_JOB_NAME for JOB_BASE_NAME --- core/src/main/java/hudson/model/Job.java | 1 + .../jenkins/model/CoreEnvironmentContributor/buildEnv.groovy | 2 +- .../model/CoreEnvironmentContributor/buildEnv.properties | 3 ++- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/hudson/model/Job.java b/core/src/main/java/hudson/model/Job.java index 263787ffcb..69cc02708e 100644 --- a/core/src/main/java/hudson/model/Job.java +++ b/core/src/main/java/hudson/model/Job.java @@ -369,6 +369,7 @@ public abstract class Job, RunT extends Run +["BUILD_NUMBER","BUILD_ID","BUILD_DISPLAY_NAME","JOB_NAME", "JOB_BASE_NAME","BUILD_TAG","EXECUTOR_NUMBER","NODE_NAME","NODE_LABELS","WORKSPACE","JENKINS_HOME","JENKINS_URL","BUILD_URL","JOB_URL"].each { name -> l.buildEnvVar(name:name) { raw(_("${name}.blurb")) } diff --git a/core/src/main/resources/jenkins/model/CoreEnvironmentContributor/buildEnv.properties b/core/src/main/resources/jenkins/model/CoreEnvironmentContributor/buildEnv.properties index b1c4d4d8e1..8eed43cf12 100644 --- a/core/src/main/resources/jenkins/model/CoreEnvironmentContributor/buildEnv.properties +++ b/core/src/main/resources/jenkins/model/CoreEnvironmentContributor/buildEnv.properties @@ -1,7 +1,8 @@ BUILD_NUMBER.blurb=The current build number, such as "153" BUILD_ID.blurb=The current build ID, identical to BUILD_NUMBER for builds created in 1.597+, but a YYYY-MM-DD_hh-mm-ss timestamp for older builds BUILD_DISPLAY_NAME.blurb=The display name of the current build, which is something like "#153" by default. -JOB_NAME.blurb=Name of the project of this build, such as "foo" or "foo/bar". (To strip off folder paths from a Bourne shell script, try: $'{'JOB_NAME##*/}) +JOB_NAME.blurb=Name of the project of this build, such as "foo" or "foo/bar". +JOB_BASE_NAME.blurb=Short Name of the project of this build stripping off folder paths, such as "foo" for "bar/foo". BUILD_TAG.blurb=String of "jenkins-$'{'JOB_NAME}-$'{'BUILD_NUMBER}". Convenient to put into a resource file, a jar file, etc for easier identification. EXECUTOR_NUMBER.blurb=\ The unique number that identifies the current executor \ From d8076e9654a4be6cd0792ea954363b209c44313b Mon Sep 17 00:00:00 2001 From: Carlos Rendon Date: Thu, 12 May 2016 07:38:27 -0700 Subject: [PATCH 14/20] Fix RSS id for builds in folders (#1965) [JENKINS-34767] - Prevent RSS ID collisions for items with same name in different folders --- core/src/main/java/hudson/model/Run.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/hudson/model/Run.java b/core/src/main/java/hudson/model/Run.java index 7c5aba4be5..37457d831b 100644 --- a/core/src/main/java/hudson/model/Run.java +++ b/core/src/main/java/hudson/model/Run.java @@ -2391,7 +2391,7 @@ public abstract class Run ,RunT extends Run Date: Fri, 13 May 2016 08:53:14 +0200 Subject: [PATCH 15/20] Fixed typo in comment --- core/src/main/java/hudson/security/SecurityRealm.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/hudson/security/SecurityRealm.java b/core/src/main/java/hudson/security/SecurityRealm.java index b93a1f6372..142eeed468 100644 --- a/core/src/main/java/hudson/security/SecurityRealm.java +++ b/core/src/main/java/hudson/security/SecurityRealm.java @@ -489,9 +489,9 @@ public abstract class SecurityRealm extends AbstractDescribableImpl Date: Fri, 13 May 2016 04:36:36 -0400 Subject: [PATCH 16/20] Update SezPoz to 1.11. (#2338) --- core/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/pom.xml b/core/pom.xml index 2b1e864e19..cae0dfbd24 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -539,7 +539,7 @@ THE SOFTWARE. net.java.sezpoz sezpoz - 1.9 + 1.11 org.kohsuke.jinterop From ebb84da617a989d9d1bff8660d9903d89ca3ea41 Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Fri, 13 May 2016 10:39:01 +0200 Subject: [PATCH 17/20] [JENKINS-34748] - Fix handling of dependency trees in PluginManager::addDependencies() (#2334) --- core/src/main/java/hudson/PluginManager.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/hudson/PluginManager.java b/core/src/main/java/hudson/PluginManager.java index 0d8b3cdf7f..26872054d4 100644 --- a/core/src/main/java/hudson/PluginManager.java +++ b/core/src/main/java/hudson/PluginManager.java @@ -558,9 +558,10 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas } if (dependencyURL != null) { - dependencySet.add(dependencyURL); // And transitive deps... addDependencies(dependencyURL, fromPath, dependencySet); + // And then add the current plugin + dependencySet.add(dependencyURL); } } } From 0c16c8576bf662b2af049d8d9720ea7d30085c3a Mon Sep 17 00:00:00 2001 From: Vincent Latombe Date: Fri, 13 May 2016 12:36:45 +0200 Subject: [PATCH 18/20] [JENKINS-23507] Make FilePath.installIfNecessaryFrom follow redirects (#2331) --- core/src/main/java/hudson/FilePath.java | 64 +++++++++++++++++++-- core/src/test/java/hudson/FilePathTest.java | 24 ++++++++ 2 files changed, 82 insertions(+), 6 deletions(-) diff --git a/core/src/main/java/hudson/FilePath.java b/core/src/main/java/hudson/FilePath.java index 72383256e4..d0ee7b5c4f 100644 --- a/core/src/main/java/hudson/FilePath.java +++ b/core/src/main/java/hudson/FilePath.java @@ -25,6 +25,7 @@ */ package hudson; +import com.google.common.annotations.VisibleForTesting; import com.jcraft.jzlib.GZIPInputStream; import com.jcraft.jzlib.GZIPOutputStream; import hudson.Launcher.LocalLauncher; @@ -71,6 +72,8 @@ import org.apache.tools.ant.Project; import org.apache.tools.ant.types.FileSet; import org.apache.tools.zip.ZipEntry; import org.apache.tools.zip.ZipFile; +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.NoExternalUse; import org.kohsuke.stapler.Stapler; import javax.annotation.CheckForNull; @@ -91,6 +94,7 @@ import java.io.RandomAccessFile; import java.io.Serializable; import java.io.Writer; import java.net.HttpURLConnection; +import java.net.MalformedURLException; import java.net.URI; import java.net.URL; import java.net.URLConnection; @@ -188,6 +192,11 @@ import org.jenkinsci.remoting.RoleSensitive; * @see VirtualFile */ public final class FilePath implements Serializable { + /** + * Maximum http redirects we will follow. This defaults to the same number as Firefox/Chrome tolerates. + */ + private static final int MAX_REDIRECTS = 20; + /** * When this {@link FilePath} represents the remote path, * this field is always non-null on master (the field represents @@ -755,6 +764,10 @@ public final class FilePath implements Serializable { * @since 1.299 */ public boolean installIfNecessaryFrom(@Nonnull URL archive, @CheckForNull TaskListener listener, @Nonnull String message) throws IOException, InterruptedException { + return installIfNecessaryFrom(archive, listener, message, MAX_REDIRECTS); + } + + private boolean installIfNecessaryFrom(@Nonnull URL archive, @CheckForNull TaskListener listener, @Nonnull String message, int maxRedirects) throws InterruptedException, IOException { try { FilePath timestamp = this.child(".timestamp"); long lastModified = timestamp.lastModified(); @@ -777,14 +790,28 @@ public final class FilePath implements Serializable { } } - if (lastModified != 0 && con instanceof HttpURLConnection) { + if (con instanceof HttpURLConnection) { HttpURLConnection httpCon = (HttpURLConnection) con; int responseCode = httpCon.getResponseCode(); - if (responseCode == HttpURLConnection.HTTP_NOT_MODIFIED) { - return false; - } else if (responseCode != HttpURLConnection.HTTP_OK) { - listener.getLogger().println("Skipping installation of " + archive + " to " + remote + " due to server error: " + responseCode + " " + httpCon.getResponseMessage()); - return false; + if (responseCode == HttpURLConnection.HTTP_MOVED_PERM + || responseCode == HttpURLConnection.HTTP_MOVED_TEMP) { + // follows redirect + if (maxRedirects > 0) { + String location = httpCon.getHeaderField("Location"); + listener.getLogger().println("Following redirect " + archive.toExternalForm() + " -> " + location); + return installIfNecessaryFrom(getUrlFactory().newURL(location), listener, message, maxRedirects - 1); + } else { + listener.getLogger().println("Skipping installation of " + archive + " to " + remote + " due to too many redirects."); + return false; + } + } + if (lastModified != 0) { + if (responseCode == HttpURLConnection.HTTP_NOT_MODIFIED) { + return false; + } else if (responseCode != HttpURLConnection.HTTP_OK) { + listener.getLogger().println("Skipping installation of " + archive + " to " + remote + " due to server error: " + responseCode + " " + httpCon.getResponseMessage()); + return false; + } } } @@ -2520,6 +2547,31 @@ public final class FilePath implements Serializable { }); } + private static final UrlFactory DEFAULT_URL_FACTORY = new UrlFactory(); + + @Restricted(NoExternalUse.class) + static class UrlFactory { + public URL newURL(String location) throws MalformedURLException { + return new URL(location); + } + } + + private UrlFactory urlFactory; + + @VisibleForTesting + @Restricted(NoExternalUse.class) + void setUrlFactory(UrlFactory urlFactory) { + this.urlFactory = urlFactory; + } + + private UrlFactory getUrlFactory() { + if (urlFactory != null) { + return urlFactory; + } else { + return DEFAULT_URL_FACTORY; + } + } + /** * Short for {@code validateFileMask(path, value, true)} */ diff --git a/core/src/test/java/hudson/FilePathTest.java b/core/src/test/java/hudson/FilePathTest.java index 6788c93f54..7b515ec213 100644 --- a/core/src/test/java/hudson/FilePathTest.java +++ b/core/src/test/java/hudson/FilePathTest.java @@ -38,9 +38,11 @@ import java.io.OutputStream; import java.io.RandomAccessFile; import java.net.ConnectException; import java.net.HttpURLConnection; +import java.net.MalformedURLException; import java.net.URL; import java.net.URLConnection; import java.net.URLStreamHandler; +import java.net.URLStreamHandlerFactory; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; @@ -637,6 +639,28 @@ public class FilePathTest { assertTrue(log, log.contains("504 Gateway Timeout")); } + @Issue("JENKINS-23507") + @Test public void installIfNecessaryFollowsRedirects() throws Exception{ + File tmp = temp.getRoot(); + final FilePath d = new FilePath(tmp); + FilePath.UrlFactory urlFactory = mock(FilePath.UrlFactory.class); + d.setUrlFactory(urlFactory); + final HttpURLConnection con = mock(HttpURLConnection.class); + final HttpURLConnection con2 = mock(HttpURLConnection.class); + final URL url = someUrlToZipFile(con); + when(con.getResponseCode()).thenReturn(HttpURLConnection.HTTP_MOVED_TEMP); + URL url2 = someUrlToZipFile(con2); + String someUrl = url2.toExternalForm(); + when(con.getHeaderField("Location")).thenReturn(someUrl); + when(urlFactory.newURL(someUrl)).thenReturn(url2); + when(con2.getResponseCode()).thenReturn(HttpURLConnection.HTTP_OK); + when(con2.getInputStream()).thenReturn(someZippedContent()); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + String message = "going ahead"; + assertTrue(d.installIfNecessaryFrom(url, new StreamTaskListener(baos), message)); + } + private URL someUrlToZipFile(final URLConnection con) throws IOException { final URLStreamHandler urlHandler = new URLStreamHandler() { From 3c066624b9eb6398cd0d0c111b606685f877319e Mon Sep 17 00:00:00 2001 From: Vincent Latombe Date: Fri, 13 May 2016 12:40:58 +0200 Subject: [PATCH 19/20] Noting #2331 --- changelog.html | 2 ++ 1 file changed, 2 insertions(+) diff --git a/changelog.html b/changelog.html index cf78459346..77239d0c2e 100644 --- a/changelog.html +++ b/changelog.html @@ -97,6 +97,8 @@ Upcoming changes Developer API: Switch Jenkins.getInstance() to @Nonnull. (new system property) (pull 2297) +
  • + Tool installers now follow http redirects properly (issue 23507) From 17dfb8ab00c00f3ad7c35196047bac7fc5fcac7c Mon Sep 17 00:00:00 2001 From: James Nord Date: Fri, 13 May 2016 16:33:40 +0100 Subject: [PATCH 20/20] Amending #2055 (#2342) --- .../resources/hudson/triggers/TimerTrigger/help-spec.html | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/core/src/main/resources/hudson/triggers/TimerTrigger/help-spec.html b/core/src/main/resources/hudson/triggers/TimerTrigger/help-spec.html index d097fc4193..bfe1d96e21 100644 --- a/core/src/main/resources/hudson/triggers/TimerTrigger/help-spec.html +++ b/core/src/main/resources/hudson/triggers/TimerTrigger/help-spec.html @@ -73,8 +73,10 @@ H/15 * * * * # every ten minutes in the first half of every hour (three times, perhaps at :04, :14, :24) H(0-29)/10 * * * * -# once every two hours every weekday (perhaps at 9:38 AM, 11:38 AM, 1:38 PM, 3:38 PM) -H 9-16/2 * * 1-5 +# once every two hours at 45 minutes past the hour starting at 9:45 AM and finishing at 3:45 PM every weekday. +45 9-16/2 * * 1-5 +# once in every two hours slot between 9 AM and 5 PM every weekday (perhaps at 10:38 AM, 12:38 PM, 2:38 PM, 4:38 PM) +H H(9-16)/2 * * 1-5 # once a day on the 1st and 15th of every month except December H H 1,15 1-11 *