/* * The MIT License * * Copyright (c) 2004-2009, Sun Microsystems, Inc., Alan Harder * * 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.tasks; import static org.junit.Assert.*; import hudson.Launcher; import hudson.maven.MavenModuleSet; import hudson.maven.MavenModuleSetBuild; import hudson.model.AbstractBuild; import hudson.model.AbstractProject; import hudson.model.Action; import hudson.model.BuildListener; import hudson.model.Cause; import hudson.model.Computer; import hudson.model.FreeStyleBuild; import hudson.model.FreeStyleProject; import hudson.model.DependencyGraph; import hudson.model.DependencyGraph.Dependency; import hudson.model.Item; import hudson.model.Result; import hudson.model.Run; import hudson.model.TaskListener; import hudson.model.User; import hudson.security.ACL; import hudson.security.AuthorizationMatrixProperty; import hudson.security.LegacySecurityRealm; import hudson.security.Permission; import hudson.security.ProjectMatrixAuthorizationStrategy; import hudson.util.FormValidation; import java.io.File; import java.io.IOException; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.List; import javax.annotation.CheckForNull; import jenkins.model.Jenkins; import jenkins.security.QueueItemAuthenticatorConfiguration; import jenkins.triggers.ReverseBuildTriggerTest; import org.acegisecurity.Authentication; import org.acegisecurity.context.SecurityContext; import org.acegisecurity.context.SecurityContextHolder; import org.dom4j.DocumentException; import org.dom4j.io.SAXReader; import org.junit.Assume; import org.junit.ClassRule; import org.junit.Rule; import org.junit.Test; import org.jvnet.hudson.test.BuildWatcher; import org.jvnet.hudson.test.ExtractResourceSCM; import org.jvnet.hudson.test.Issue; import org.jvnet.hudson.test.JenkinsRule; import org.jvnet.hudson.test.JenkinsRule.WebClient; import org.jvnet.hudson.test.TestBuilder; import org.jvnet.hudson.test.MockBuilder; import org.jvnet.hudson.test.MockQueueItemAuthenticator; import org.jvnet.hudson.test.ToolInstallations; import org.xml.sax.SAXException; public class BuildTriggerTest { @Rule public JenkinsRule j = new JenkinsRule(); @ClassRule public static BuildWatcher buildWatcher = new BuildWatcher(); private FreeStyleProject createDownstreamProject() throws Exception { FreeStyleProject dp = j.createFreeStyleProject("downstream"); dp.setQuietPeriod(0); return dp; } private void doTriggerTest(boolean evenWhenUnstable, Result triggerResult, Result dontTriggerResult) throws Exception { FreeStyleProject p = j.createFreeStyleProject(); FreeStyleProject dp = createDownstreamProject(); p.getPublishersList().add(new BuildTrigger("downstream", evenWhenUnstable)); p.getBuildersList().add(new MockBuilder(dontTriggerResult)); j.jenkins.rebuildDependencyGraph(); // First build should not trigger downstream job FreeStyleBuild b = p.scheduleBuild2(0).get(); assertNoDownstreamBuild(dp, b); // Next build should trigger downstream job p.getBuildersList().replace(new MockBuilder(triggerResult)); b = p.scheduleBuild2(0).get(); assertDownstreamBuild(dp, b); } private void assertNoDownstreamBuild(FreeStyleProject dp, Run<?,?> b) throws Exception { for (int i = 0; i < 3; i++) { Thread.sleep(200); assertTrue("downstream build should not run! upstream log: " + b.getLog(), !dp.isInQueue() && !dp.isBuilding() && dp.getLastBuild()==null); } } private FreeStyleBuild assertDownstreamBuild(FreeStyleProject dp, Run<?,?> b) throws Exception { // Wait for downstream build for (int i = 0; dp.getLastBuild()==null && i < 20; i++) Thread.sleep(100); assertNotNull("downstream build didn't run.. upstream log: " + b.getLog(), dp.getLastBuild()); return dp.getLastBuild(); } @Test public void buildTrigger() throws Exception { doTriggerTest(false, Result.SUCCESS, Result.UNSTABLE); } @Test public void triggerEvenWhenUnstable() throws Exception { doTriggerTest(true, Result.UNSTABLE, Result.FAILURE); } private void doMavenTriggerTest(boolean evenWhenUnstable) throws Exception { File problematic = new File(System.getProperty("user.home"), ".m2/repository/org/apache/maven/plugins/maven-surefire-plugin/2.4.3/maven-surefire-plugin-2.4.3.pom"); if (problematic.isFile()) { try { new SAXReader().read(problematic); } catch (DocumentException x) { x.printStackTrace(); // somehow maven-surefire-plugin-2.4.3.pom got corrupted on CI builders Assume.assumeNoException(x); } } FreeStyleProject dp = createDownstreamProject(); ToolInstallations.configureDefaultMaven(); MavenModuleSet m = j.jenkins.createProject(MavenModuleSet.class, "p"); m.getPublishersList().add(new BuildTrigger("downstream", evenWhenUnstable)); if (!evenWhenUnstable) { // Configure for UNSTABLE m.setGoals("clean test"); m.setScm(new ExtractResourceSCM(getClass().getResource("maven-test-failure.zip"))); } // otherwise do nothing which gets FAILURE // First build should not trigger downstream project MavenModuleSetBuild b = m.scheduleBuild2(0).get(); assertNoDownstreamBuild(dp, b); if (evenWhenUnstable) { // Configure for UNSTABLE m.setGoals("clean test"); m.setScm(new ExtractResourceSCM(getClass().getResource("maven-test-failure.zip"))); } else { // Configure for SUCCESS m.setGoals("clean"); m.setScm(new ExtractResourceSCM(getClass().getResource("maven-empty.zip"))); } // Next build should trigger downstream project b = m.scheduleBuild2(0).get(); assertDownstreamBuild(dp, b); } @Test public void mavenBuildTrigger() throws Exception { doMavenTriggerTest(false); } @Test public void mavenTriggerEvenWhenUnstable() throws Exception { doMavenTriggerTest(true); } /** @see ReverseBuildTriggerTest#upstreamProjectSecurity */ @Test public void downstreamProjectSecurity() throws Exception { j.jenkins.setSecurityRealm(new LegacySecurityRealm()); ProjectMatrixAuthorizationStrategy auth = new ProjectMatrixAuthorizationStrategy(); auth.add(Jenkins.READ, "alice"); auth.add(Computer.BUILD, "alice"); auth.add(Computer.BUILD, "anonymous"); j.jenkins.setAuthorizationStrategy(auth); final FreeStyleProject upstream =j. createFreeStyleProject("upstream"); Authentication alice = User.get("alice").impersonate(); QueueItemAuthenticatorConfiguration.get().getAuthenticators().add(new MockQueueItemAuthenticator(Collections.singletonMap("upstream", alice))); Map<Permission,Set<String>> perms = new HashMap<Permission,Set<String>>(); perms.put(Item.READ, Collections.singleton("alice")); perms.put(Item.CONFIGURE, Collections.singleton("alice")); upstream.addProperty(new AuthorizationMatrixProperty(perms)); String downstreamName = "d0wnstr3am"; // do not clash with English messages! FreeStyleProject downstream = j.createFreeStyleProject(downstreamName); upstream.getPublishersList().add(new BuildTrigger(downstreamName, Result.SUCCESS)); j.jenkins.rebuildDependencyGraph(); /* The long way: WebClient wc = createWebClient(); wc.login("alice"); HtmlPage page = wc.getHistoryPageFilter(upstream, "configure"); HtmlForm config = page.getFormByName("config"); config.getButtonByCaption("Add post-build action").click(); // lib/hudson/project/config-publishers2.jelly page.getAnchorByText("Build other projects").click(); HtmlTextInput childProjects = config.getInputByName("buildTrigger.childProjects"); childProjects.setValueAttribute(downstreamName); submit(config); */ assertEquals(Collections.singletonList(downstream), upstream.getDownstreamProjects()); // Downstream projects whose existence we are not aware of will silently not be triggered: assertDoCheck(alice, Messages.BuildTrigger_NoSuchProject(downstreamName, "upstream"), upstream, downstreamName); assertDoCheck(alice, null, null, downstreamName); FreeStyleBuild b = j.buildAndAssertSuccess(upstream); j.assertLogNotContains(downstreamName, b); j.waitUntilNoActivity(); assertNull(downstream.getLastBuild()); // If we can see them, but not build them, that is a warning (but this is in cleanUp so the build is still considered a success): Map<Permission,Set<String>> grantedPermissions = new HashMap<Permission,Set<String>>(); grantedPermissions.put(Item.READ, Collections.singleton("alice")); AuthorizationMatrixProperty amp = new AuthorizationMatrixProperty(grantedPermissions); downstream.addProperty(amp); assertDoCheck(alice, Messages.BuildTrigger_you_have_no_permission_to_build_(downstreamName), upstream, downstreamName); assertDoCheck(alice, null, null, downstreamName); b = j.buildAndAssertSuccess(upstream); j.assertLogContains(downstreamName, b); j.waitUntilNoActivity(); assertNull(downstream.getLastBuild()); // If we can build them, then great: grantedPermissions.put(Item.BUILD, Collections.singleton("alice")); downstream.removeProperty(amp); amp = new AuthorizationMatrixProperty(grantedPermissions); downstream.addProperty(amp); assertDoCheck(alice, null, upstream, downstreamName); assertDoCheck(alice, null, null, downstreamName); b = j.buildAndAssertSuccess(upstream); j.assertLogContains(downstreamName, b); j.waitUntilNoActivity(); FreeStyleBuild b2 = downstream.getLastBuild(); assertNotNull(b2); Cause.UpstreamCause cause = b2.getCause(Cause.UpstreamCause.class); assertNotNull(cause); assertEquals(b, cause.getUpstreamRun()); // Now if we have configured some QIA’s but they are not active on this job, we should run as anonymous. Which would normally have no permissions: QueueItemAuthenticatorConfiguration.get().getAuthenticators().replace(new MockQueueItemAuthenticator(Collections.<String, Authentication>emptyMap())); assertDoCheck(alice, Messages.BuildTrigger_you_have_no_permission_to_build_(downstreamName), upstream, downstreamName); assertDoCheck(alice, null, null, downstreamName); b = j.buildAndAssertSuccess(upstream); j.assertLogNotContains(downstreamName, b); j.assertLogContains(Messages.BuildTrigger_warning_this_build_has_no_associated_aut(), b); j.waitUntilNoActivity(); assertEquals(1, downstream.getLastBuild().number); // Unless we explicitly granted them: grantedPermissions.put(Item.READ, Collections.singleton("anonymous")); grantedPermissions.put(Item.BUILD, Collections.singleton("anonymous")); downstream.removeProperty(amp); amp = new AuthorizationMatrixProperty(grantedPermissions); downstream.addProperty(amp); assertDoCheck(alice, null, upstream, downstreamName); assertDoCheck(alice, null, null, downstreamName); b = j.buildAndAssertSuccess(upstream); j.assertLogContains(downstreamName, b); j.waitUntilNoActivity(); assertEquals(2, downstream.getLastBuild().number); FreeStyleProject simple = j.createFreeStyleProject("simple"); FreeStyleBuild b3 = j.buildAndAssertSuccess(simple); // See discussion in BuildTrigger for why this is necessary: j.assertLogContains(Messages.BuildTrigger_warning_this_build_has_no_associated_aut(), b3); // Finally, in legacy mode we run as SYSTEM: grantedPermissions.clear(); // similar behavior but different message if DescriptorImpl removed downstream.removeProperty(amp); amp = new AuthorizationMatrixProperty(grantedPermissions); downstream.addProperty(amp); QueueItemAuthenticatorConfiguration.get().getAuthenticators().clear(); assertDoCheck(alice, Messages.BuildTrigger_NoSuchProject(downstreamName, "upstream"), upstream, downstreamName); assertDoCheck(alice, null, null, downstreamName); b = j.buildAndAssertSuccess(upstream); j.assertLogContains(downstreamName, b); j.assertLogContains(Messages.BuildTrigger_warning_access_control_for_builds_in_glo(), b); j.waitUntilNoActivity(); assertEquals(3, downstream.getLastBuild().number); b3 = j.buildAndAssertSuccess(simple); j.assertLogNotContains(Messages.BuildTrigger_warning_access_control_for_builds_in_glo(), b3); } private void assertDoCheck(Authentication auth, @CheckForNull String expectedError, AbstractProject<?, ?> project, String value) { FormValidation result; SecurityContext orig = ACL.impersonate(auth); try { result = j.jenkins.getDescriptorByType(BuildTrigger.DescriptorImpl.class).doCheck(project, value); } finally { SecurityContextHolder.setContext(orig); } if (expectedError == null) { assertEquals(result.renderHtml(), FormValidation.Kind.OK, result.kind); } else { assertEquals(result.renderHtml(), FormValidation.Kind.ERROR, result.kind); assertEquals(result.renderHtml(), expectedError); } } @Test @Issue("JENKINS-20989") public void downstreamProjectShouldObserveCompletedParent() throws Exception { j.jenkins.setNumExecutors(2); final FreeStyleProject us = j.createFreeStyleProject(); us.getPublishersList().add(new BuildTrigger("downstream", true)); FreeStyleProject ds = createDownstreamProject(); ds.getBuildersList().add(new AssertTriggerBuildCompleted(us, j.createWebClient())); j.jenkins.rebuildDependencyGraph(); j.buildAndAssertSuccess(us); j.waitUntilNoActivity(); final FreeStyleBuild dsb = ds.getBuildByNumber(1); assertNotNull(dsb); j.waitForCompletion(dsb); j.assertBuildStatusSuccess(dsb); } @Test @Issue("JENKINS-20989") public void allDownstreamProjectsShouldObserveCompletedParent() throws Exception { j.jenkins.setNumExecutors(3); final FreeStyleProject us = j.createFreeStyleProject(); us.getPublishersList().add(new SlowTrigger("downstream,downstream2")); FreeStyleProject ds = createDownstreamProject(); ds.getBuildersList().add(new AssertTriggerBuildCompleted(us, j.createWebClient())); FreeStyleProject ds2 = j.createFreeStyleProject("downstream2"); ds2.setQuietPeriod(0); ds2.getBuildersList().add(new AssertTriggerBuildCompleted(us, j.createWebClient())); j.jenkins.rebuildDependencyGraph(); FreeStyleBuild upstream = j.buildAndAssertSuccess(us); FreeStyleBuild dsb = assertDownstreamBuild(ds, upstream); j.waitForCompletion(dsb); j.assertBuildStatusSuccess(dsb); dsb = assertDownstreamBuild(ds2, upstream); j.waitForCompletion(dsb); j.assertBuildStatusSuccess(dsb); } // Trigger that goes through dependencies very slowly private static final class SlowTrigger extends BuildTrigger { private static final class Dep extends Dependency { private static boolean block = false; private Dep(AbstractProject upstream, AbstractProject downstream) { super(upstream, downstream); } @Override public boolean shouldTriggerBuild(AbstractBuild build, TaskListener listener, List<Action> actions) { if (block) { try { Thread.sleep(5000); } catch (InterruptedException ex) { throw new AssertionError(ex); } } block = true; final boolean should = super.shouldTriggerBuild(build, listener, actions); return should; } } public SlowTrigger(String childProjects) { super(childProjects, true); } @Override @SuppressWarnings("rawtypes") public void buildDependencyGraph(AbstractProject owner, DependencyGraph graph) { for (AbstractProject ch: getChildProjects(owner)) { graph.addDependency(new Dep(owner, ch)); } } } // Fail downstream build if upstream is not completed yet private static final class AssertTriggerBuildCompleted extends TestBuilder { private final FreeStyleProject us; private final WebClient wc; private AssertTriggerBuildCompleted(FreeStyleProject us, WebClient wc) { this.us = us; this.wc = wc; } @Override public boolean perform(AbstractBuild<?, ?> build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException { FreeStyleBuild success = us.getLastSuccessfulBuild(); FreeStyleBuild last = us.getLastBuild(); try { assertFalse("Upstream build is not completed after downstream started", last.isBuilding()); assertNotNull("Upstream build permalink not correctly updated", success); assertEquals(1, success.getNumber()); } catch (AssertionError ex) { System.err.println("Upstream build log: " + last.getLog()); throw ex; } try { wc.getPage(us, "lastSuccessfulBuild"); } catch (SAXException ex) { throw new AssertionError(ex); } return true; } } }