/* * The MIT License * * Copyright 2013 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.model; import com.gargoylesoftware.htmlunit.HttpMethod; import com.gargoylesoftware.htmlunit.WebRequest; import hudson.model.queue.QueueTaskFuture; import hudson.security.AccessDeniedException2; import org.acegisecurity.context.SecurityContextHolder; import hudson.security.HudsonPrivateSecurityRealm; import hudson.security.GlobalMatrixAuthorizationStrategy; import java.net.URL; import java.util.Collections; import org.jvnet.hudson.reactor.ReactorException; import org.jvnet.hudson.test.FakeChangeLogSCM; import hudson.scm.SCMRevisionState; import hudson.scm.PollingResult; import hudson.Launcher; import hudson.Launcher.RemoteLauncher; import hudson.Util; import hudson.scm.NullSCM; import hudson.scm.SCM; import hudson.model.queue.SubTaskContributor; import hudson.model.queue.AbstractSubTask; import hudson.model.Queue.Executable; import hudson.model.Queue.Task; import hudson.model.queue.SubTask; import hudson.model.AbstractProject.BecauseOfUpstreamBuildInProgress; import hudson.model.AbstractProject.BecauseOfDownstreamBuildInProgress; import jenkins.model.WorkspaceWriter; import jenkins.model.Jenkins; import antlr.ANTLRException; import hudson.triggers.SCMTrigger; import hudson.model.Cause.LegacyCodeCause; import com.gargoylesoftware.htmlunit.html.HtmlElement; import com.gargoylesoftware.htmlunit.html.HtmlForm; import com.gargoylesoftware.htmlunit.html.HtmlPage; import java.io.Serializable; import jenkins.scm.DefaultSCMCheckoutStrategyImpl; import jenkins.scm.SCMCheckoutStrategy; import java.io.File; import hudson.FilePath; import hudson.slaves.EnvironmentVariablesNodeProperty; import hudson.EnvVars; import hudson.model.labels.LabelAtom; import hudson.scm.SCMDescriptor; import hudson.slaves.Cloud; import hudson.slaves.DumbSlave; import hudson.slaves.NodeProvisioner; import hudson.tasks.Shell; import org.jvnet.hudson.test.TestExtension; import java.util.List; import java.util.ArrayList; import java.io.IOException; import java.nio.charset.Charset; import java.util.Collection; import org.junit.Rule; import org.junit.Test; import org.jvnet.hudson.test.JenkinsRule; import static org.junit.Assert.*; import hudson.tasks.Fingerprinter; import hudson.tasks.ArtifactArchiver; import hudson.tasks.BuildTrigger; import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.logging.Logger; import jenkins.model.BlockedBecauseOfBuildInProgress; import org.junit.Ignore; import org.jvnet.hudson.test.Issue; import org.jvnet.hudson.test.TestBuilder; /** * * @author Lucie Votypkova */ public class ProjectTest { @Rule public JenkinsRule j = new JenkinsRule(); public static boolean createAction = false; public static boolean getFilePath = false; public static boolean createSubTask = false; @Test public void testSave() throws IOException, InterruptedException, ReactorException { FreeStyleProject p = j.createFreeStyleProject("project"); p.disabled = true; p.nextBuildNumber = 5; p.description = "description"; p.save(); j.jenkins.reload(); assertEquals("All persistent data should be saved.", "description", p.description); assertEquals("All persistent data should be saved.", 5, p.nextBuildNumber); assertEquals("All persistent data should be saved", true, p.disabled); } @Test public void testOnCreateFromScratch() throws IOException, Exception{ FreeStyleProject p = j.createFreeStyleProject("project"); j.buildAndAssertSuccess(p); p.removeRun(p.getLastBuild()); createAction = true; p.onCreatedFromScratch(); assertNotNull("Project should have last build.", p.getLastBuild()); assertNotNull("Project should have transient action TransientAction.", p.getAction(TransientAction.class)); createAction = false; } @Test public void testOnLoad() throws IOException, Exception{ FreeStyleProject p = j.createFreeStyleProject("project"); j.buildAndAssertSuccess(p); p.removeRun(p.getLastBuild()); createAction = true; p.onLoad(j.jenkins, "project"); assertTrue("Project should have a build.", p.getLastBuild()!=null); assertTrue("Project should have a scm.", p.getScm()!=null); assertTrue("Project should have Transient Action TransientAction.", p.getAction(TransientAction.class)!=null); createAction = false; } @Test public void testGetEnvironment() throws Exception{ FreeStyleProject p = j.createFreeStyleProject("project"); Slave slave = j.createOnlineSlave(); EnvironmentVariablesNodeProperty.Entry entry = new EnvironmentVariablesNodeProperty.Entry("jdk","some_java"); slave.getNodeProperties().add(new EnvironmentVariablesNodeProperty(entry)); EnvVars var = p.getEnvironment(slave, TaskListener.NULL); assertEquals("Environment should have set jdk.", "some_java", var.get("jdk")); } @Test public void testPerformDelete() throws IOException, Exception{ FreeStyleProject p = j.createFreeStyleProject("project"); p.performDelete(); assertFalse("Project should be deleted from disk.", p.getConfigFile().exists()); assertTrue("Project should be disabled when deleting start.", p.isDisabled()); } @Test public void testGetAssignedLabel() throws Exception{ FreeStyleProject p = j.createFreeStyleProject("project"); p.setAssignedLabel(j.jenkins.getSelfLabel()); Slave slave = j.createOnlineSlave(); assertEquals("Project should have Jenkins's self label.", j.jenkins.getSelfLabel(), p.getAssignedLabel()); p.setAssignedLabel(null); assertNull("Project should not have any label.", p.getAssignedLabel()); p.setAssignedLabel(slave.getSelfLabel()); assertEquals("Project should have self label of slave", slave.getSelfLabel(), p.getAssignedLabel()); } @Test public void testGetAssignedLabelString() throws Exception{ FreeStyleProject p = j.createFreeStyleProject("project"); Slave slave = j.createOnlineSlave(); assertNull("Project should not have any label.", p.getAssignedLabelString()); p.setAssignedLabel(j.jenkins.getSelfLabel()); assertNull("Project should return null, because assigned label is Jenkins.", p.getAssignedLabelString()); p.setAssignedLabel(slave.getSelfLabel()); assertEquals("Project should return name of slave.", slave.getSelfLabel().name, p.getAssignedLabelString()); } @Test public void testGetSomeWorkspace() throws Exception{ FreeStyleProject p = j.createFreeStyleProject("project"); assertNull("Project which has never run should not have any workspace.", p.getSomeWorkspace()); getFilePath = true; assertNotNull("Project should have any workspace because WorkspaceBrowser find some.", p.getSomeWorkspace()); getFilePath = false; p.getBuildersList().add(new Shell("echo ahoj > some.log")); j.buildAndAssertSuccess(p); assertNotNull("Project should has any workspace.", p.getSomeWorkspace()); } @Test public void testGetSomeBuildWithWorkspace() throws Exception{ FreeStyleProject p = j.createFreeStyleProject("project"); p.getBuildersList().add(new Shell("echo ahoj > some.log")); assertNull("Project which has never run should not have any build with workspace.", p.getSomeBuildWithWorkspace()); j.buildAndAssertSuccess(p); assertEquals("Last build should have workspace.", p.getLastBuild(), p.getSomeBuildWithWorkspace()); p.getLastBuild().delete(); assertNull("Project should not have build with some workspace.", p.getSomeBuildWithWorkspace()); } @Issue("JENKINS-10450") @Test public void workspaceBrowsing() throws Exception { FreeStyleProject p = j.createFreeStyleProject("project"); p.getBuildersList().add(new Shell("echo ahoj > some.log")); j.buildAndAssertSuccess(p); JenkinsRule.WebClient wc = j.createWebClient(); wc.goTo("job/project/ws/some.log", "text/plain"); wc.assertFails("job/project/ws/other.log", 404); p.doDoWipeOutWorkspace(); wc.assertFails("job/project/ws/some.log", 404); } @Test public void testGetQuietPeriod() throws IOException{ FreeStyleProject p = j.createFreeStyleProject("project"); assertEquals("Quiet period should be default.", j.jenkins.getQuietPeriod(), p.getQuietPeriod()); j.jenkins.setQuietPeriod(0); assertEquals("Quiet period is not set so it should be the same as global quiet period.", 0, p.getQuietPeriod()); p.setQuietPeriod(10); assertEquals("Quiet period was set.",p.getQuietPeriod(),10); } @Test public void testGetScmCheckoutStrategy() throws IOException{ FreeStyleProject p = j.createFreeStyleProject("project"); p.setScmCheckoutStrategy(null); assertTrue("Project should return default checkout strategy if scm checkout strategy is not set.", p.getScmCheckoutStrategy() instanceof DefaultSCMCheckoutStrategyImpl); SCMCheckoutStrategy strategy = new SCMCheckoutStrategyImpl(); p.setScmCheckoutStrategy(strategy); assertEquals("Project should return its scm checkout strategy if this strategy is not null", strategy, p.getScmCheckoutStrategy()); } @Test public void testGetScmCheckoutRetryCount() throws Exception{ FreeStyleProject p = j.createFreeStyleProject("project"); assertEquals("Scm retry count should be default.", j.jenkins.getScmCheckoutRetryCount(), p.getScmCheckoutRetryCount()); j.jenkins.setScmCheckoutRetryCount(6); assertEquals("Scm retry count should be the same as global scm retry count.", 6, p.getScmCheckoutRetryCount()); HtmlForm form = j.createWebClient().goTo(p.getUrl() + "/configure").getFormByName("config"); ((HtmlElement)form.getByXPath("//div[@class='advancedLink']//button").get(0)).click(); form.getInputByName("hasCustomScmCheckoutRetryCount").click(); form.getInputByName("scmCheckoutRetryCount").setValueAttribute("7"); j.submit(form); assertEquals("Scm retry count was set.", 7, p.getScmCheckoutRetryCount()); } @Test public void isBuildable() throws IOException{ FreeStyleProject p = j.createFreeStyleProject("project"); assertTrue("Project should be buildable.", p.isBuildable()); p.disable(); assertFalse("Project should not be buildable if it is disabled.", p.isBuildable()); p.enable(); AbstractProject p2 = (AbstractProject) j.jenkins.copy(j.jenkins.getItem("project"), "project2"); assertFalse("Project should not be buildable until is saved.", p2.isBuildable()); p2.save(); assertTrue("Project should be buildable after save.", p2.isBuildable()); } @Test public void testMakeDisabled() throws IOException{ FreeStyleProject p = j.createFreeStyleProject("project"); p.makeDisabled(false); assertFalse("Project should be enabled.", p.isDisabled()); p.makeDisabled(true); assertTrue("Project should be disabled.", p.isDisabled()); p.makeDisabled(false); p.setAssignedLabel(j.jenkins.getLabel("nonExist")); p.scheduleBuild2(0); p.makeDisabled(true); assertNull("Project should be canceled.", Queue.getInstance().getItem(p)); } @Test public void testAddProperty() throws IOException{ FreeStyleProject p = j.createFreeStyleProject("project"); JobProperty prop = new JobPropertyImp(); createAction = true; p.addProperty(prop); assertNotNull("Project does not contain added property.", p.getProperty(prop.getClass())); assertNotNull("Project did not update transient actions.", p.getAction(TransientAction.class)); } @Test public void testScheduleBuild2() throws IOException, InterruptedException{ FreeStyleProject p = j.createFreeStyleProject("project"); p.setAssignedLabel(j.jenkins.getLabel("nonExist")); p.scheduleBuild(0, new LegacyCodeCause(), new Action[0]); assertNotNull("Project should be in queue.", Queue.getInstance().getItem(p)); p.setAssignedLabel(null); int count = 0; while(count<5 && p.getLastBuild()==null){ Thread.sleep(1000); //give some time to start build count++; } assertNotNull("Build should be done or in progress.", p.getLastBuild()); } @Test public void testSchedulePolling() throws IOException, ANTLRException{ FreeStyleProject p = j.createFreeStyleProject("project"); assertFalse("Project should not schedule polling because no scm trigger is set.",p.schedulePolling()); SCMTrigger trigger = new SCMTrigger("0 0 * * *"); p.addTrigger(trigger); trigger.start(p, true); assertTrue("Project should schedule polling.", p.schedulePolling()); p.disable(); assertFalse("Project should not schedule polling because project is disabled.", p.schedulePolling()); } @Test public void testSaveAfterSet() throws Exception, ReactorException { FreeStyleProject p = j.createFreeStyleProject("project"); p.setScm(new NullSCM()); p.setScmCheckoutStrategy(new SCMCheckoutStrategyImpl()); p.setQuietPeriod(15); p.setBlockBuildWhenDownstreamBuilding(true); p.setBlockBuildWhenUpstreamBuilding(true); j.jenkins.getJDKs().add(new JDK("jdk", "path")); j.jenkins.save(); p.setJDK(j.jenkins.getJDK("jdk")); p.setCustomWorkspace("/some/path"); j.jenkins.reload(); assertNotNull("Project did not save scm.", p.getScm()); assertTrue("Project did not save scm checkout strategy.", p.getScmCheckoutStrategy() instanceof SCMCheckoutStrategyImpl); assertEquals("Project did not save quiet period.", 15, p.getQuietPeriod()); assertTrue("Project did not save block if downstream is building.", p.blockBuildWhenDownstreamBuilding()); assertTrue("Project did not save block if upstream is buildidng.", p.blockBuildWhenUpstreamBuilding()); assertNotNull("Project did not save jdk", p.getJDK()); assertEquals("Project did not save custom workspace.", "/some/path", p.getCustomWorkspace()); } @Test public void testGetActions() throws IOException{ FreeStyleProject p = j.createFreeStyleProject("project"); createAction = true; p.updateTransientActions(); assertNotNull("Action should contain transient actions too.", p.getAction(TransientAction.class)); createAction = false; } // for debugging // static { // Logger.getLogger("").getHandlers()[0].setFormatter(new MilliSecLogFormatter()); // } @Test public void testGetCauseOfBlockage() throws Exception { FreeStyleProject p = j.createFreeStyleProject("project"); p.getBuildersList().add(new Shell("sleep 10")); QueueTaskFuture<FreeStyleBuild> b1 = waitForStart(p); assertInstanceOf("Build can not start because previous build has not finished: " + p.getCauseOfBlockage(), p.getCauseOfBlockage(), BlockedBecauseOfBuildInProgress.class); p.getLastBuild().getExecutor().interrupt(); b1.get(); // wait for it to finish FreeStyleProject downstream = j.createFreeStyleProject("project-downstream"); downstream.getBuildersList().add(new Shell("sleep 10")); p.getPublishersList().add(new BuildTrigger(Collections.singleton(downstream), Result.SUCCESS)); Jenkins.getInstance().rebuildDependencyGraph(); p.setBlockBuildWhenDownstreamBuilding(true); QueueTaskFuture<FreeStyleBuild> b2 = waitForStart(downstream); assertInstanceOf("Build can not start because build of downstream project has not finished.", p.getCauseOfBlockage(), BecauseOfDownstreamBuildInProgress.class); downstream.getLastBuild().getExecutor().interrupt(); b2.get(); downstream.setBlockBuildWhenUpstreamBuilding(true); waitForStart(p); assertInstanceOf("Build can not start because build of upstream project has not finished.", downstream.getCauseOfBlockage(), BecauseOfUpstreamBuildInProgress.class); } private static final Logger LOGGER = Logger.getLogger(ProjectTest.class.getName()); private QueueTaskFuture<FreeStyleBuild> waitForStart(FreeStyleProject p) throws InterruptedException, ExecutionException { long start = System.nanoTime(); LOGGER.info("Scheduling "+p); QueueTaskFuture<FreeStyleBuild> f = p.scheduleBuild2(0); f.waitForStart(); LOGGER.info("Wait:"+ TimeUnit.NANOSECONDS.toMillis(System.nanoTime()-start)); return f; } private void assertInstanceOf(String msg, Object o, Class t) { if (t.isInstance(o)) return; fail(msg + ": " + o); } @Test public void testGetSubTasks() throws IOException{ FreeStyleProject p = j.createFreeStyleProject("project"); p.addProperty(new JobPropertyImp()); createSubTask = true; List<SubTask> subtasks = p.getSubTasks(); boolean containsSubTaskImpl = false; boolean containsSubTaskImpl2 = false; for(SubTask sub: subtasks){ if(sub instanceof SubTaskImpl) containsSubTaskImpl = true; if(sub instanceof SubTaskImpl2) containsSubTaskImpl2 = true; } createSubTask = false; assertTrue("Project should return subtasks provided by SubTaskContributor.", containsSubTaskImpl2); assertTrue("Project should return subtasks provided by JobProperty.", containsSubTaskImpl); } @Test public void testCreateExecutable() throws IOException{ FreeStyleProject p = j.createFreeStyleProject("project"); Build build = p.createExecutable(); assertNotNull("Project should create executable.", build); assertEquals("CreatedExecutable should be the last build.", build, p.getLastBuild()); assertEquals("Next build number should be increased.", 2, p.nextBuildNumber); p.disable(); build = p.createExecutable(); assertNull("Disabled project should not create executable.", build); assertEquals("Next build number should not be increased.", 2, p.nextBuildNumber); } @Test public void testCheckout() throws IOException, Exception{ SCM scm = new NullSCM(); FreeStyleProject p = j.createFreeStyleProject("project"); Slave slave = j.createOnlineSlave(); AbstractBuild build = p.createExecutable(); FilePath ws = slave.getWorkspaceFor(p); assertNotNull(ws); FilePath path = slave.toComputer().getWorkspaceList().allocate(ws, build).path; build.setWorkspace(path); BuildListener listener = new StreamBuildListener(BuildListener.NULL.getLogger(), Charset.defaultCharset()); assertTrue("Project with null smc should perform checkout without problems.", p.checkout(build, new RemoteLauncher(listener, slave.getChannel(), true), listener, new File(build.getRootDir(),"changelog.xml"))); p.setScm(scm); assertTrue("Project should perform checkout without problems.",p.checkout(build, new RemoteLauncher(listener, slave.getChannel(), true), listener, new File(build.getRootDir(),"changelog.xml"))); } @Ignore("randomly failed: Project should have polling result no change expected:<NONE> but was:<INCOMPARABLE>") @Test public void testPoll() throws Exception{ FreeStyleProject p = j.createFreeStyleProject("project"); SCM scm = new NullSCM(); p.setScm(null); SCM alwaysChange = new AlwaysChangedSCM(); assertEquals("Project with null scm should have have polling result no change.", PollingResult.Change.NONE, p.poll(TaskListener.NULL).change); p.setScm(scm); p.disable(); assertEquals("Project which is disabled should have have polling result no change.", PollingResult.Change.NONE, p.poll(TaskListener.NULL).change); p.enable(); assertEquals("Project which has no builds should have have polling result incomparable.", PollingResult.Change.INCOMPARABLE, p.poll(TaskListener.NULL).change); p.setAssignedLabel(j.jenkins.getLabel("nonExist")); p.scheduleBuild2(0); assertEquals("Project which build is building should have polling result result no change.", PollingResult.Change.NONE, p.poll(TaskListener.NULL).change); p.setAssignedLabel(null); while(p.getLastBuild()==null) Thread.sleep(100); //wait until build start assertEquals("Project should have polling result no change", PollingResult.Change.NONE, p.poll(TaskListener.NULL).change); p.setScm(alwaysChange); j.buildAndAssertSuccess(p); assertEquals("Project should have polling result significant", PollingResult.Change.SIGNIFICANT, p.poll(TaskListener.NULL).change); } @Test public void testHasParticipant() throws Exception{ User user = User.get("John Smith", true, Collections.emptyMap()); FreeStyleProject project = j.createFreeStyleProject("project"); FreeStyleProject project2 = j.createFreeStyleProject("project2"); FakeChangeLogSCM scm = new FakeChangeLogSCM(); project2.setScm(scm); j.buildAndAssertSuccess(project2); assertFalse("Project should not have any participant.", project2.hasParticipant(user)); scm.addChange().withAuthor(user.getId()); project.setScm(scm); j.buildAndAssertSuccess(project); assertTrue("Project should have participant.", project.hasParticipant(user)); } @Test public void testGetRelationship() throws Exception{ final FreeStyleProject upstream = j.createFreeStyleProject("upstream"); FreeStyleProject downstream = j.createFreeStyleProject("downstream"); j.buildAndAssertSuccess(upstream); j.buildAndAssertSuccess(upstream); j.buildAndAssertSuccess(downstream); assertTrue("Project upstream should not have any relationship with downstream", upstream.getRelationship(downstream).isEmpty()); upstream.getPublishersList().add(new Fingerprinter("change.log", true)); upstream.getBuildersList().add(new WorkspaceWriter("change.log", "hello")); upstream.getPublishersList().add(new ArtifactArchiver("change.log")); downstream.getPublishersList().add(new Fingerprinter("change.log", false)); downstream.getBuildersList().add(new TestBuilder() { @Override public boolean perform(AbstractBuild<?, ?> build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException { for (Run<?, ?>.Artifact a: upstream.getLastBuild().getArtifacts()) { Util.copyFile(a.getFile(), new File(build.getWorkspace().child(a.getFileName()).getRemote())); } return true; } }); j.buildAndAssertSuccess(upstream); j.buildAndAssertSuccess(downstream); j.buildAndAssertSuccess(upstream); j.buildAndAssertSuccess(downstream); upstream.getBuildersList().add(new WorkspaceWriter("change.log", "helloWorld")); j.buildAndAssertSuccess(upstream); j.buildAndAssertSuccess(downstream); Map<Integer,Fingerprint.RangeSet> relationship = upstream.getRelationship(downstream); assertFalse("Project upstream should have relationship with downstream", relationship.isEmpty()); assertTrue("Relationship should contain upstream #3", relationship.keySet().contains(3)); assertFalse("Relationship should not contain upstream #4 because previous fingerprinted file was not changed since #3", relationship.keySet().contains(4)); assertEquals("downstream #2 should be the first build which depends on upstream #3", 2, relationship.get(3).min()); assertEquals("downstream #3 should be the last build which depends on upstream #3", 3, relationship.get(3).max()-1); assertEquals("downstream #4 should depend only on upstream #5", 4, relationship.get(5).min()); assertEquals("downstream #4 should depend only on upstream #5", 4, relationship.get(5).max()-1); } @Test public void testDoCancelQueue() throws Exception{ FreeStyleProject project = j.createFreeStyleProject("project"); GlobalMatrixAuthorizationStrategy auth = new GlobalMatrixAuthorizationStrategy(); j.jenkins.setAuthorizationStrategy(auth); j.jenkins.setCrumbIssuer(null); HudsonPrivateSecurityRealm realm = new HudsonPrivateSecurityRealm(false); j.jenkins.setSecurityRealm(realm); User user = realm.createAccount("John Smith", "password"); SecurityContextHolder.getContext().setAuthentication(user.impersonate()); try{ project.doCancelQueue(null, null); fail("User should not have permission to build project"); } catch(Exception e){ if(!(e.getClass().isAssignableFrom(AccessDeniedException2.class))){ fail("AccessDeniedException should be thrown."); } } } @Test public void testDoDoDelete() throws Exception{ FreeStyleProject project = j.createFreeStyleProject("project"); GlobalMatrixAuthorizationStrategy auth = new GlobalMatrixAuthorizationStrategy(); j.jenkins.setAuthorizationStrategy(auth); j.jenkins.setCrumbIssuer(null); HudsonPrivateSecurityRealm realm = new HudsonPrivateSecurityRealm(false); j.jenkins.setSecurityRealm(realm); User user = realm.createAccount("John Smith", "password"); SecurityContextHolder.getContext().setAuthentication(user.impersonate()); try{ project.doDoDelete(null, null); fail("User should not have permission to build project"); } catch(Exception e){ if(!(e.getClass().isAssignableFrom(AccessDeniedException2.class))){ fail("AccessDeniedException should be thrown."); } } auth.add(Jenkins.READ, user.getId()); auth.add(Job.READ, user.getId()); auth.add(Job.DELETE, user.getId()); List<HtmlForm> forms = j.createWebClient().login(user.getId(), "password").goTo(project.getUrl() + "delete").getForms(); for(HtmlForm form:forms){ if("doDelete".equals(form.getAttribute("action"))){ j.submit(form); } } assertNull("Project should be deleted form memory.", j.jenkins.getItem(project.getDisplayName())); assertFalse("Project should be deleted form disk.", project.getRootDir().exists()); } @Test public void testDoDoWipeOutWorkspace() throws Exception{ FreeStyleProject project = j.createFreeStyleProject("project"); GlobalMatrixAuthorizationStrategy auth = new GlobalMatrixAuthorizationStrategy(); j.jenkins.setAuthorizationStrategy(auth); j.jenkins.setCrumbIssuer(null); HudsonPrivateSecurityRealm realm = new HudsonPrivateSecurityRealm(false); j.jenkins.setSecurityRealm(realm); User user = realm.createAccount("John Smith", "password"); SecurityContextHolder.getContext().setAuthentication(user.impersonate()); try{ project.doDoWipeOutWorkspace(); fail("User should not have permission to build project"); } catch(Exception e){ if(!(e.getClass().isAssignableFrom(AccessDeniedException2.class))){ fail("AccessDeniedException should be thrown."); } } auth.add(Job.READ, user.getId()); auth.add(Job.BUILD, user.getId()); auth.add(Job.WIPEOUT, user.getId()); auth.add(Jenkins.READ, user.getId()); Slave slave = j.createOnlineSlave(); project.setAssignedLabel(slave.getSelfLabel()); project.getBuildersList().add(new Shell("echo hello > change.log")); j.buildAndAssertSuccess(project); JenkinsRule.WebClient wc = j.createWebClient().login(user.getId(), "password"); WebRequest request = new WebRequest(new URL(wc.getContextPath() + project.getUrl() + "doWipeOutWorkspace"), HttpMethod.POST); HtmlPage p = wc.getPage(request); Thread.sleep(500); assertFalse("Workspace should not exist.", project.getSomeWorkspace().exists()); } @Test public void testDoDisable() throws Exception{ FreeStyleProject project = j.createFreeStyleProject("project"); GlobalMatrixAuthorizationStrategy auth = new GlobalMatrixAuthorizationStrategy(); j.jenkins.setAuthorizationStrategy(auth); j.jenkins.setCrumbIssuer(null); HudsonPrivateSecurityRealm realm = new HudsonPrivateSecurityRealm(false); j.jenkins.setSecurityRealm(realm); User user = realm.createAccount("John Smith", "password"); SecurityContextHolder.getContext().setAuthentication(user.impersonate()); try{ project.doDisable(); fail("User should not have permission to build project"); } catch(Exception e){ if(!(e.getClass().isAssignableFrom(AccessDeniedException2.class))){ fail("AccessDeniedException should be thrown."); } } auth.add(Job.READ, user.getId()); auth.add(Job.CONFIGURE, user.getId()); auth.add(Jenkins.READ, user.getId()); List<HtmlForm> forms = j.createWebClient().login(user.getId(), "password").goTo(project.getUrl()).getForms(); for(HtmlForm form:forms){ if("disable".equals(form.getAttribute("action"))){ j.submit(form); } } assertTrue("Project should be disabled.", project.isDisabled()); } @Test public void testDoEnable() throws Exception{ FreeStyleProject project = j.createFreeStyleProject("project"); GlobalMatrixAuthorizationStrategy auth = new GlobalMatrixAuthorizationStrategy(); j.jenkins.setAuthorizationStrategy(auth); j.jenkins.setCrumbIssuer(null); HudsonPrivateSecurityRealm realm = new HudsonPrivateSecurityRealm(false); j.jenkins.setSecurityRealm(realm); User user = realm.createAccount("John Smith", "password"); SecurityContextHolder.getContext().setAuthentication(user.impersonate()); project.disable(); try{ project.doEnable(); fail("User should not have permission to build project"); } catch(Exception e){ if(!(e.getClass().isAssignableFrom(AccessDeniedException2.class))){ fail("AccessDeniedException should be thrown."); } } auth.add(Job.READ, user.getId()); auth.add(Job.CONFIGURE, user.getId()); auth.add(Jenkins.READ, user.getId()); List<HtmlForm> forms = j.createWebClient().login(user.getId(), "password").goTo(project.getUrl()).getForms(); for(HtmlForm form:forms){ if("enable".equals(form.getAttribute("action"))){ j.submit(form); } } assertFalse("Project should be enabled.", project.isDisabled()); } /** * Job is un-restricted (no nabel), this is submitted to queue, which spawns an on demand slave * @throws Exception */ @Test public void testJobSubmittedShouldSpawnCloud() throws Exception { /** * Setup a project with an SCM. Jenkins should have no executors in itself. */ FreeStyleProject proj = j.createFreeStyleProject("JENKINS-21394-spawn"); RequiresWorkspaceSCM requiresWorkspaceScm = new RequiresWorkspaceSCM(true); proj.setScm(requiresWorkspaceScm); j.jenkins.setNumExecutors(0); /* * We have a cloud */ DummyCloudImpl2 c2 = new DummyCloudImpl2(j, 0); c2.label = new LabelAtom("test-cloud-label"); j.jenkins.clouds.add(c2); SCMTrigger t = new SCMTrigger("@daily", true); t.start(proj, true); proj.addTrigger(t); t.new Runner().run(); Thread.sleep(1000); //Assert that the job IS submitted to Queue. assertEquals(1, j.jenkins.getQueue().getItems().length); } /** * Job is restricted, but label can not be provided by any cloud, only normal slaves. Then job will not submit, because no slave is available. * @throws Exception */ @Test public void testUnrestrictedJobNoLabelByCloudNoQueue() throws Exception { assertTrue(j.jenkins.clouds.isEmpty()); //Create slave. (Online) Slave s1 = j.createOnlineSlave(); //Create a project, and bind the job to the created slave FreeStyleProject proj = j.createFreeStyleProject("JENKINS-21394-noqueue"); proj.setAssignedLabel(s1.getSelfLabel()); //Add an SCM to the project. We require a workspace for the poll RequiresWorkspaceSCM requiresWorkspaceScm = new RequiresWorkspaceSCM(true); proj.setScm(requiresWorkspaceScm); j.buildAndAssertSuccess(proj); //Now create another slave. And restrict the job to that slave. The slave is offline, leaving the job with no assignable nodes. //We tell our mock SCM to return that it has got changes. But since there are no slaves, we get the desired result. Slave s2 = j.createSlave(); proj.setAssignedLabel(s2.getSelfLabel()); requiresWorkspaceScm.hasChange = true; //Poll (We now should have NO online slaves, this should now return NO_CHANGES. PollingResult pr = proj.poll(j.createTaskListener()); assertFalse(pr.hasChanges()); SCMTrigger t = new SCMTrigger("@daily", true); t.start(proj, true); proj.addTrigger(t); t.new Runner().run(); /** * Assert that the log contains the correct message. */ HtmlPage log = j.createWebClient().getPage(proj, "scmPollLog"); String logastext = log.asText(); assertTrue(logastext.contains("(" + AbstractProject.WorkspaceOfflineReason.all_suitable_nodes_are_offline.name() + ")")); } /** * Job is restricted. Label is on slave that can be started in cloud. Job is submitted to queue, which spawns an on demand slave. * @throws Exception */ @Test public void testRestrictedLabelOnSlaveYesQueue() throws Exception { FreeStyleProject proj = j.createFreeStyleProject("JENKINS-21394-yesqueue"); RequiresWorkspaceSCM requiresWorkspaceScm = new RequiresWorkspaceSCM(true); proj.setScm(requiresWorkspaceScm); j.jenkins.setNumExecutors(0); /* * We have a cloud */ DummyCloudImpl2 c2 = new DummyCloudImpl2(j, 0); c2.label = new LabelAtom("test-cloud-label"); j.jenkins.clouds.add(c2); proj.setAssignedLabel(c2.label); SCMTrigger t = new SCMTrigger("@daily", true); t.start(proj, true); proj.addTrigger(t); t.new Runner().run(); Thread.sleep(1000); //The job should be in queue assertEquals(1, j.jenkins.getQueue().getItems().length); } @Issue("JENKINS-22750") @Test public void testMasterJobPutInQueue() throws Exception { FreeStyleProject proj = j.createFreeStyleProject("JENKINS-21394-yes-master-queue"); RequiresWorkspaceSCM requiresWorkspaceScm = new RequiresWorkspaceSCM(true); proj.setAssignedLabel(null); proj.setScm(requiresWorkspaceScm); j.jenkins.setNumExecutors(1); proj.setScm(requiresWorkspaceScm); //First build is not important j.buildAndAssertSuccess(proj); SCMTrigger t = new SCMTrigger("@daily", true); t.start(proj, true); proj.addTrigger(t); t.new Runner().run(); assertFalse(j.jenkins.getQueue().isEmpty()); } public static class TransientAction extends InvisibleAction{ } @TestExtension public static class TransientActionFactoryImpl extends TransientProjectActionFactory{ @Override public Collection<? extends Action> createFor(AbstractProject target) { List<Action> actions = new ArrayList<Action>(); if(createAction) actions.add(new TransientAction()); return actions; } } @TestExtension public static class RequiresWorkspaceSCM extends NullSCM { public boolean hasChange = false; public RequiresWorkspaceSCM() { } public RequiresWorkspaceSCM(boolean hasChange) { this.hasChange = hasChange; } @Override public boolean pollChanges(AbstractProject<?, ?> project, Launcher launcher, FilePath workspace, TaskListener listener) throws IOException, InterruptedException { return hasChange; } @Override public boolean requiresWorkspaceForPolling(){ return true; } @Override public SCMDescriptor<?> getDescriptor() { return new SCMDescriptor<SCM>(null) {}; } @Override protected PollingResult compareRemoteRevisionWith(AbstractProject project, Launcher launcher, FilePath workspace, TaskListener listener, SCMRevisionState baseline) throws IOException, InterruptedException { if(!hasChange) { return PollingResult.NO_CHANGES; } return PollingResult.SIGNIFICANT; } } @TestExtension public static class AlwaysChangedSCM extends NullSCM { @Override public boolean pollChanges(AbstractProject<?, ?> project, Launcher launcher, FilePath workspace, TaskListener listener) throws IOException, InterruptedException { return true; } @Override public boolean requiresWorkspaceForPolling(){ return false; } @Override protected PollingResult compareRemoteRevisionWith(AbstractProject project, Launcher launcher, FilePath workspace, TaskListener listener, SCMRevisionState baseline) throws IOException, InterruptedException { return PollingResult.SIGNIFICANT; } } @TestExtension public static class WorkspaceBrowserImpl extends WorkspaceBrowser{ @Override public FilePath getWorkspace(Job job) { if(getFilePath) return new FilePath(new File("some_file_path")); return null; } } public static class SCMCheckoutStrategyImpl extends DefaultSCMCheckoutStrategyImpl implements Serializable{ public SCMCheckoutStrategyImpl(){ } } public static class JobPropertyImp extends JobProperty{ @Override public Collection getSubTasks() { ArrayList<SubTask> list = new ArrayList<SubTask>(); list.add(new SubTaskImpl()); return list; } } @TestExtension public static class SubTaskContributorImpl extends SubTaskContributor{ @Override public Collection<? extends SubTask> forProject(AbstractProject<?, ?> p) { ArrayList<SubTask> list = new ArrayList<SubTask>(); if(createSubTask){ list.add(new SubTaskImpl2()); } return list; } } public static class SubTaskImpl2 extends SubTaskImpl{ } public static class SubTaskImpl extends AbstractSubTask{ public String projectName; @Override public Executable createExecutable() throws IOException { return null; } @Override public Task getOwnerTask() { return (Task) Jenkins.getInstance().getItem(projectName); } @Override public String getDisplayName() { return "some task"; } } public class ActionImpl extends InvisibleAction{ } @TestExtension public static class DummyCloudImpl2 extends Cloud { private final transient JenkinsRule caller; /** * Configurable delay between the {@link Cloud#provision(Label,int)} and the actual launch of a slave, * to emulate a real cloud that takes some time for provisioning a new system. * * <p> * Number of milliseconds. */ private final int delay; // stats counter to perform assertions later public int numProvisioned; /** * Only reacts to provisioning for this label. */ public Label label; public DummyCloudImpl2() { super("test"); this.delay = 0; this.caller = null; } public DummyCloudImpl2(JenkinsRule caller, int delay) { super("test"); this.caller = caller; this.delay = delay; } @Override public Collection<NodeProvisioner.PlannedNode> provision(Label label, int excessWorkload) { List<NodeProvisioner.PlannedNode> r = new ArrayList<NodeProvisioner.PlannedNode>(); //Always provision...even if there is no workload. while(excessWorkload >= 0) { System.out.println("Provisioning"); numProvisioned++; Future<Node> f = Computer.threadPoolForRemoting.submit(new ProjectTest.DummyCloudImpl2.Launcher(delay)); r.add(new NodeProvisioner.PlannedNode(name+" #"+numProvisioned,f,1)); excessWorkload-=1; } return r; } @Override public boolean canProvision(Label label) { //This cloud can ALWAYS provision return true; /* return label==this.label; */ } private final class Launcher implements Callable<Node> { private final long time; /** * This is so that we can find out the status of Callable from the debugger. */ private volatile Computer computer; private Launcher(long time) { this.time = time; } @Override public Node call() throws Exception { // simulate the delay in provisioning a new slave, // since it's normally some async operation. Thread.sleep(time); System.out.println("launching slave"); DumbSlave slave = caller.createSlave(label); computer = slave.toComputer(); computer.connect(false).get(); synchronized (ProjectTest.DummyCloudImpl2.this) { System.out.println(computer.getName()+" launch"+(computer.isOnline()?"ed successfully":" failed")); System.out.println(computer.getLog()); } return slave; } } @Override public Descriptor<Cloud> getDescriptor() { throw new UnsupportedOperationException(); } } }