/*
* The MIT License
*
* Copyright (c) 2004-2015, Sun Microsystems, Inc., CloudBees, 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.model;
import com.gargoylesoftware.htmlunit.Page;
import hudson.EnvVars;
import hudson.Launcher;
import hudson.model.queue.QueueTaskFuture;
import hudson.slaves.EnvironmentVariablesNodeProperty;
import hudson.tasks.Builder;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.Set;
import java.util.TreeSet;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.CaptureEnvironmentBuilder;
import org.jvnet.hudson.test.FailureBuilder;
import org.jvnet.hudson.test.FakeChangeLogSCM;
import org.jvnet.hudson.test.Issue;
import org.jvnet.hudson.test.JenkinsRule;
import org.jvnet.hudson.test.TestBuilder;
import org.jvnet.hudson.test.TestExtension;
import org.jvnet.hudson.test.UnstableBuilder;
/**
* Unit tests of {@link AbstractBuild}.
*/
public class AbstractBuildTest {
@Rule
public JenkinsRule j = new JenkinsRule();
@Test
@Issue("JENKINS-30730")
@SuppressWarnings("deprecation")
public void reportErrorShouldNotFailForNonPublisherClass() throws Exception {
FreeStyleProject prj = j.createFreeStyleProject();
ErroneousJobProperty errorneousJobProperty = new ErroneousJobProperty();
prj.addProperty(errorneousJobProperty);
QueueTaskFuture<FreeStyleBuild> future = prj.scheduleBuild2(0);
assertThat("Build should be actually scheduled by Jenkins", future, notNullValue());
FreeStyleBuild build = future.get();
j.assertLogContains(ErroneousJobProperty.ERROR_MESSAGE, build);
j.assertLogNotContains(ClassCastException.class.getName(), build);
}
/**
* Job property, which always fails with an exception.
*/
public static class ErroneousJobProperty extends JobProperty<FreeStyleProject> {
public static final String ERROR_MESSAGE = "This publisher fails by design";
@Override
public boolean perform(AbstractBuild<?, ?> build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException {
throw new IOException(ERROR_MESSAGE);
}
@TestExtension("reportErrorShouldNotFailForNonPublisherClass")
public static class DescriptorImpl extends JobPropertyDescriptor {}
}
@Test
public void variablesResolved() throws Exception {
FreeStyleProject project = j.createFreeStyleProject();
j.jenkins.getNodeProperties().add(new EnvironmentVariablesNodeProperty(new EnvironmentVariablesNodeProperty.Entry("KEY1", "value"), new EnvironmentVariablesNodeProperty.Entry("KEY2", "$KEY1")));
CaptureEnvironmentBuilder builder = new CaptureEnvironmentBuilder();
project.getBuildersList().add(builder);
j.buildAndAssertSuccess(project);
EnvVars envVars = builder.getEnvVars();
assertEquals("value", envVars.get("KEY1"));
assertEquals("value", envVars.get("KEY2"));
}
/**
* Makes sure that raw console output doesn't get affected by XML escapes.
*/
@Test
public void rawConsoleOutput() throws Exception {
final String out = "<test>&</test>";
FreeStyleProject p = j.createFreeStyleProject();
p.getBuildersList().add(new TestBuilder() {
@Override public boolean perform(AbstractBuild<?, ?> build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException {
listener.getLogger().println(out);
return true;
}
});
FreeStyleBuild b = j.buildAndAssertSuccess(p);
Page rsp = j.createWebClient().goTo(b.getUrl() + "/consoleText", "text/plain");
assertThat(rsp.getWebResponse().getContentAsString(), containsString(out));
}
private void assertCulprits(AbstractBuild<?,?> b, String... expectedIds) {
Set<String> actual = new TreeSet<>();
for (User u : b.getCulprits()) {
actual.add(u.getId());
}
assertEquals(actual, new TreeSet<>(Arrays.asList(expectedIds)));
}
@Test
public void culprits() throws Exception {
FreeStyleProject p = j.createFreeStyleProject();
FakeChangeLogSCM scm = new FakeChangeLogSCM();
p.setScm(scm);
// 1st build, successful, no culprits
scm.addChange().withAuthor("alice");
FreeStyleBuild b = j.buildAndAssertSuccess(p);
assertCulprits(b, "alice");
// 2nd build
scm.addChange().withAuthor("bob");
p.getBuildersList().add(new FailureBuilder());
b = j.assertBuildStatus(Result.FAILURE, p.scheduleBuild2(0).get());
assertCulprits(b, "bob");
// 3rd build. bob continues to be in culprit
scm.addChange().withAuthor("charlie");
b = j.assertBuildStatus(Result.FAILURE, p.scheduleBuild2(0).get());
assertCulprits(b, "bob", "charlie");
// 4th build, unstable. culprit list should continue
scm.addChange().withAuthor("dave");
p.getBuildersList().replaceBy(Collections.singleton(new UnstableBuilder()));
b = j.assertBuildStatus(Result.UNSTABLE, p.scheduleBuild2(0).get());
assertCulprits(b, "bob", "charlie", "dave");
// 5th build, unstable. culprit list should continue
scm.addChange().withAuthor("eve");
b = j.assertBuildStatus(Result.UNSTABLE, p.scheduleBuild2(0).get());
assertCulprits(b, "bob", "charlie", "dave", "eve");
// 6th build, success, accumulation continues up to this point
scm.addChange().withAuthor("fred");
p.getBuildersList().clear();
b = j.buildAndAssertSuccess(p);
assertCulprits(b, "bob", "charlie", "dave", "eve", "fred");
// 7th build, back to empty culprits
scm.addChange().withAuthor("george");
b = j.buildAndAssertSuccess(p);
assertCulprits(b, "george");
}
@Issue("JENKINS-19920")
@Test
public void lastBuildNextBuild() throws Exception {
FreeStyleProject p = j.createFreeStyleProject();
FreeStyleBuild b1 = j.buildAndAssertSuccess(p);
FreeStyleBuild b2 = j.buildAndAssertSuccess(p);
assertEquals(b2, p.getLastBuild());
b2.getNextBuild(); // force this to be initialized
b2.delete();
assertEquals(b1, p.getLastBuild());
b1 = p.getLastBuild();
assertNull(b1.getNextBuild());
}
@Test
public void doNotInterruptBuildAbruptlyWhenExceptionThrownFromBuildStep() throws Exception {
FreeStyleProject p = j.createFreeStyleProject();
p.getBuildersList().add(new ThrowBuilder());
FreeStyleBuild build = p.scheduleBuild2(0).get();
j.assertBuildStatus(Result.FAILURE, build);
j.assertLogContains("Finished: FAILURE", build);
j.assertLogContains("Build step 'ThrowBuilder' marked build as failure", build);
}
@Test
public void fixEmptyDisplayName() throws Exception {
FreeStyleProject p = j.createFreeStyleProject("foo");
p.setDisplayName("");
assertEquals("An empty display name should be ignored.", "foo", p.getDisplayName());
}
@Test
public void fixBlankDisplayName() throws Exception {
FreeStyleProject p = j.createFreeStyleProject("foo");
p.setDisplayName(" ");
assertEquals("A blank display name should be ignored.", "foo", p.getDisplayName());
}
@Test
public void validDisplayName() throws Exception {
FreeStyleProject p = j.createFreeStyleProject("foo");
p.setDisplayName("bar");
assertEquals("A non-blank display name should be used.", "bar", p.getDisplayName());
}
@Test
public void trimValidDisplayName() throws Exception {
FreeStyleProject p = j.createFreeStyleProject("foo");
p.setDisplayName(" bar ");
assertEquals("A non-blank display name with whitespace should be trimmed.", "bar", p.getDisplayName());
}
private static class ThrowBuilder extends Builder {
@Override public boolean perform(AbstractBuild<?,?> build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException {
throw new NullPointerException();
}
@TestExtension("doNotInterruptBuildAbruptlyWhenExceptionThrownFromBuildStep")
public static class DescriptorImpl extends Descriptor<Builder> {}
}
}