/* * The MIT License * * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi * * 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.FailingHttpStatusCodeException; import com.gargoylesoftware.htmlunit.WebAssert; import com.gargoylesoftware.htmlunit.html.HtmlPage; import hudson.util.TextFile; import java.io.IOException; import java.util.concurrent.CountDownLatch; import org.jvnet.hudson.test.Bug; import org.jvnet.hudson.test.HudsonTestCase; import org.jvnet.hudson.test.recipes.LocalData; /** * @author Kohsuke Kawaguchi */ public class JobTest extends HudsonTestCase { @SuppressWarnings("unchecked") public void testJobPropertySummaryIsShownInMainPage() throws Exception { AbstractProject project = createFreeStyleProject(); project.addProperty(new JobPropertyImpl("NeedleInPage")); HtmlPage page = new WebClient().getPage(project); WebAssert.assertTextPresent(page, "NeedleInPage"); } public void testBuildNumberSynchronization() throws Exception { AbstractProject project = createFreeStyleProject(); CountDownLatch startLatch = new CountDownLatch(1); CountDownLatch stopLatch = new CountDownLatch(2); BuildNumberSyncTester test1 = new BuildNumberSyncTester(project, startLatch, stopLatch, true); BuildNumberSyncTester test2 = new BuildNumberSyncTester(project, startLatch, stopLatch, false); new Thread(test1).start(); new Thread(test2).start(); startLatch.countDown(); stopLatch.await(); assertTrue(test1.message, test2.passed); assertTrue(test2.message, test2.passed); } public static class BuildNumberSyncTester implements Runnable { private final AbstractProject p; private final CountDownLatch start; private final CountDownLatch stop; private final boolean assign; String message; boolean passed; BuildNumberSyncTester(AbstractProject p, CountDownLatch l1, CountDownLatch l2, boolean b) { this.p = p; this.start = l1; this.stop = l2; this.assign = b; this.message = null; this.passed = false; } public void run() { try { start.await(); for (int i = 0; i < 100; i++) { int buildNumber = -1, savedBuildNumber = -1; TextFile f; synchronized (p) { if (assign) { buildNumber = p.assignBuildNumber(); f = p.getNextBuildNumberFile(); if (f == null) { this.message = "Could not get build number file"; this.passed = false; return; } savedBuildNumber = Integer.parseInt(f.readTrim()); if (buildNumber != (savedBuildNumber-1)) { this.message = "Build numbers don't match (" + buildNumber + ", " + (savedBuildNumber-1) + ")"; this.passed = false; return; } } else { buildNumber = p.getNextBuildNumber() + 100; p.updateNextBuildNumber(buildNumber); f = p.getNextBuildNumberFile(); if (f == null) { this.message = "Could not get build number file"; this.passed = false; return; } savedBuildNumber = Integer.parseInt(f.readTrim()); if (buildNumber != savedBuildNumber) { this.message = "Build numbers don't match (" + buildNumber + ", " + savedBuildNumber + ")"; this.passed = false; return; } } } } this.passed = true; } catch (InterruptedException e) {} catch (IOException e) { fail("Failed to assign build number"); } finally { stop.countDown(); } } } @SuppressWarnings("unchecked") public static class JobPropertyImpl extends JobProperty<Job<?,?>> { public static DescriptorImpl DESCRIPTOR = new DescriptorImpl(); private final String testString; public JobPropertyImpl(String testString) { this.testString = testString; } public String getTestString() { return testString; } @Override public JobPropertyDescriptor getDescriptor() { return DESCRIPTOR; } private static final class DescriptorImpl extends JobPropertyDescriptor { public String getDisplayName() { return ""; } } } @LocalData public void testReadPermission() throws Exception { WebClient wc = new WebClient(); try { HtmlPage page = wc.goTo("job/testJob/"); fail("getJob bypassed Item.READ permission: " + page.getTitleText()); } catch (FailingHttpStatusCodeException expected) { } try { HtmlPage page = wc.goTo("jobCaseInsensitive/testJob/"); fail("getJobCaseInsensitive bypassed Item.READ permission: " + page.getTitleText()); } catch (FailingHttpStatusCodeException expected) { } wc.login("joe"); // Has Item.READ permission // Verify we can access both URLs: wc.goTo("job/testJob/"); wc.goTo("jobCaseInsensitive/TESTJOB/"); } @LocalData public void testConfigDotXmlPermission() throws Exception { hudson.setCrumbIssuer(null); WebClient wc = new WebClient(); boolean saveEnabled = Item.EXTENDED_READ.getEnabled(); Item.EXTENDED_READ.setEnabled(true); try { try { wc.goTo("job/testJob/config.xml", "text/plain"); fail("doConfigDotXml bypassed EXTENDED_READ permission"); } catch (FailingHttpStatusCodeException expected) { assertEquals("403 for no permission", 403, expected.getStatusCode()); } wc.login("alice"); // Has CONFIGURE and EXTENDED_READ permission tryConfigDotXml(wc, 500, "Both perms; should get 500"); wc.login("bob"); // Has only CONFIGURE permission (this should imply EXTENDED_READ) tryConfigDotXml(wc, 500, "Config perm should imply EXTENDED_READ"); wc.login("charlie"); // Has only EXTENDED_READ permission tryConfigDotXml(wc, 403, "No permission, should get 403"); } finally { Item.EXTENDED_READ.setEnabled(saveEnabled); } } private static void tryConfigDotXml(WebClient wc, int status, String msg) throws Exception { // Verify we can GET the config.xml: wc.goTo("job/testJob/config.xml", "application/xml"); // This page is a simple form to POST to /job/testJob/config.xml // But it posts invalid data so we expect 500 if we have permission, 403 if not HtmlPage page = wc.goTo("userContent/post.html"); try { page.getForms().get(0).submit(); fail("Expected exception: " + msg); } catch (FailingHttpStatusCodeException expected) { assertEquals(msg, status, expected.getStatusCode()); } wc.goTo("logout"); } @LocalData @Bug(6371) public void testGetArtifactsUpTo() throws Exception { // There was a bug where intermediate directories were counted, // so too few artifacts were returned. Run r = hudson.getItemByFullName("testJob", Job.class).getLastCompletedBuild(); assertEquals(3, r.getArtifacts().size()); assertEquals(3, r.getArtifactsUpTo(3).size()); assertEquals(2, r.getArtifactsUpTo(2).size()); assertEquals(1, r.getArtifactsUpTo(1).size()); } }