File D:\maven-qcr-plugin-testsunitaires\qcr4java\jenkins-master\target\site\qc\test\src\main\java\org\jvnet\hudson\test\JenkinsRule.java.html

1 /*
2 * The MIT License
3 *
4 * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Erik Ramfelt,
5 * Yahoo! Inc., Tom Huybrechts, Olivier Lamy
6 *
7 * Permission is hereby granted, free of charge, to any person obtaining a copy
8 * of this software and associated documentation files (the "Software"), to deal
9 * in the Software without restriction, including without limitation the rights
10 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 * copies of the Software, and to permit persons to whom the Software is
12 * furnished to do so, subject to the following conditions:
13 *
14 * The above copyright notice and this permission notice shall be included in
15 * all copies or substantial portions of the Software.
16 *
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 * THE SOFTWARE.
24 */
25 package org.jvnet.hudson.test;
26
27 import com.gargoylesoftware.htmlunit.AjaxController;
28 import com.gargoylesoftware.htmlunit.BrowserVersion;
29 import com.gargoylesoftware.htmlunit.DefaultCssErrorHandler;
30 import com.gargoylesoftware.htmlunit.FailingHttpStatusCodeException;
31 import com.gargoylesoftware.htmlunit.Page;
32 import com.gargoylesoftware.htmlunit.WebRequestSettings;
33 import com.gargoylesoftware.htmlunit.html.DomNode;
34 import com.gargoylesoftware.htmlunit.html.HtmlButton;
35 import com.gargoylesoftware.htmlunit.html.HtmlElement;
36 import com.gargoylesoftware.htmlunit.html.HtmlForm;
37 import com.gargoylesoftware.htmlunit.html.HtmlImage;
38 import com.gargoylesoftware.htmlunit.html.HtmlInput;
39 import com.gargoylesoftware.htmlunit.html.HtmlPage;
40 import com.gargoylesoftware.htmlunit.javascript.HtmlUnitContextFactory;
41 import com.gargoylesoftware.htmlunit.javascript.host.xml.XMLHttpRequest;
42 import com.gargoylesoftware.htmlunit.xml.XmlPage;
43 import hudson.ClassicPluginStrategy;
44 import hudson.CloseProofOutputStream;
45 import hudson.DNSMultiCast;
46 import hudson.DescriptorExtensionList;
47 import hudson.EnvVars;
48 import hudson.Extension;
49 import hudson.ExtensionList;
50 import hudson.FilePath;
51 import hudson.Functions;
52 import hudson.Launcher;
53 import hudson.Main;
54 import hudson.PluginManager;
55 import hudson.Util;
56 import hudson.WebAppMain;
57 import hudson.matrix.MatrixBuild;
58 import hudson.matrix.MatrixProject;
59 import hudson.matrix.MatrixRun;
60 import hudson.maven.MavenBuild;
61 import hudson.maven.MavenEmbedder;
62 import hudson.maven.MavenEmbedderException;
63 import hudson.maven.MavenModule;
64 import hudson.maven.MavenModuleSet;
65 import hudson.maven.MavenModuleSetBuild;
66 import hudson.maven.MavenUtil;
67 import hudson.model.AbstractBuild;
68 import hudson.model.AbstractProject;
69 import hudson.model.BuildListener;
70 import hudson.model.Computer;
71 import hudson.model.Describable;
72 import hudson.model.Descriptor;
73 import hudson.model.DownloadService;
74 import hudson.model.Executor;
75 import hudson.model.FreeStyleBuild;
76 import hudson.model.FreeStyleProject;
77 import hudson.model.Hudson;
78 import hudson.model.Item;
79 import hudson.model.JDK;
80 import hudson.model.Job;
81 import hudson.model.Label;
82 import hudson.model.Node;
83 import hudson.model.Queue;
84 import hudson.model.Result;
85 import hudson.model.RootAction;
86 import hudson.model.Run;
87 import hudson.model.TaskListener;
88 import hudson.model.UpdateSite;
89 import hudson.model.User;
90 import hudson.model.View;
91 import hudson.remoting.Channel;
92 import hudson.remoting.VirtualChannel;
93 import hudson.remoting.Which;
94 import hudson.security.ACL;
95 import hudson.security.AbstractPasswordBasedSecurityRealm;
96 import hudson.security.GroupDetails;
97 import hudson.security.SecurityRealm;
98 import hudson.security.csrf.CrumbIssuer;
99 import hudson.slaves.CommandLauncher;
100 import hudson.slaves.ComputerConnector;
101 import hudson.slaves.ComputerListener;
102 import hudson.slaves.DumbSlave;
103 import hudson.slaves.RetentionStrategy;
104 import hudson.tasks.Ant;
105 import hudson.tasks.BuildWrapper;
106 import hudson.tasks.BuildWrapperDescriptor;
107 import hudson.tasks.Builder;
108 import hudson.tasks.Mailer;
109 import hudson.tasks.Maven;
110 import hudson.tasks.Publisher;
111 import hudson.tools.ToolProperty;
112 import hudson.util.PersistedList;
113 import hudson.util.ReflectionUtils;
114 import hudson.util.StreamTaskListener;
115 import hudson.util.jna.GNUCLibrary;
116 import jenkins.model.Jenkins;
117 import jenkins.model.JenkinsAdaptor;
118 import net.sf.json.JSONObject;
119 import net.sourceforge.htmlunit.corejs.javascript.Context;
120 import net.sourceforge.htmlunit.corejs.javascript.ContextFactory;
121 import org.acegisecurity.AuthenticationException;
122 import org.acegisecurity.BadCredentialsException;
123 import org.acegisecurity.GrantedAuthority;
124 import org.acegisecurity.userdetails.UserDetails;
125 import org.acegisecurity.userdetails.UsernameNotFoundException;
126 import org.apache.commons.beanutils.PropertyUtils;
127 import org.apache.commons.httpclient.NameValuePair;
128 import org.apache.commons.io.FileUtils;
129 import org.apache.maven.artifact.Artifact;
130 import org.apache.maven.artifact.resolver.AbstractArtifactResolutionException;
131 import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
132 import org.hamcrest.Matchers;
133 import org.junit.rules.MethodRule;
134 import org.junit.rules.TestRule;
135 import org.junit.runner.Description;
136 import org.junit.runners.model.FrameworkMethod;
137 import org.junit.runners.model.Statement;
138 import org.jvnet.hudson.test.rhino.JavaScriptDebugger;
139 import org.kohsuke.stapler.ClassDescriptor;
140 import org.kohsuke.stapler.DataBoundConstructor;
141 import org.kohsuke.stapler.Dispatcher;
142 import org.kohsuke.stapler.MetaClass;
143 import org.kohsuke.stapler.MetaClassLoader;
144 import org.kohsuke.stapler.Stapler;
145 import org.kohsuke.stapler.StaplerRequest;
146 import org.kohsuke.stapler.StaplerResponse;
147 import org.mortbay.jetty.MimeTypes;
148 import org.mortbay.jetty.Server;
149 import org.mortbay.jetty.bio.SocketConnector;
150 import org.mortbay.jetty.security.HashUserRealm;
151 import org.mortbay.jetty.security.UserRealm;
152 import org.mortbay.jetty.webapp.Configuration;
153 import org.mortbay.jetty.webapp.WebAppContext;
154 import org.mortbay.jetty.webapp.WebXmlConfiguration;
155 import org.mozilla.javascript.tools.debugger.Dim;
156 import org.mozilla.javascript.tools.shell.Global;
157 import org.springframework.dao.DataAccessException;
158 import org.w3c.css.sac.CSSException;
159 import org.w3c.css.sac.CSSParseException;
160 import org.w3c.css.sac.ErrorHandler;
161 import org.xml.sax.SAXException;
162
163 import javax.servlet.ServletContext;
164 import javax.servlet.ServletContextEvent;
165 import java.beans.PropertyDescriptor;
166 import java.io.BufferedReader;
167 import java.io.File;
168 import java.io.IOException;
169 import java.io.InputStream;
170 import java.io.InputStreamReader;
171 import java.lang.annotation.Annotation;
172 import java.lang.management.ThreadInfo;
173 import java.lang.reflect.Array;
174 import java.lang.reflect.Constructor;
175 import java.lang.reflect.Field;
176 import java.net.MalformedURLException;
177 import java.net.URISyntaxException;
178 import java.net.URL;
179 import java.net.URLClassLoader;
180 import java.nio.charset.Charset;
181 import java.util.ArrayList;
182 import java.util.Arrays;
183 import java.util.Collections;
184 import java.util.Enumeration;
185 import java.util.HashMap;
186 import java.util.Iterator;
187 import java.util.List;
188 import java.util.Locale;
189 import java.util.Map;
190 import java.util.Set;
191 import java.util.Timer;
192 import java.util.TimerTask;
193 import java.util.TreeSet;
194 import java.util.UUID;
195 import java.util.concurrent.Callable;
196 import java.util.concurrent.CountDownLatch;
197 import java.util.concurrent.Future;
198 import java.util.concurrent.LinkedBlockingQueue;
199 import java.util.concurrent.ThreadFactory;
200 import java.util.concurrent.ThreadPoolExecutor;
201 import java.util.concurrent.TimeUnit;
202 import java.util.jar.Manifest;
203 import java.util.logging.Filter;
204 import java.util.logging.Level;
205 import java.util.logging.LogRecord;
206 import java.util.logging.Logger;
207 import org.acegisecurity.GrantedAuthorityImpl;
208
209 import static org.hamcrest.Matchers.hasXPath;
210 import static org.hamcrest.Matchers.is;
211 import static org.hamcrest.Matchers.not;
212 import static org.hamcrest.Matchers.notNullValue;
213 import static org.junit.Assert.*;
214 import static org.junit.matchers.JUnitMatchers.containsString;
215
216 /**
217 * JUnit 4.10+ style rule to allow test cases to fire up a Jenkins instance
218 *
219 * @see <a href="http://wiki.jenkins-ci.org/display/JENKINS/Unit+Test+JUnit4">Wiki article about unit testing in Jenkins</a>
220 * @author Stephen Connolly
221 * @since 1.436
222 */
223 public class JenkinsRule implements TestRule, MethodRule, RootAction {
224
225 private TestEnvironment env;
226
227 private Description testDescription;
228
229 private static JenkinsRule CURRENT = null;
230
231 /**
232 * Points to the same object as {@link #jenkins} does.
233 */
234 public Hudson hudson;
235
236 public Jenkins jenkins;
237
238 protected HudsonHomeLoader homeLoader = HudsonHomeLoader.NEW;
239 /**
240 * TCP/IP port that the server is listening on.
241 */
242 protected int localPort;
243 protected Server server;
244
245 /**
246 * Where in the {@link Server} is Hudson deployed?
247 * <p>
248 * Just like {@link javax.servlet.ServletContext#getContextPath()}, starts with '/' but doesn't end with '/'.
249 */
250 protected String contextPath = "";
251
252 /**
253 * {@link Runnable}s to be invoked at {@link #after()} .
254 */
255 protected List<LenientRunnable> tearDowns = new ArrayList<LenientRunnable>();
256
257 protected List<JenkinsRecipe.Runner> recipes = new ArrayList<JenkinsRecipe.Runner>();
258
259 /**
260 * Remember {@link WebClient}s that are created, to release them properly.
261 */
262 private List<WebClient> clients = new ArrayList<WebClient>();
263
264 /**
265 * Remember channels that are created, to release them at the end.
266 */
267 private List<Channel> channels = new ArrayList<Channel>();
268
269 /**
270 * JavaScript "debugger" that provides you information about the JavaScript call stack
271 * and the current values of the local variables in those stack frame.
272 *
273 * <p>
274 * Unlike Java debugger, which you as a human interfaces directly and interactively,
275 * this JavaScript debugger is to be interfaced by your program (or through the
276 * expression evaluation capability of your Java debugger.)
277 */
278 protected JavaScriptDebugger jsDebugger = new JavaScriptDebugger();
279
280 /**
281 * If this test case has additional {@link org.jvnet.hudson.test.recipes.WithPlugin} annotations, set to true.
282 * This will cause a fresh {@link hudson.PluginManager} to be created for this test.
283 * Leaving this to false enables the test harness to use a pre-loaded plugin manager,
284 * which runs faster.
285 *
286 * @deprecated
287 * Use {@link #pluginManager}
288 */
289 public boolean useLocalPluginManager;
290
291 /**
292 * Number of seconds until the test times out.
293 */
294 public int timeout = 90;
295
296 private volatile Timer timeoutTimer;
297
298 /**
299 * Set the plugin manager to be passed to {@link Jenkins} constructor.
300 *
301 * For historical reasons, {@link #useLocalPluginManager}==true will take the precedence.
302 */
303 private PluginManager pluginManager = TestPluginManager.INSTANCE;
304
305 public JenkinsComputerConnectorTester computerConnectorTester = new JenkinsComputerConnectorTester(this);
306
307 public Jenkins getInstance() {
308 return jenkins;
309 }
310
311 /**
312 * Override to set up your specific external resource.
313 * @throws Throwable if setup fails (which will disable {@code after}
314 */
315 protected void before() throws Throwable {
316 env = new TestEnvironment(testDescription);
317 env.pin();
318 recipe();
319 AbstractProject.WORKSPACE.toString();
320 User.clear();
321
322
323 try {
324 jenkins = hudson = newHudson();
325 } catch (Exception e) {
326 // if Hudson instance fails to initialize, it leaves the instance field non-empty and break all the rest of the tests, so clean that up.
327 Field f = Jenkins.class.getDeclaredField("theInstance");
328 f.setAccessible(true);
329 f.set(null,null);
330 throw e;
331 }
332 jenkins.setNoUsageStatistics(true); // collecting usage stats from tests are pointless.
333
334 jenkins.setCrumbIssuer(new TestCrumbIssuer());
335
336 jenkins.servletContext.setAttribute("app",jenkins);
337 jenkins.servletContext.setAttribute("version","?");
338 WebAppMain.installExpressionFactory(new ServletContextEvent(jenkins.servletContext));
339
340 // set a default JDK to be the one that the harness is using.
341 jenkins.getJDKs().add(new JDK("default",System.getProperty("java.home")));
342
343 configureUpdateCenter();
344
345 // expose the test instance as a part of URL tree.
346 // this allows tests to use a part of the URL space for itself.
347 jenkins.getActions().add(this);
348
349 // cause all the descriptors to reload.
350 // ideally we'd like to reset them to properly emulate the behavior, but that's not possible.
351 Mailer.DescriptorImpl desc = Mailer.descriptor();
352 // prevent NPE with eclipse
353 if (desc != null) Mailer.descriptor().setHudsonUrl(null);
354 for( Descriptor d : jenkins.getExtensionList(Descriptor.class) )
355 d.load();
356 }
357
358 /**
359 * Configures the update center setting for the test.
360 * By default, we load updates from local proxy to avoid network traffic as much as possible.
361 */
362 protected void configureUpdateCenter() throws Exception {
363 final String updateCenterUrl = "http://localhost:"+ JavaNetReverseProxy.getInstance().localPort+"/update-center.json";
364
365 // don't waste bandwidth talking to the update center
366 DownloadService.neverUpdate = true;
367 UpdateSite.neverUpdate = true;
368
369 PersistedList<UpdateSite> sites = jenkins.getUpdateCenter().getSites();
370 sites.clear();
371 sites.add(new UpdateSite("default", updateCenterUrl));
372 }
373
374 protected void setUpTimeout() {
375 if (timeout<=0) return; // no timeout
376
377 final Thread testThread = Thread.currentThread();
378 timeoutTimer = new Timer();
379 timeoutTimer.schedule(new TimerTask() {
380 @Override
381 public void run() {
382 if (timeoutTimer!=null)
383 testThread.interrupt();
384 }
385 }, TimeUnit.SECONDS.toMillis(timeout));
386 }
387
388 /**
389 * Override to tear down your specific external resource.
390 */
391 protected void after() {
392 try {
393 if (timeoutTimer!=null) {
394 timeoutTimer.cancel();
395 timeoutTimer = null;
396 }
397
398 // cancel pending asynchronous operations, although this doesn't really seem to be working
399 for (WebClient client : clients) {
400 // unload the page to cancel asynchronous operations
401 try {
402 client.getPage("about:blank");
403 } catch (IOException e) {
404 // ignore
405 }
406 client.closeAllWindows();
407 }
408 clients.clear();
409
410 for (Channel c : channels)
411 try {
412 c.close();
413 } catch (IOException e) {
414 // ignore
415 }
416 for (Channel c : channels)
417 try {
418 c.join();
419 } catch (InterruptedException e) {
420 // ignore
421 }
422 channels.clear();
423
424 } finally {
425 try {
426 server.stop();
427 } catch (Exception e) {
428 // ignore
429 }
430 for (LenientRunnable r : tearDowns)
431 try {
432 r.run();
433 } catch (Exception e) {
434 // ignore
435 }
436
437 jenkins.cleanUp();
438 ExtensionList.clearLegacyInstances();
439 DescriptorExtensionList.clearLegacyInstances();
440
441 try {
442 env.dispose();
443 } catch (Exception x) {
444 x.printStackTrace();
445 }
446
447 // Hudson creates ClassLoaders for plugins that hold on to file descriptors of its jar files,
448 // but because there's no explicit dispose method on ClassLoader, they won't get GC-ed until
449 // at some later point, leading to possible file descriptor overflow. So encourage GC now.
450 // see http://bugs.sun.com/view_bug.do?bug_id=4950148
451 System.gc();
452 }
453 }
454
455 /**
456 * Backward compatibility with JUnit 4.8.
457 */
458 public Statement apply(Statement base, FrameworkMethod method, Object target) {
459 return apply(base,Description.createTestDescription(method.getMethod().getDeclaringClass(), method.getName(), method.getAnnotations()));
460 }
461
462 public Statement apply(final Statement base, final Description description) {
463 if (description.getAnnotation(WithoutJenkins.class) != null) {
464 // request has been made to not create the instance for this test method
465 return base;
466 }
467 return new Statement() {
468 @Override
469 public void evaluate() throws Throwable {
470 testDescription = description;
471 Thread t = Thread.currentThread();
472 String o = t.getName();
473 t.setName("Executing "+ testDescription.getDisplayName());
474 CURRENT = JenkinsRule.this;
475 before();
476 try {
477 System.out.println("=== Starting " + testDescription.getDisplayName());
478 // so that test code has all the access to the system
479 ACL.impersonate(ACL.SYSTEM);
480 try {
481 base.evaluate();
482 } catch (Throwable th) {
483 // allow the late attachment of a debugger in case of a failure. Useful
484 // for diagnosing a rare failure
485 try {
486 throw new BreakException();
487 } catch (BreakException e) {}
488
489 // dump threads
490 ThreadInfo[] threadInfos = Functions.getThreadInfos();
491 Functions.ThreadGroupMap m = Functions.sortThreadsAndGetGroupMap(threadInfos);
492 for (ThreadInfo ti : threadInfos) {
493 System.err.println(Functions.dumpThreadInfo(ti, m));
494 }
495 throw th;
496 }
497 } finally {
498 after();
499 testDescription = null;
500 t.setName(o);
501 CURRENT = null;
502 }
503 }
504 };
505 }
506
507 @SuppressWarnings("serial")
508 public static class BreakException extends Exception {}
509
510 public String getIconFileName() {
511 return null;
512 }
513
514 public String getDisplayName() {
515 return null;
516 }
517
518 public String getUrlName() {
519 return "self";
520 }
521
522 /**
523 * Creates a new instance of {@link jenkins.model.Jenkins}. If the derived class wants to create it in a different way,
524 * you can override it.
525 */
526 protected Hudson newHudson() throws Exception {
527 ServletContext webServer = createWebServer();
528 File home = homeLoader.allocate();
529 for (JenkinsRecipe.Runner r : recipes)
530 r.decorateHome(this,home);
531 return new Hudson(home, webServer, getPluginManager());
532 }
533
534 public PluginManager getPluginManager() {
535 if (jenkins == null) {
536 return useLocalPluginManager ? null : pluginManager;
537 } else {
538 return jenkins.getPluginManager();
539 }
540 }
541
542 /**
543 * Sets the {@link PluginManager} to be used when creating a new {@link Jenkins} instance.
544 *
545 * @param pluginManager
546 * null to let Jenkins create a new instance of default plugin manager, like it normally does when running as a webapp outside the test.
547 */
548 public void setPluginManager(PluginManager pluginManager) {
549 this.useLocalPluginManager = false;
550 this.pluginManager = pluginManager;
551 if (jenkins!=null)
552 throw new IllegalStateException("Too late to override the plugin manager");
553 }
554
555 public JenkinsRule with(PluginManager pluginManager) {
556 setPluginManager(pluginManager);
557 return this;
558 }
559
560 public File getWebAppRoot() throws Exception {
561 return WarExploder.getExplodedDir();
562 }
563
564 /**
565 * Prepares a webapp hosting environment to get {@link javax.servlet.ServletContext} implementation
566 * that we need for testing.
567 */
568 protected ServletContext createWebServer() throws Exception {
569 server = new Server();
570
571 WebAppContext context = new WebAppContext(WarExploder.getExplodedDir().getPath(), contextPath);
572 context.setClassLoader(getClass().getClassLoader());
573 context.setConfigurations(new Configuration[]{new WebXmlConfiguration(), new NoListenerConfiguration()});
574 server.setHandler(context);
575 context.setMimeTypes(MIME_TYPES);
576
577 SocketConnector connector = new SocketConnector();
578 connector.setHeaderBufferSize(12*1024); // use a bigger buffer as Stapler traces can get pretty large on deeply nested URL
579
580 server.setThreadPool(new ThreadPoolImpl(new ThreadPoolExecutor(10, 10, 10L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(),new ThreadFactory() {
581 public Thread newThread(Runnable r) {
582 Thread t = new Thread(r);
583 t.setName("Jetty Thread Pool");
584 return t;
585 }
586 })));
587 server.addConnector(connector);
588 server.addUserRealm(configureUserRealm());
589 server.start();
590
591 localPort = connector.getLocalPort();
592
593 return context.getServletContext();
594 }
595
596 /**
597 * Configures a security realm for a test.
598 */
599 public UserRealm configureUserRealm() {
600 HashUserRealm realm = new HashUserRealm();
601 realm.setName("default"); // this is the magic realm name to make it effective on everywhere
602 realm.put("alice","alice");
603 realm.put("bob","bob");
604 realm.put("charlie","charlie");
605
606 realm.addUserToRole("alice","female");
607 realm.addUserToRole("bob","male");
608 realm.addUserToRole("charlie","male");
609
610 return realm;
611 }
612
613 @TestExtension
614 public static class ComputerListenerImpl extends ComputerListener {
615 @Override
616 public void onOnline(Computer c, TaskListener listener) throws IOException, InterruptedException {
617 VirtualChannel ch = c.getChannel();
618 if (ch instanceof Channel)
619 CURRENT.channels.add((Channel)ch);
620 }
621 }
622
623
624 /**
625 * Returns the older default Maven, while still allowing specification of other bundled Mavens.
626 */
627 public Maven.MavenInstallation configureDefaultMaven() throws Exception {
628 return configureDefaultMaven("apache-maven-2.2.1", Maven.MavenInstallation.MAVEN_20);
629 }
630
631 public Maven.MavenInstallation configureMaven3() throws Exception {
632 Maven.MavenInstallation mvn = configureDefaultMaven("apache-maven-3.0.1", Maven.MavenInstallation.MAVEN_30);
633
634 Maven.MavenInstallation m3 = new Maven.MavenInstallation("apache-maven-3.0.1",mvn.getHome(), NO_PROPERTIES);
635 jenkins.getDescriptorByType(Maven.DescriptorImpl.class).setInstallations(m3);
636 return m3;
637 }
638
639 /**
640 * Locates Maven2 and configure that as the only Maven in the system.
641 */
642 public Maven.MavenInstallation configureDefaultMaven(String mavenVersion, int mavenReqVersion) throws Exception {
643 // first if we are running inside Maven, pick that Maven, if it meets the criteria we require..
644 // does it exists in the buildDirectory see maven-junit-plugin systemProperties
645 // buildDirectory -> ${project.build.directory} (so no reason to be null ;-) )
646 String buildDirectory = System.getProperty( "buildDirectory", "./target/classes/" );
647 File mavenAlreadyInstalled = new File(buildDirectory, mavenVersion);
648 if (mavenAlreadyInstalled.exists()) {
649 Maven.MavenInstallation
650 mavenInstallation = new Maven.MavenInstallation("default",mavenAlreadyInstalled.getAbsolutePath(), NO_PROPERTIES);
651 jenkins.getDescriptorByType(Maven.DescriptorImpl.class).setInstallations(mavenInstallation);
652 return mavenInstallation;
653 }
654
655 // Does maven.home point to a Maven installation which satisfies mavenReqVersion?
656 String home = System.getProperty("maven.home");
657 if(home!=null) {
658 Maven.MavenInstallation mavenInstallation = new Maven.MavenInstallation("default",home, NO_PROPERTIES);
659 if (mavenInstallation.meetsMavenReqVersion(createLocalLauncher(), mavenReqVersion)) {
660 jenkins.getDescriptorByType(Maven.DescriptorImpl.class).setInstallations(mavenInstallation);
661 return mavenInstallation;
662 }
663 }
664
665 // otherwise extract the copy we have.
666 // this happens when a test is invoked from an IDE, for example.
667 LOGGER.warning("Extracting a copy of Maven bundled in the test harness. " +
668 "To avoid a performance hit, set the system property 'maven.home' to point to a Maven2 installation.");
669 FilePath mvn = jenkins.getRootPath().createTempFile("maven", "zip");
670 mvn.copyFrom(HudsonTestCase.class.getClassLoader().getResource(mavenVersion + "-bin.zip"));
671 File mvnHome = new File(buildDirectory);//createTmpDir();
672 mvn.unzip(new FilePath(mvnHome));
673 // TODO: switch to tar that preserves file permissions more easily
674 if(!Functions.isWindows())
675 GNUCLibrary.LIBC.chmod(new File(mvnHome,mavenVersion+"/bin/mvn").getPath(),0755);
676
677 Maven.MavenInstallation mavenInstallation = new Maven.MavenInstallation("default",
678 new File(mvnHome,mavenVersion).getAbsolutePath(), NO_PROPERTIES);
679 jenkins.getDescriptorByType(Maven.DescriptorImpl.class).setInstallations(mavenInstallation);
680 return mavenInstallation;
681 }
682
683 /**
684 * Extracts Ant and configures it.
685 */
686 public Ant.AntInstallation configureDefaultAnt() throws Exception {
687 Ant.AntInstallation antInstallation;
688 if (System.getenv("ANT_HOME") != null) {
689 antInstallation = new Ant.AntInstallation("default", System.getenv("ANT_HOME"), NO_PROPERTIES);
690 } else {
691 LOGGER.warning("Extracting a copy of Ant bundled in the test harness. " +
692 "To avoid a performance hit, set the environment variable ANT_HOME to point to an Ant installation.");
693 FilePath ant = jenkins.getRootPath().createTempFile("ant", "zip");
694 ant.copyFrom(HudsonTestCase.class.getClassLoader().getResource("apache-ant-1.8.1-bin.zip"));
695 File antHome = createTmpDir();
696 ant.unzip(new FilePath(antHome));
697 // TODO: switch to tar that preserves file permissions more easily
698 if(!Functions.isWindows())
699 GNUCLibrary.LIBC.chmod(new File(antHome,"apache-ant-1.8.1/bin/ant").getPath(),0755);
700
701 antInstallation = new Ant.AntInstallation("default", new File(antHome,"apache-ant-1.8.1").getAbsolutePath(),NO_PROPERTIES);
702 }
703 jenkins.getDescriptorByType(Ant.DescriptorImpl.class).setInstallations(antInstallation);
704 return antInstallation;
705 }
706
707 //
708 // Convenience methods
709 //
710
711 public FreeStyleProject createFreeStyleProject() throws IOException {
712 return createFreeStyleProject(createUniqueProjectName());
713 }
714
715 public FreeStyleProject createFreeStyleProject(String name) throws IOException {
716 return jenkins.createProject(FreeStyleProject.class, name);
717 }
718
719 public MatrixProject createMatrixProject() throws IOException {
720 return createMatrixProject(createUniqueProjectName());
721 }
722
723 public MatrixProject createMatrixProject(String name) throws IOException {
724 return jenkins.createProject(MatrixProject.class, name);
725 }
726
727 /**
728 * Creates a empty Maven project with an unique name.
729 *
730 * @see #configureDefaultMaven()
731 */
732 public MavenModuleSet createMavenProject() throws IOException {
733 return createMavenProject(createUniqueProjectName());
734 }
735
736 /**
737 * Creates a empty Maven project with the given name.
738 *
739 * @see #configureDefaultMaven()
740 */
741 public MavenModuleSet createMavenProject(String name) throws IOException {
742 return jenkins.createProject(MavenModuleSet.class,name);
743 }
744
745 /**
746 * Creates a simple folder that other jobs can be placed in.
747 * @since 1.494
748 */
749 public MockFolder createFolder(String name) throws IOException {
750 return jenkins.createProject(MockFolder.class, name);
751 }
752
753 protected String createUniqueProjectName() {
754 return "test"+jenkins.getItems().size();
755 }
756
757 /**
758 * Creates {@link hudson.Launcher.LocalLauncher}. Useful for launching processes.
759 */
760 public Launcher.LocalLauncher createLocalLauncher() {
761 return new Launcher.LocalLauncher(StreamTaskListener.fromStdout());
762 }
763
764 /**
765 * Allocates a new temporary directory for the duration of this test.
766 */
767 public File createTmpDir() throws IOException {
768 return env.temporaryDirectoryAllocator.allocate();
769 }
770
771 public DumbSlave createSlave() throws Exception {
772 return createSlave("",null);
773 }
774
775 /**
776 * Creates and launches a new slave on the local host.
777 */
778 public DumbSlave createSlave(Label l) throws Exception {
779 return createSlave(l, null);
780 }
781
782 /**
783 * Creates a test {@link hudson.security.SecurityRealm} that recognizes username==password as valid.
784 */
785 public DummySecurityRealm createDummySecurityRealm() {
786 return new DummySecurityRealm();
787 }
788
789 /** @see #createDummySecurityRealm */
790 public static class DummySecurityRealm extends AbstractPasswordBasedSecurityRealm {
791
792 private final Map<String,Set<String>> groupsByUser = new HashMap<String,Set<String>>();
793
794 DummySecurityRealm() {}
795
796 @Override
797 protected UserDetails authenticate(String username, String password) throws AuthenticationException {
798 if (username.equals(password))
799 return loadUserByUsername(username);
800 throw new BadCredentialsException(username);
801 }
802
803 @Override
804 public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException,
805 DataAccessException {
806 List<GrantedAuthority> auths = new ArrayList<GrantedAuthority>();
807 auths.add(AUTHENTICATED_AUTHORITY);
808 Set<String> groups = groupsByUser.get(username);
809 if (groups != null) {
810 for (String g : groups) {
811 auths.add(new GrantedAuthorityImpl(g));
812 }
813 }
814 return new org.acegisecurity.userdetails.User(username,"",true,true,true,true, auths.toArray(new GrantedAuthority[auths.size()]));
815 }
816
817 @Override
818 public GroupDetails loadGroupByGroupname(final String groupname) throws UsernameNotFoundException, DataAccessException {
819 for (Set<String> groups : groupsByUser.values()) {
820 if (groups.contains(groupname)) {
821 return new GroupDetails() {
822 @Override
823 public String getName() {
824 return groupname;
825 }
826 };
827 }
828 }
829 throw new UsernameNotFoundException(groupname);
830 }
831
832 /** Associate some groups with a username. */
833 public void addGroups(String username, String... groups) {
834 Set<String> gs = groupsByUser.get(username);
835 if (gs == null) {
836 groupsByUser.put(username, gs = new TreeSet<String>());
837 }
838 gs.addAll(Arrays.asList(groups));
839 }
840
841 }
842
843 /**
844 * Returns the URL of the webapp top page.
845 * URL ends with '/'.
846 */
847 public URL getURL() throws IOException {
848 return new URL("http://localhost:"+localPort+contextPath+"/");
849 }
850
851 public DumbSlave createSlave(EnvVars env) throws Exception {
852 return createSlave("",env);
853 }
854
855 public DumbSlave createSlave(Label l, EnvVars env) throws Exception {
856 return createSlave(l==null ? null : l.getExpression(), env);
857 }
858
859 /**
860 * Creates a slave with certain additional environment variables
861 */
862 public DumbSlave createSlave(String labels, EnvVars env) throws Exception {
863 synchronized (jenkins) {
864 int sz = jenkins.getNodes().size();
865 return createSlave("slave" + sz,labels,env);
866 }
867 }
868
869 public DumbSlave createSlave(String nodeName, String labels, EnvVars env) throws Exception {
870 synchronized (jenkins) {
871 DumbSlave slave = new DumbSlave(nodeName, "dummy",
872 createTmpDir().getPath(), "1", Node.Mode.NORMAL, labels==null?"":labels, createComputerLauncher(env), RetentionStrategy.NOOP, Collections.EMPTY_LIST);
873 jenkins.addNode(slave);
874 return slave;
875 }
876 }
877
878 public PretendSlave createPretendSlave(FakeLauncher faker) throws Exception {
879 synchronized (jenkins) {
880 int sz = jenkins.getNodes().size();
881 PretendSlave slave = new PretendSlave("slave" + sz, createTmpDir().getPath(), "", createComputerLauncher(null), faker);
882 jenkins.addNode(slave);
883 return slave;
884 }
885 }
886
887 /**
888 * Creates a {@link hudson.slaves.CommandLauncher} for launching a slave locally.
889 *
890 * @param env
891 * Environment variables to add to the slave process. Can be null.
892 */
893 public CommandLauncher createComputerLauncher(EnvVars env) throws URISyntaxException, MalformedURLException {
894 int sz = jenkins.getNodes().size();
895 return new CommandLauncher(
896 String.format("\"%s/bin/java\" %s -jar \"%s\"",
897 System.getProperty("java.home"),
898 SLAVE_DEBUG_PORT>0 ? " -Xdebug -Xrunjdwp:transport=dt_socket,server=y,address="+(SLAVE_DEBUG_PORT+sz): "",
899 new File(jenkins.getJnlpJars("slave.jar").getURL().toURI()).getAbsolutePath()),
900 env);
901 }
902
903 /**
904 * Create a new slave on the local host and wait for it to come onilne
905 * before returning.
906 */
907 public DumbSlave createOnlineSlave() throws Exception {
908 return createOnlineSlave(null);
909 }
910
911 /**
912 * Create a new slave on the local host and wait for it to come onilne
913 * before returning.
914 */
915 public DumbSlave createOnlineSlave(Label l) throws Exception {
916 return createOnlineSlave(l, null);
917 }
918
919 /**
920 * Create a new slave on the local host and wait for it to come online
921 * before returning
922 */
923 @SuppressWarnings({"deprecation"})
924 public DumbSlave createOnlineSlave(Label l, EnvVars env) throws Exception {
925 final CountDownLatch latch = new CountDownLatch(1);
926 ComputerListener waiter = new ComputerListener() {
927 @Override
928 public void onOnline(Computer C, TaskListener t) {
929 latch.countDown();
930 unregister();
931 }
932 };
933 waiter.register();
934
935 DumbSlave s = createSlave(l, env);
936 latch.await();
937
938 return s;
939 }
940
941 /**
942 * Blocks until the ENTER key is hit.
943 * This is useful during debugging a test so that one can inspect the state of Hudson through the web browser.
944 */
945 public void interactiveBreak() throws Exception {
946 System.out.println("Jenkins is running at http://localhost:"+localPort+"/");
947 new BufferedReader(new InputStreamReader(System.in)).readLine();
948 }
949
950 /**
951 * Returns the last item in the list.
952 */
953 public <T> T last(List<T> items) {
954 return items.get(items.size()-1);
955 }
956
957 /**
958 * Pauses the execution until ENTER is hit in the console.
959 * <p>
960 * This is often very useful so that you can interact with Hudson
961 * from an browser, while developing a test case.
962 */
963 public void pause() throws IOException {
964 new BufferedReader(new InputStreamReader(System.in)).readLine();
965 }
966
967 /**
968 * Performs a search from the search box.
969 */
970 public Page search(String q) throws Exception {
971 return new WebClient().search(q);
972 }
973
974 /**
975 * Hits the Hudson system configuration and submits without any modification.
976 */
977 public void configRoundtrip() throws Exception {
978 submit(createWebClient().goTo("configure").getFormByName("config"));
979 }
980
981 /**
982 * Loads a configuration page and submits it without any modifications, to
983 * perform a round-trip configuration test.
984 * <p>
985 * See http://wiki.jenkins-ci.org/display/JENKINS/Unit+Test#UnitTest-Configurationroundtriptesting
986 */
987 public <P extends Job> P configRoundtrip(P job) throws Exception {
988 submit(createWebClient().getPage(job,"configure").getFormByName("config"));
989 return job;
990 }
991
992 public <P extends Item> P configRoundtrip(P job) throws Exception {
993 submit(createWebClient().getPage(job, "configure").getFormByName("config"));
994 return job;
995 }
996
997 /**
998 * Performs a configuration round-trip testing for a builder.
999 */
1000 public <B extends Builder> B configRoundtrip(B before) throws Exception {
1001 FreeStyleProject p = createFreeStyleProject();
1002 p.getBuildersList().add(before);
1003 configRoundtrip((Item)p);
1004 return (B)p.getBuildersList().get(before.getClass());
1005 }
1006
1007 /**
1008 * Performs a configuration round-trip testing for a publisher.
1009 */
1010 public <P extends Publisher> P configRoundtrip(P before) throws Exception {
1011 FreeStyleProject p = createFreeStyleProject();
1012 p.getPublishersList().add(before);
1013 configRoundtrip((Item) p);
1014 return (P)p.getPublishersList().get(before.getClass());
1015 }
1016
1017 public <C extends ComputerConnector> C configRoundtrip(C before) throws Exception {
1018 computerConnectorTester.connector = before;
1019 submit(createWebClient().goTo("self/computerConnectorTester/configure").getFormByName("config"));
1020 return (C)computerConnectorTester.connector;
1021 }
1022
1023 public User configRoundtrip(User u) throws Exception {
1024 submit(createWebClient().goTo(u.getUrl()+"/configure").getFormByName("config"));
1025 return u;
1026 }
1027
1028 public <N extends Node> N configRoundtrip(N node) throws Exception {
1029 submit(createWebClient().goTo("/computer/" + node.getNodeName() + "/configure").getFormByName("config"));
1030 return (N)jenkins.getNode(node.getNodeName());
1031 }
1032
1033 public <V extends View> V configRoundtrip(V view) throws Exception {
1034 submit(createWebClient().getPage(view, "configure").getFormByName("viewConfig"));
1035 return view;
1036 }
1037
1038
1039 /**
1040 * Asserts that the outcome of the build is a specific outcome.
1041 */
1042 public <R extends Run> R assertBuildStatus(Result status, R r) throws Exception {
1043 if(status==r.getResult())
1044 return r;
1045
1046 // dump the build output in failure message
1047 String msg = "unexpected build status; build log was:\n------\n" + getLog(r) + "\n------\n";
1048 if(r instanceof MatrixBuild) {
1049 MatrixBuild mb = (MatrixBuild)r;
1050 for (MatrixRun mr : mb.getRuns()) {
1051 msg+="--- "+mr.getParent().getCombination()+" ---\n"+getLog(mr)+"\n------\n";
1052 }
1053 }
1054 assertThat(msg, r.getResult(), is(status));
1055 return r;
1056 }
1057
1058 /** Determines whether the specifed HTTP status code is generally "good" */
1059 public boolean isGoodHttpStatus(int status) {
1060 if ((400 <= status) && (status <= 417)) {
1061 return false;
1062 }
1063 if ((500 <= status) && (status <= 505)) {
1064 return false;
1065 }
1066 return true;
1067 }
1068
1069 /** Assert that the specifed page can be served with a "good" HTTP status,
1070 * eg, the page is not missing and can be served without a server error
1071 * @param page
1072 */
1073 public void assertGoodStatus(Page page) {
1074 assertThat(isGoodHttpStatus(page.getWebResponse().getStatusCode()), is(true));
1075 }
1076
1077
1078 public <R extends Run> R assertBuildStatusSuccess(R r) throws Exception {
1079 assertBuildStatus(Result.SUCCESS, r);
1080 return r;
1081 }
1082
1083 public <R extends Run> R assertBuildStatusSuccess(Future<? extends R> r) throws Exception {
1084 assertThat("build was actually scheduled", r, Matchers.notNullValue());
1085 return assertBuildStatusSuccess(r.get());
1086 }
1087
1088 public <J extends AbstractProject<J,R>,R extends AbstractBuild<J,R>> R buildAndAssertSuccess(J job) throws Exception {
1089 return assertBuildStatusSuccess(job.scheduleBuild2(0));
1090 }
1091
1092 /**
1093 * Avoids need for cumbersome {@code this.<J,R>buildAndAssertSuccess(...)} type hints under JDK 7 javac (and supposedly also IntelliJ).
1094 */
1095 public FreeStyleBuild buildAndAssertSuccess(FreeStyleProject job) throws Exception {
1096 return assertBuildStatusSuccess(job.scheduleBuild2(0));
1097 }
1098 public MavenModuleSetBuild buildAndAssertSuccess(MavenModuleSet job) throws Exception {
1099 return assertBuildStatusSuccess(job.scheduleBuild2(0));
1100 }
1101 public MavenBuild buildAndAssertSuccess(MavenModule job) throws Exception {
1102 return assertBuildStatusSuccess(job.scheduleBuild2(0));
1103 }
1104
1105 /**
1106 * Asserts that the console output of the build contains the given substring.
1107 */
1108 public void assertLogContains(String substring, Run run) throws Exception {
1109 assertThat(getLog(run), containsString(substring));
1110 }
1111
1112 /**
1113 * Asserts that the console output of the build does not contain the given substring.
1114 */
1115 public void assertLogNotContains(String substring, Run run) throws Exception {
1116 assertThat(getLog(run), not(containsString(substring)));
1117 }
1118
1119 /**
1120 * Get entire log file (this method is deprecated in hudson.model.Run,
1121 * but in tests it is OK to load entire log).
1122 */
1123 public static String getLog(Run run) throws IOException {
1124 return Util.loadFile(run.getLogFile(), run.getCharset());
1125 }
1126
1127 /**
1128 * Asserts that the XPath matches.
1129 */
1130 public void assertXPath(HtmlPage page, String xpath) {
1131 assertThat(page.getDocumentElement(), hasXPath(xpath));
1132 }
1133
1134 /** Asserts that the XPath matches the contents of a DomNode page. This
1135 * variant of assertXPath(HtmlPage page, String xpath) allows us to
1136 * examine XmlPages.
1137 * @param page
1138 * @param xpath
1139 */
1140 public void assertXPath(DomNode page, String xpath) {
1141 List<? extends Object> nodes = page.getByXPath(xpath);
1142 assertThat("There should be an object that matches XPath:" + xpath, nodes.isEmpty(), is(false));
1143 }
1144
1145 public void assertXPathValue(DomNode page, String xpath, String expectedValue) {
1146 org.w3c.dom.Node node = page.getFirstByXPath(xpath);
1147 assertThat(node, hasXPath(xpath, is(expectedValue)));
1148 }
1149
1150 public void assertXPathValueContains(DomNode page, String xpath, String needle) {
1151 org.w3c.dom.Node node = page.getFirstByXPath(xpath);
1152 assertThat(node, hasXPath(xpath, Matchers.containsString(needle)));
1153 }
1154
1155 public void assertXPathResultsContainText(DomNode page, String xpath, String needle) {
1156 List<? extends Object> nodes = page.getByXPath(xpath);
1157 assertThat("no nodes matching xpath found", nodes.isEmpty(), is(false));
1158 boolean found = false;
1159 for (Object o : nodes) {
1160 if (o instanceof org.w3c.dom.Node) {
1161 org.w3c.dom.Node n = (org.w3c.dom.Node) o;
1162 String textString = n.getTextContent();
1163 if ((textString != null) && textString.contains(needle)) {
1164 found = true;
1165 break;
1166 }
1167 }
1168 }
1169 assertThat("needle found in haystack", found, is(true));
1170 }
1171
1172 /**
1173 * Makes sure that all the images in the page loads successfully.
1174 * (By default, HtmlUnit doesn't load images.)
1175 */
1176 public void assertAllImageLoadSuccessfully(HtmlPage p) {
1177 for (HtmlImage img : p.<HtmlImage>selectNodes("//IMG")) {
1178 try {
1179 img.getHeight();
1180 } catch (IOException e) {
1181 throw new Error("Failed to load "+img.getSrcAttribute(),e);
1182 }
1183 }
1184 }
1185
1186
1187 public void assertStringContains(String message, String haystack, String needle) {
1188 assertThat(message, haystack, Matchers.containsString(needle));
1189 }
1190
1191 public void assertStringContains(String haystack, String needle) {
1192 assertThat(haystack, Matchers.containsString(needle));
1193 }
1194
1195 /**
1196 * Asserts that help files exist for the specified properties of the given instance.
1197 *
1198 * @param type
1199 * The describable class type that should have the associated help files.
1200 * @param properties
1201 * ','-separated list of properties whose help files should exist.
1202 */
1203 public void assertHelpExists(final Class<? extends Describable> type, final String properties) throws Exception {
1204 executeOnServer(new Callable<Object>() {
1205 public Object call() throws Exception {
1206 Descriptor d = jenkins.getDescriptor(type);
1207 WebClient wc = createWebClient();
1208 for (String property : listProperties(properties)) {
1209 String url = d.getHelpFile(property);
1210 assertThat("Help file for the property " + property + " is missing on " + type, url,
1211 Matchers.notNullValue());
1212 wc.goTo(url); // make sure it successfully loads
1213 }
1214 return null;
1215 }
1216 });
1217 }
1218
1219 /**
1220 * Tokenizes "foo,bar,zot,-bar" and returns "foo,zot" (the token that starts with '-' is handled as
1221 * a cancellation.
1222 */
1223 private List<String> listProperties(String properties) {
1224 List<String> props = new ArrayList<String>(Arrays.asList(properties.split(",")));
1225 for (String p : props.toArray(new String[props.size()])) {
1226 if (p.startsWith("-")) {
1227 props.remove(p);
1228 props.remove(p.substring(1));
1229 }
1230 }
1231 return props;
1232 }
1233
1234 /**
1235 * Submits the form.
1236 *
1237 * Plain {@link com.gargoylesoftware.htmlunit.html.HtmlForm#submit()} doesn't work correctly due to the use of YUI in Hudson.
1238 */
1239 public HtmlPage submit(HtmlForm form) throws Exception {
1240 return (HtmlPage) form.submit((HtmlButton) last(form.getHtmlElementsByTagName("button")));
1241 }
1242
1243 /**
1244 * Submits the form by clikcing the submit button of the given name.
1245 *
1246 * @param name
1247 * This corresponds to the @name of &lt;f:submit />
1248 */
1249 public HtmlPage submit(HtmlForm form, String name) throws Exception {
1250 for( HtmlElement e : form.getHtmlElementsByTagName("button")) {
1251 HtmlElement p = (HtmlElement)e.getParentNode().getParentNode();
1252 if(p.getAttribute("name").equals(name)) {
1253 // To make YUI event handling work, this combo seems to be necessary
1254 // the click will trigger _onClick in buton-*.js, but it doesn't submit the form
1255 // (a comment alluding to this behavior can be seen in submitForm method)
1256 // so to complete it, submit the form later.
1257 //
1258 // Just doing form.submit() doesn't work either, because it doesn't do
1259 // the preparation work needed to pass along the name of the button that
1260 // triggered a submission (more concretely, m_oSubmitTrigger is not set.)
1261 ((HtmlButton)e).click();
1262 return (HtmlPage)form.submit((HtmlButton)e);
1263 }
1264 }
1265 throw new AssertionError("No such submit button with the name "+name);
1266 }
1267
1268 public HtmlInput findPreviousInputElement(HtmlElement current, String name) {
1269 return (HtmlInput)current.selectSingleNode("(preceding::input[@name='_."+name+"'])[last()]");
1270 }
1271
1272 public HtmlButton getButtonByCaption(HtmlForm f, String s) {
1273 for (HtmlElement b : f.getHtmlElementsByTagName("button")) {
1274 if(b.getTextContent().trim().equals(s))
1275 return (HtmlButton)b;
1276 }
1277 return null;
1278 }
1279
1280 /**
1281 * Creates a {@link TaskListener} connected to stdout.
1282 */
1283 public TaskListener createTaskListener() {
1284 return new StreamTaskListener(new CloseProofOutputStream(System.out));
1285 }
1286
1287 /**
1288 * Asserts that two JavaBeans are equal as far as the given list of properties are concerned.
1289 *
1290 * <p>
1291 * This method takes two objects that have properties (getXyz, isXyz, or just the public xyz field),
1292 * and makes sure that the property values for each given property are equals (by using {@link org.junit.Assert#assertThat(Object, org.hamcrest.Matcher)})
1293 *
1294 * <p>
1295 * Property values can be null on both objects, and that is OK, but passing in a property that doesn't
1296 * exist will fail an assertion.
1297 *
1298 * <p>
1299 * This method is very convenient for comparing a large number of properties on two objects,
1300 * for example to verify that the configuration is identical after a config screen roundtrip.
1301 *
1302 * @param lhs
1303 * One of the two objects to be compared.
1304 * @param rhs
1305 * The other object to be compared
1306 * @param properties
1307 * ','-separated list of property names that are compared.
1308 * @since 1.297
1309 */
1310 public void assertEqualBeans(Object lhs, Object rhs, String properties) throws Exception {
1311 assertThat("LHS", lhs, notNullValue());
1312 assertThat("RHS", rhs, notNullValue());
1313 for (String p : properties.split(",")) {
1314 PropertyDescriptor pd = PropertyUtils.getPropertyDescriptor(lhs, p);
1315 Object lp,rp;
1316 if(pd==null) {
1317 // field?
1318 try {
1319 Field f = lhs.getClass().getField(p);
1320 lp = f.get(lhs);
1321 rp = f.get(rhs);
1322 } catch (NoSuchFieldException e) {
1323 assertThat("No such property " + p + " on " + lhs.getClass(), pd, notNullValue());
1324 return;
1325 }
1326 } else {
1327 lp = PropertyUtils.getProperty(lhs, p);
1328 rp = PropertyUtils.getProperty(rhs, p);
1329 }
1330
1331 if (lp!=null && rp!=null && lp.getClass().isArray() && rp.getClass().isArray()) {
1332 // deep array equality comparison
1333 int m = Array.getLength(lp);
1334 int n = Array.getLength(rp);
1335 assertThat("Array length is different for property " + p, n, is(m));
1336 for (int i=0; i<m; i++)
1337 assertThat(p + "[" + i + "] is different", Array.get(rp, i), is(Array.get(lp,i)));
1338 return;
1339 }
1340
1341 assertThat("Property " + p + " is different", rp, is(lp));
1342 }
1343 }
1344
1345 public void setQuietPeriod(int qp) {
1346 JenkinsAdaptor.setQuietPeriod(jenkins, qp);
1347 }
1348
1349 /**
1350 * Works like {@link #assertEqualBeans(Object, Object, String)} but figure out the properties
1351 * via {@link org.kohsuke.stapler.DataBoundConstructor}
1352 */
1353 public void assertEqualDataBoundBeans(Object lhs, Object rhs) throws Exception {
1354 if (lhs==null && rhs==null) return;
1355 if (lhs==null) fail("lhs is null while rhs="+rhs);
1356 if (rhs==null) fail("rhs is null while lhs="+lhs);
1357
1358 Constructor<?> lc = findDataBoundConstructor(lhs.getClass());
1359 Constructor<?> rc = findDataBoundConstructor(rhs.getClass());
1360 assertThat("Data bound constructor mismatch. Different type?", (Constructor)rc, is((Constructor)lc));
1361
1362 List<String> primitiveProperties = new ArrayList<String>();
1363
1364 String[] names = ClassDescriptor.loadParameterNames(lc);
1365 Class<?>[] types = lc.getParameterTypes();
1366 assertThat(types.length, is(names.length));
1367 for (int i=0; i<types.length; i++) {
1368 Object lv = ReflectionUtils.getPublicProperty(lhs, names[i]);
1369 Object rv = ReflectionUtils.getPublicProperty(rhs, names[i]);
1370
1371 if (Iterable.class.isAssignableFrom(types[i])) {
1372 Iterable lcol = (Iterable) lv;
1373 Iterable rcol = (Iterable) rv;
1374 Iterator ltr,rtr;
1375 for (ltr=lcol.iterator(), rtr=rcol.iterator(); ltr.hasNext() && rtr.hasNext();) {
1376 Object litem = ltr.next();
1377 Object ritem = rtr.next();
1378
1379 if (findDataBoundConstructor(litem.getClass())!=null) {
1380 assertEqualDataBoundBeans(litem,ritem);
1381 } else {
1382 assertThat(ritem, is(litem));
1383 }
1384 }
1385 assertThat("collection size mismatch between " + lhs + " and " + rhs, ltr.hasNext() ^ rtr.hasNext(),
1386 is(false));
1387 } else
1388 if (findDataBoundConstructor(types[i])!=null || (lv!=null && findDataBoundConstructor(lv.getClass())!=null) || (rv!=null && findDataBoundConstructor(rv.getClass())!=null)) {
1389 // recurse into nested databound objects
1390 assertEqualDataBoundBeans(lv,rv);
1391 } else {
1392 primitiveProperties.add(names[i]);
1393 }
1394 }
1395
1396 // compare shallow primitive properties
1397 if (!primitiveProperties.isEmpty())
1398 assertEqualBeans(lhs,rhs,Util.join(primitiveProperties,","));
1399 }
1400
1401 /**
1402 * Makes sure that two collections are identical via {@link #assertEqualDataBoundBeans(Object, Object)}
1403 */
1404 public void assertEqualDataBoundBeans(List<?> lhs, List<?> rhs) throws Exception {
1405 assertThat(rhs.size(), is(lhs.size()));
1406 for (int i=0; i<lhs.size(); i++)
1407 assertEqualDataBoundBeans(lhs.get(i),rhs.get(i));
1408 }
1409
1410 public Constructor<?> findDataBoundConstructor(Class<?> c) {
1411 for (Constructor<?> m : c.getConstructors()) {
1412 if (m.getAnnotation(DataBoundConstructor.class)!=null)
1413 return m;
1414 }
1415 return null;
1416 }
1417
1418 /**
1419 * Gets the descriptor instance of the current Hudson by its type.
1420 */
1421 public <T extends Descriptor<?>> T get(Class<T> d) {
1422 return jenkins.getDescriptorByType(d);
1423 }
1424
1425
1426 /**
1427 * Returns true if Hudson is building something or going to build something.
1428 */
1429 public boolean isSomethingHappening() {
1430 if (!jenkins.getQueue().isEmpty())
1431 return true;
1432 for (Computer n : jenkins.getComputers())
1433 if (!n.isIdle())
1434 return true;
1435 return false;
1436 }
1437
1438 /**
1439 * Waits until Hudson finishes building everything, including those in the queue.
1440 * <p>
1441 * This method uses a default time out to prevent infinite hang in the automated test execution environment.
1442 */
1443 public void waitUntilNoActivity() throws Exception {
1444 waitUntilNoActivityUpTo(60*1000);
1445 }
1446
1447 /**
1448 * Waits until Hudson finishes building everything, including those in the queue, or fail the test
1449 * if the specified timeout milliseconds is
1450 */
1451 public void waitUntilNoActivityUpTo(int timeout) throws Exception {
1452 long startTime = System.currentTimeMillis();
1453 int streak = 0;
1454
1455 while (true) {
1456 Thread.sleep(10);
1457 if (isSomethingHappening())
1458 streak=0;
1459 else
1460 streak++;
1461
1462 if (streak>5) // the system is quiet for a while
1463 return;
1464
1465 if (System.currentTimeMillis()-startTime > timeout) {
1466 List<Queue.Executable> building = new ArrayList<Queue.Executable>();
1467 for (Computer c : jenkins.getComputers()) {
1468 for (Executor e : c.getExecutors()) {
1469 if (e.isBusy())
1470 building.add(e.getCurrentExecutable());
1471 }
1472 }
1473 throw new AssertionError(String.format("Hudson is still doing something after %dms: queue=%s building=%s",
1474 timeout, Arrays.asList(jenkins.getQueue().getItems()), building));
1475 }
1476 }
1477 }
1478
1479
1480 //
1481 // recipe methods. Control the test environments.
1482 //
1483
1484 /**
1485 * Called during the {@link #before()} to give a test case an opportunity to
1486 * control the test environment in which Hudson is run.
1487 *
1488 * <p>
1489 * One could override this method and call a series of {@code withXXX} methods,
1490 * or you can use the annotations with {@link Recipe} meta-annotation.
1491 */
1492 public void recipe() throws Exception {
1493 recipeLoadCurrentPlugin();
1494 // look for recipe meta-annotation
1495 try {
1496 for (final Annotation a : testDescription.getAnnotations()) {
1497 JenkinsRecipe r = a.annotationType().getAnnotation(JenkinsRecipe.class);
1498 if(r==null) continue;
1499 final JenkinsRecipe.Runner runner = r.value().newInstance();
1500 recipes.add(runner);
1501 tearDowns.add(new LenientRunnable() {
1502 public void run() throws Exception {
1503 runner.tearDown(JenkinsRule.this,a);
1504 }
1505 });
1506 runner.setup(this,a);
1507 }
1508 } catch (NoSuchMethodException e) {
1509 // not a plain JUnit test.
1510 }
1511 }
1512
1513 /**
1514 * If this test harness is launched for a Jenkins plugin, locate the <tt>target/test-classes/the.jpl</tt>
1515 * and add a recipe to install that to the new Jenkins.
1516 *
1517 * <p>
1518 * This file is created by <tt>maven-hpi-plugin</tt> at the testCompile phase when the current
1519 * packaging is <tt>jpi</tt>.
1520 */
1521 public void recipeLoadCurrentPlugin() throws Exception {
1522 final Enumeration<URL> jpls = getClass().getClassLoader().getResources("the.jpl");
1523 final Enumeration<URL> hpls = getClass().getClassLoader().getResources("the.hpl");
1524
1525 final List<URL> all = Collections.list(jpls);
1526 all.addAll(Collections.list(hpls));
1527
1528 if(all.isEmpty()) return; // nope
1529
1530 recipes.add(new JenkinsRecipe.Runner() {
1531 @Override
1532 public void decorateHome(JenkinsRule testCase, File home) throws Exception {
1533
1534 for (URL hpl : all) {
1535
1536 // make the plugin itself available
1537 Manifest m = new Manifest(hpl.openStream());
1538 String shortName = m.getMainAttributes().getValue("Short-Name");
1539 if(shortName==null)
1540 throw new Error(hpl+" doesn't have the Short-Name attribute");
1541 FileUtils.copyURLToFile(hpl, new File(home, "plugins/" + shortName + ".jpl"));
1542
1543 // make dependency plugins available
1544 // TODO: probably better to read POM, but where to read from?
1545 // TODO: this doesn't handle transitive dependencies
1546
1547 // Tom: plugins are now searched on the classpath first. They should be available on
1548 // the compile or test classpath. As a backup, we do a best-effort lookup in the Maven repository
1549 // For transitive dependencies, we could evaluate Plugin-Dependencies transitively.
1550
1551 String dependencies = m.getMainAttributes().getValue("Plugin-Dependencies");
1552 if(dependencies!=null) {
1553 MavenEmbedder embedder = MavenUtil
1554 .createEmbedder(new StreamTaskListener(System.out, Charset.defaultCharset()),
1555 (File) null, null);
1556 for( String dep : dependencies.split(",")) {
1557 String[] tokens = dep.split(":");
1558 String artifactId = tokens[0];
1559 String version = tokens[1];
1560 File dependencyJar=resolveDependencyJar(embedder,artifactId,version);
1561
1562 File dst = new File(home, "plugins/" + artifactId + ".jpi");
1563 if(!dst.exists() || dst.lastModified()!=dependencyJar.lastModified()) {
1564 FileUtils.copyFile(dependencyJar, dst);
1565 }
1566 }
1567 }
1568 }
1569 }
1570
1571 private File resolveDependencyJar(MavenEmbedder embedder, String artifactId, String version) throws Exception {
1572 // try to locate it from manifest
1573 Enumeration<URL> manifests = getClass().getClassLoader().getResources("META-INF/MANIFEST.MF");
1574 while (manifests.hasMoreElements()) {
1575 URL manifest = manifests.nextElement();
1576 InputStream is = manifest.openStream();
1577 Manifest m = new Manifest(is);
1578 is.close();
1579
1580 if (artifactId.equals(m.getMainAttributes().getValue("Short-Name")))
1581 return Which.jarFile(manifest);
1582 }
1583
1584 // need to search multiple group IDs
1585 // TODO: extend manifest to include groupID:artifactID:version
1586 Exception resolutionError=null;
1587 for (String groupId : new String[]{"org.jvnet.hudson.plugins","org.jvnet.hudson.main"}) {
1588
1589 // first try to find it on the classpath.
1590 // this takes advantage of Maven POM located in POM
1591 URL dependencyPomResource = getClass().getResource("/META-INF/maven/"+groupId+"/"+artifactId+"/pom.xml");
1592 if (dependencyPomResource != null) {
1593 // found it
1594 return Which.jarFile(dependencyPomResource);
1595 } else {
1596
1597 try {
1598 // currently the most of the plugins are still hpi
1599 return resolvePluginFile(embedder, artifactId, version, groupId, "hpi");
1600 } catch(AbstractArtifactResolutionException x){
1601 try {
1602 // but also try with the new jpi
1603 return resolvePluginFile(embedder, artifactId, version, groupId, "jpi");
1604 } catch(AbstractArtifactResolutionException x2){
1605 // could be a wrong groupId
1606 resolutionError = x;
1607 }
1608 }
1609
1610 }
1611 }
1612
1613 throw new Exception("Failed to resolve plugin: "+artifactId+" version "+version,resolutionError);
1614 }
1615
1616 private File resolvePluginFile(MavenEmbedder embedder, String artifactId, String version, String groupId, String type)
1617 throws MavenEmbedderException, ComponentLookupException, AbstractArtifactResolutionException {
1618 final Artifact jpi = embedder.createArtifact(groupId, artifactId, version, "compile"/*doesn't matter*/, type);
1619 embedder.resolve(jpi, Arrays.asList(embedder.createRepository("http://maven.glassfish.org/content/groups/public/","repo")),embedder.getLocalRepository());
1620 return jpi.getFile();
1621
1622 }
1623 });
1624 }
1625
1626 public JenkinsRule withNewHome() {
1627 return with(HudsonHomeLoader.NEW);
1628 }
1629
1630 public JenkinsRule withExistingHome(File source) throws Exception {
1631 return with(new HudsonHomeLoader.CopyExisting(source));
1632 }
1633
1634 /**
1635 * Declares that this test case expects to start with one of the preset data sets.
1636 * See {@code test/src/main/preset-data/}
1637 * for available datasets and what they mean.
1638 */
1639 public JenkinsRule withPresetData(String name) {
1640 name = "/" + name + ".zip";
1641 URL res = getClass().getResource(name);
1642 if(res==null) throw new IllegalArgumentException("No such data set found: "+name);
1643
1644 return with(new HudsonHomeLoader.CopyExisting(res));
1645 }
1646
1647 public JenkinsRule with(HudsonHomeLoader homeLoader) {
1648 this.homeLoader = homeLoader;
1649 return this;
1650 }
1651
1652
1653 /**
1654 * Executes the given closure on the server, by the servlet request handling thread,
1655 * in the context of an HTTP request.
1656 *
1657 * <p>
1658 * In {@link HudsonTestCase}, a thread that's executing the test code is different from the thread
1659 * that carries out HTTP requests made through {@link WebClient}. But sometimes you want to
1660 * make assertions and other calls with side-effect from within the request handling thread.
1661 *
1662 * <p>
1663 * This method allows you to do just that. It is useful for testing some methods that
1664 * require {@link org.kohsuke.stapler.StaplerRequest} and {@link org.kohsuke.stapler.StaplerResponse}, or getting the credential
1665 * of the current user (via {@link jenkins.model.Jenkins#getAuthentication()}, and so on.
1666 *
1667 * @param c
1668 * The closure to be executed on the server.
1669 * @return
1670 * The return value from the closure.
1671 * @throws Exception
1672 * If a closure throws any exception, that exception will be carried forward.
1673 */
1674 public <V> V executeOnServer(Callable<V> c) throws Exception {
1675 return createWebClient().executeOnServer(c);
1676 }
1677
1678 /**
1679 * Sometimes a part of a test case may ends up creeping into the serialization tree of {@link hudson.model.Saveable#save()},
1680 * so detect that and flag that as an error.
1681 */
1682 private Object writeReplace() {
1683 throw new AssertionError("JenkinsRule " + testDescription.getDisplayName() + " is not supposed to be serialized");
1684 }
1685
1686 /**
1687 * This is to assist Groovy test clients who are incapable of instantiating the inner classes properly.
1688 */
1689 public WebClient createWebClient() {
1690 return new WebClient();
1691 }
1692
1693 /**
1694 * Extends {@link com.gargoylesoftware.htmlunit.WebClient} and provide convenience methods
1695 * for accessing Hudson.
1696 */
1697 public class WebClient extends com.gargoylesoftware.htmlunit.WebClient {
1698 private static final long serialVersionUID = 5808915989048338267L;
1699
1700 public WebClient() {
1701 // default is IE6, but this causes 'n.doScroll('left')' to fail in event-debug.js:1907 as HtmlUnit doesn't implement such a method,
1702 // so trying something else, until we discover another problem.
1703 super(BrowserVersion.FIREFOX_2);
1704
1705 // setJavaScriptEnabled(false);
1706 setPageCreator(HudsonPageCreator.INSTANCE);
1707 clients.add(this);
1708 // make ajax calls run as post-action for predictable behaviors that simplify debugging
1709 setAjaxController(new AjaxController() {
1710 private static final long serialVersionUID = -5844060943564822678L;
1711 public boolean processSynchron(HtmlPage page, WebRequestSettings settings, boolean async) {
1712 return false;
1713 }
1714 });
1715
1716 setCssErrorHandler(new ErrorHandler() {
1717 final ErrorHandler defaultHandler = new DefaultCssErrorHandler();
1718
1719 public void warning(CSSParseException exception) throws CSSException {
1720 if (!ignore(exception))
1721 defaultHandler.warning(exception);
1722 }
1723
1724 public void error(CSSParseException exception) throws CSSException {
1725 if (!ignore(exception))
1726 defaultHandler.error(exception);
1727 }
1728
1729 public void fatalError(CSSParseException exception) throws CSSException {
1730 if (!ignore(exception))
1731 defaultHandler.fatalError(exception);
1732 }
1733
1734 private boolean ignore(CSSParseException e) {
1735 return e.getURI().contains("/yui/");
1736 }
1737 });
1738
1739 // if no other debugger is installed, install jsDebugger,
1740 // so as not to interfere with the 'Dim' class.
1741 getJavaScriptEngine().getContextFactory().addListener(new ContextFactory.Listener() {
1742 public void contextCreated(Context cx) {
1743 if (cx.getDebugger() == null)
1744 cx.setDebugger(jsDebugger, null);
1745 }
1746
1747 public void contextReleased(Context cx) {
1748 }
1749 });
1750
1751 // avoid a hang by setting a time out. It should be long enough to prevent
1752 // false-positive timeout on slow systems
1753 setTimeout(60*1000);
1754 }
1755
1756 /**
1757 * Logs in to Hudson.
1758 */
1759 public WebClient login(String username, String password) throws Exception {
1760 HtmlPage page = goTo("/login");
1761 // page = (HtmlPage) page.getFirstAnchorByText("Login").click();
1762
1763 HtmlForm form = page.getFormByName("login");
1764 form.getInputByName("j_username").setValueAttribute(username);
1765 form.getInputByName("j_password").setValueAttribute(password);
1766 form.submit(null);
1767 return this;
1768 }
1769
1770 /**
1771 * Logs in to Hudson, by using the user name as the password.
1772 *
1773 * <p>
1774 * See {@link HudsonTestCase#configureUserRealm()} for how the container is set up with the user names
1775 * and passwords. All the test accounts have the same user name and password.
1776 */
1777 public WebClient login(String username) throws Exception {
1778 login(username,username);
1779 return this;
1780 }
1781
1782 /**
1783 * Executes the given closure on the server, by the servlet request handling thread,
1784 * in the context of an HTTP request.
1785 *
1786 * <p>
1787 * In {@link HudsonTestCase}, a thread that's executing the test code is different from the thread
1788 * that carries out HTTP requests made through {@link WebClient}. But sometimes you want to
1789 * make assertions and other calls with side-effect from within the request handling thread.
1790 *
1791 * <p>
1792 * This method allows you to do just that. It is useful for testing some methods that
1793 * require {@link org.kohsuke.stapler.StaplerRequest} and {@link org.kohsuke.stapler.StaplerResponse}, or getting the credential
1794 * of the current user (via {@link jenkins.model.Jenkins#getAuthentication()}, and so on.
1795 *
1796 * @param c
1797 * The closure to be executed on the server.
1798 * @return
1799 * The return value from the closure.
1800 * @throws Exception
1801 * If a closure throws any exception, that exception will be carried forward.
1802 */
1803 public <V> V executeOnServer(final Callable<V> c) throws Exception {
1804 final Exception[] t = new Exception[1];
1805 final List<V> r = new ArrayList<V>(1); // size 1 list
1806
1807 ClosureExecuterAction cea = jenkins.getExtensionList(RootAction.class).get(ClosureExecuterAction.class);
1808 UUID id = UUID.randomUUID();
1809 cea.add(id,new Runnable() {
1810 public void run() {
1811 try {
1812 StaplerResponse rsp = Stapler.getCurrentResponse();
1813 rsp.setStatus(200);
1814 rsp.setContentType("text/html");
1815 r.add(c.call());
1816 } catch (Exception e) {
1817 t[0] = e;
1818 }
1819 }
1820 });
1821 goTo("closures/?uuid="+id);
1822
1823 if (t[0]!=null)
1824 throw t[0];
1825 return r.get(0);
1826 }
1827
1828 public HtmlPage search(String q) throws IOException, SAXException {
1829 HtmlPage top = goTo("");
1830 HtmlForm search = top.getFormByName("search");
1831 search.getInputByName("q").setValueAttribute(q);
1832 return (HtmlPage)search.submit(null);
1833 }
1834
1835 /**
1836 * Short for {@code getPage(r,"")}, to access the top page of a build.
1837 */
1838 public HtmlPage getPage(Run r) throws IOException, SAXException {
1839 return getPage(r,"");
1840 }
1841
1842 /**
1843 * Accesses a page inside {@link Run}.
1844 *
1845 * @param relative
1846 * Relative URL within the build URL, like "changes". Doesn't start with '/'. Can be empty.
1847 */
1848 public HtmlPage getPage(Run r, String relative) throws IOException, SAXException {
1849 return goTo(r.getUrl()+relative);
1850 }
1851
1852 public HtmlPage getPage(Item item) throws IOException, SAXException {
1853 return getPage(item,"");
1854 }
1855
1856 public HtmlPage getPage(Item item, String relative) throws IOException, SAXException {
1857 return goTo(item.getUrl()+relative);
1858 }
1859
1860 public HtmlPage getPage(Node item) throws IOException, SAXException {
1861 return getPage(item,"");
1862 }
1863
1864 public HtmlPage getPage(Node item, String relative) throws IOException, SAXException {
1865 return goTo(item.toComputer().getUrl()+relative);
1866 }
1867
1868 public HtmlPage getPage(View view) throws IOException, SAXException {
1869 return goTo(view.getUrl());
1870 }
1871
1872 public HtmlPage getPage(View view, String relative) throws IOException, SAXException {
1873 return goTo(view.getUrl()+relative);
1874 }
1875
1876 /**
1877 * @deprecated
1878 * This method expects a full URL. This method is marked as deprecated to warn you
1879 * that you probably should be using {@link #goTo(String)} method, which accepts
1880 * a relative path within the Hudson being tested. (IOW, if you really need to hit
1881 * a website on the internet, there's nothing wrong with using this method.)
1882 */
1883 @SuppressWarnings("unchecked")
1884 @Override
1885 public Page getPage(String url) throws IOException, FailingHttpStatusCodeException {
1886 return super.getPage(url);
1887 }
1888
1889 /**
1890 * Requests a page within Hudson.
1891 *
1892 * @param relative
1893 * Relative path within Hudson. Starts without '/'.
1894 * For example, "job/test/" to go to a job top page.
1895 */
1896 public HtmlPage goTo(String relative) throws IOException, SAXException {
1897 Page p = goTo(relative, "text/html");
1898 if (p instanceof HtmlPage) {
1899 return (HtmlPage) p;
1900 } else {
1901 throw new AssertionError("Expected text/html but instead the content type was "+p.getWebResponse().getContentType());
1902 }
1903 }
1904
1905 public Page goTo(String relative, String expectedContentType) throws IOException, SAXException {
1906 Page p = super.getPage(getContextPath() + relative);
1907 assertThat(p.getWebResponse().getContentType(), is(expectedContentType));
1908 return p;
1909 }
1910
1911 /** Loads a page as XML. Useful for testing Hudson's xml api, in concert with
1912 * assertXPath(DomNode page, String xpath)
1913 * @param path the path part of the url to visit
1914 * @return the XmlPage found at that url
1915 * @throws IOException
1916 * @throws SAXException
1917 */
1918 public XmlPage goToXml(String path) throws IOException, SAXException {
1919 Page page = goTo(path, "application/xml");
1920 if (page instanceof XmlPage)
1921 return (XmlPage) page;
1922 else
1923 return null;
1924 }
1925
1926 /**
1927 * Verify that the server rejects an attempt to load the given page.
1928 * @param url a URL path (relative to Jenkins root)
1929 * @param statusCode the expected failure code (such as {@link HttpURLConnection#HTTP_FORBIDDEN})
1930 * @since 1.504
1931 */
1932 public void assertFails(String url, int statusCode) throws Exception {
1933 try {
1934 fail(url + " should have been rejected but produced: " + super.getPage(getContextPath() + url).getWebResponse().getContentAsString());
1935 } catch (FailingHttpStatusCodeException x) {
1936 assertEquals(statusCode, x.getStatusCode());
1937 }
1938 }
1939
1940 /**
1941 * Returns the URL of the webapp top page.
1942 * URL ends with '/'.
1943 */
1944 public String getContextPath() throws IOException {
1945 return getURL().toExternalForm();
1946 }
1947
1948 /**
1949 * Adds a security crumb to the quest
1950 */
1951 public WebRequestSettings addCrumb(WebRequestSettings req) {
1952 NameValuePair crumb[] = { new NameValuePair() };
1953
1954 crumb[0].setName(jenkins.getCrumbIssuer().getDescriptor().getCrumbRequestField());
1955 crumb[0].setValue(jenkins.getCrumbIssuer().getCrumb( null ));
1956
1957 req.setRequestParameters(Arrays.asList( crumb ));
1958 return req;
1959 }
1960
1961 /**
1962 * Creates a URL with crumb parameters relative to {{@link #getContextPath()}
1963 */
1964 public URL createCrumbedUrl(String relativePath) throws IOException {
1965 CrumbIssuer issuer = jenkins.getCrumbIssuer();
1966 String crumbName = issuer.getDescriptor().getCrumbRequestField();
1967 String crumb = issuer.getCrumb(null);
1968
1969 return new URL(getContextPath()+relativePath+"?"+crumbName+"="+crumb);
1970 }
1971
1972 /**
1973 * Makes an HTTP request, process it with the given request handler, and returns the response.
1974 */
1975 public HtmlPage eval(final Runnable requestHandler) throws IOException, SAXException {
1976 ClosureExecuterAction cea = jenkins.getExtensionList(RootAction.class).get(ClosureExecuterAction.class);
1977 UUID id = UUID.randomUUID();
1978 cea.add(id,requestHandler);
1979 return goTo("closures/?uuid="+id);
1980 }
1981
1982 /**
1983 * Starts an interactive JavaScript debugger, and break at the next JavaScript execution.
1984 *
1985 * <p>
1986 * This is useful during debugging a test so that you can step execute and inspect state of JavaScript.
1987 * This will launch a Swing GUI, and the method returns immediately.
1988 *
1989 * <p>
1990 * Note that installing a debugger appears to make an execution of JavaScript substantially slower.
1991 *
1992 * <p>
1993 * TODO: because each script block evaluation in HtmlUnit is done in a separate Rhino context,
1994 * if you step over from one script block, the debugger fails to kick in on the beginning of the next script
1995 * block.
1996 * This makes it difficult to set a break point on arbitrary script block in the HTML page. We need to fix this
1997 * by tweaking {@link org.mozilla.javascript.tools.debugger.Dim.StackFrame#onLineChange(Context, int)}.
1998 */
1999 public Dim interactiveJavaScriptDebugger() {
2000 Global global = new Global();
2001 HtmlUnitContextFactory cf = getJavaScriptEngine().getContextFactory();
2002 global.init(cf);
2003
2004 Dim dim = org.mozilla.javascript.tools.debugger.Main.mainEmbedded(cf, global, "Rhino debugger: " + testDescription.getDisplayName());
2005
2006 // break on exceptions. this catch most of the errors
2007 dim.setBreakOnExceptions(true);
2008
2009 return dim;
2010 }
2011 }
2012
2013 // needs to keep reference, or it gets GC-ed.
2014 private static final Logger XML_HTTP_REQUEST_LOGGER = Logger.getLogger(XMLHttpRequest.class.getName());
2015
2016 static {
2017 // screen scraping relies on locale being fixed.
2018 Locale.setDefault(Locale.ENGLISH);
2019
2020 {// enable debug assistance, since tests are often run from IDE
2021 Dispatcher.TRACE = true;
2022 MetaClass.NO_CACHE=true;
2023 // load resources from the source dir.
2024 File dir = new File("src/main/resources");
2025 if(dir.exists() && MetaClassLoader.debugLoader==null)
2026 try {
2027 MetaClassLoader.debugLoader = new MetaClassLoader(
2028 new URLClassLoader(new URL[]{dir.toURI().toURL()}));
2029 } catch (MalformedURLException e) {
2030 throw new AssertionError(e);
2031 }
2032 }
2033
2034 // suppress INFO output from Spring, which is verbose
2035 Logger.getLogger("org.springframework").setLevel(Level.WARNING);
2036
2037 // hudson-behavior.js relies on this to decide whether it's running unit tests.
2038 Main.isUnitTest = true;
2039
2040 // prototype.js calls this method all the time, so ignore this warning.
2041 XML_HTTP_REQUEST_LOGGER.setFilter(new Filter() {
2042 public boolean isLoggable(LogRecord record) {
2043 return !record.getMessage().contains("XMLHttpRequest.getResponseHeader() was called before the respon"
2044 + "se was available.");
2045 }
2046 });
2047
2048 // remove the upper bound of the POST data size in Jetty.
2049 System.setProperty("org.mortbay.jetty.Request.maxFormContentSize","-1");
2050 }
2051
2052 private static final Logger LOGGER = Logger.getLogger(HudsonTestCase.class.getName());
2053
2054 public static final List<ToolProperty<?>> NO_PROPERTIES = Collections.<ToolProperty<?>>emptyList();
2055
2056 /**
2057 * Specify this to a TCP/IP port number to have slaves started with the debugger.
2058 */
2059 public static int SLAVE_DEBUG_PORT = Integer.getInteger(HudsonTestCase.class.getName()+".slaveDebugPort",-1);
2060
2061 public static final MimeTypes MIME_TYPES = new MimeTypes();
2062 static {
2063 MIME_TYPES.addMimeMapping("js","application/javascript");
2064 Functions.DEBUG_YUI = true;
2065
2066 // during the unit test, predictably releasing classloader is important to avoid
2067 // file descriptor leak.
2068 ClassicPluginStrategy.useAntClassLoader = true;
2069
2070 // DNS multicast support takes up a lot of time during tests, so just disable it altogether
2071 // this also prevents tests from falsely advertising Hudson
2072 DNSMultiCast.disabled = true;
2073
2074 if (!Functions.isWindows()) {
2075 try {
2076 GNUCLibrary.LIBC.unsetenv("MAVEN_OPTS");
2077 GNUCLibrary.LIBC.unsetenv("MAVEN_DEBUG_OPTS");
2078 } catch (Exception e) {
2079 LOGGER.log(Level.WARNING,"Failed to cancel out MAVEN_OPTS",e);
2080 }
2081 }
2082 }
2083
2084 public static class TestBuildWrapper extends BuildWrapper {
2085 public Result buildResultInTearDown;
2086
2087 @Override
2088 public Environment setUp(AbstractBuild build, Launcher launcher, BuildListener listener) throws IOException, InterruptedException {
2089 return new BuildWrapper.Environment() {
2090 @Override
2091 public boolean tearDown(AbstractBuild build, BuildListener listener) throws IOException, InterruptedException {
2092 buildResultInTearDown = build.getResult();
2093 return true;
2094 }
2095 };
2096 }
2097
2098 @Extension
2099 public static class TestBuildWrapperDescriptor extends BuildWrapperDescriptor {
2100 @Override
2101 public boolean isApplicable(AbstractProject<?, ?> project) {
2102 return true;
2103 }
2104
2105 @Override
2106 public BuildWrapper newInstance(StaplerRequest req, JSONObject formData) {
2107 throw new UnsupportedOperationException();
2108 }
2109
2110 @Override
2111 public String getDisplayName() {
2112 return this.getClass().getName();
2113 }
2114 }
2115 }
2116
2117 public Description getTestDescription() {
2118 return testDescription;
2119 }
2120 }
2121