
org.jvnet.hudson.test.HudsonTestCase Maven / Gradle / Ivy
Show all versions of hudson-test-framework Show documentation
/*******************************************************************************
*
* Copyright (c) 2004-2009 Oracle Corporation.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
*
* Kohsuke Kawaguchi, Erik Ramfelt, Yahoo! Inc., Tom Huybrechts
*
*
*******************************************************************************/
package org.jvnet.hudson.test;
import com.gargoylesoftware.htmlunit.DefaultCssErrorHandler;
import com.gargoylesoftware.htmlunit.javascript.HtmlUnitContextFactory;
import com.gargoylesoftware.htmlunit.javascript.host.xml.XMLHttpRequest;
import hudson.*;
import hudson.Util;
import hudson.model.*;
import hudson.model.Queue.Executable;
import hudson.security.ACL;
import hudson.security.AbstractPasswordBasedSecurityRealm;
import hudson.security.GroupDetails;
import hudson.security.SecurityRealm;
import hudson.slaves.ComputerConnector;
import hudson.tasks.Builder;
import hudson.tasks.Publisher;
import hudson.tools.ToolProperty;
import hudson.remoting.Which;
import hudson.Launcher.LocalLauncher;
import hudson.matrix.MatrixProject;
import hudson.matrix.MatrixBuild;
import hudson.matrix.MatrixRun;
import hudson.security.csrf.CrumbIssuer;
import hudson.slaves.CommandLauncher;
import hudson.slaves.DumbSlave;
import hudson.slaves.RetentionStrategy;
import org.eclipse.hudson.WebAppController;
import org.eclipse.hudson.WebAppController.DefaultInstallStrategy;
import hudson.tasks.Mailer;
import hudson.tasks.Maven;
import hudson.tasks.Ant;
import hudson.tasks.Ant.AntInstallation;
import hudson.tasks.Maven.MavenInstallation;
import hudson.util.PersistedList;
import hudson.util.ReflectionUtils;
import hudson.util.StreamTaskListener;
import hudson.util.PluginServletFilter;
import hudson.model.Node.Mode;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.annotation.Annotation;
import java.lang.ref.WeakReference;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Field;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Arrays;
import java.util.Collections;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import java.util.jar.Manifest;
import java.util.logging.Filter;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import java.beans.PropertyDescriptor;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import junit.framework.TestCase;
import net.sourceforge.htmlunit.corejs.javascript.Context;
import net.sourceforge.htmlunit.corejs.javascript.ContextFactory.Listener;
import org.apache.commons.httpclient.NameValuePair;
import org.apache.commons.io.FileUtils;
import org.apache.commons.beanutils.PropertyUtils;
import org.hudsonci.inject.Smoothie;
import org.hudsonci.inject.SmoothieUtil;
import org.hudsonci.inject.internal.SmoothieContainerBootstrap;
import org.jvnet.hudson.test.HudsonHomeLoader.CopyExisting;
import org.jvnet.hudson.test.recipes.Recipe;
import org.jvnet.hudson.test.recipes.Recipe.Runner;
import org.jvnet.hudson.test.recipes.WithPlugin;
import org.jvnet.hudson.test.rhino.JavaScriptDebugger;
import org.kohsuke.stapler.ClassDescriptor;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.Dispatcher;
import org.kohsuke.stapler.MetaClass;
import org.kohsuke.stapler.MetaClassLoader;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import org.kohsuke.stapler.Stapler;
import org.mortbay.jetty.MimeTypes;
import org.mortbay.jetty.Server;
import org.mortbay.jetty.bio.SocketConnector;
import org.mortbay.jetty.security.HashUserRealm;
import org.mortbay.jetty.security.UserRealm;
import org.mortbay.jetty.webapp.Configuration;
import org.mortbay.jetty.webapp.WebAppContext;
import org.mortbay.jetty.webapp.WebXmlConfiguration;
import org.mozilla.javascript.tools.debugger.Dim;
import org.mozilla.javascript.tools.shell.Global;
import org.springframework.dao.DataAccessException;
import org.w3c.css.sac.CSSException;
import org.w3c.css.sac.CSSParseException;
import org.w3c.css.sac.ErrorHandler;
import org.xml.sax.SAXException;
import com.gargoylesoftware.htmlunit.AjaxController;
import com.gargoylesoftware.htmlunit.BrowserVersion;
import com.gargoylesoftware.htmlunit.FailingHttpStatusCodeException;
import com.gargoylesoftware.htmlunit.Page;
import com.gargoylesoftware.htmlunit.WebRequestSettings;
import com.gargoylesoftware.htmlunit.xml.XmlPage;
import com.gargoylesoftware.htmlunit.html.*;
import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.WebResource;
import com.sun.jersey.api.client.config.ClientConfig;
import com.sun.jersey.api.client.config.DefaultClientConfig;
import hudson.slaves.ComputerListener;
import java.util.concurrent.CountDownLatch;
import hudson.maven.MavenEmbedder;
import hudson.maven.MavenRequest;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.charset.Charset;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.resolver.AbstractArtifactResolutionException;
import org.eclipse.hudson.HudsonServletContextListener;
import org.eclipse.hudson.security.HudsonSecurityEntitiesHolder;
import org.eclipse.hudson.security.HudsonSecurityManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
/**
* Base class for all Hudson test cases.
*
* @see Wiki
* article about unit testing in Hudson
* @author Kohsuke Kawaguchi
*/
public abstract class HudsonTestCase extends TestCase implements RootAction {
public Hudson hudson;
protected final TestEnvironment env = new TestEnvironment(this);
protected HudsonHomeLoader homeLoader = HudsonHomeLoader.NEW;
/**
* TCP/IP port that the server is listening on.
*/
protected int localPort;
protected Server server;
/**
* Where in the {@link Server} is Hudson deployed? Just like
* {@link ServletContext#getContextPath()}, starts with '/' but doesn't end
* with '/'.
*/
protected String contextPath = "";
/**
* {@link Runnable}s to be invoked at {@link #tearDown()}.
*/
protected List tearDowns = new ArrayList();
protected List recipes = new ArrayList();
/**
* Remember {@link WebClient}s that are created, to release them properly.
*/
private List> clients = new ArrayList>();
/**
* JavaScript "debugger" that provides you information about the JavaScript
* call stack and the current values of the local variables in those stack
* frame.
*
* Unlike Java debugger, which you as a human interfaces directly and
* interactively, this JavaScript debugger is to be interfaced by your
* program (or through the expression evaluation capability of your Java
* debugger.)
*/
protected JavaScriptDebugger jsDebugger = new JavaScriptDebugger();
/**
* If this test case has additional {@link WithPlugin} annotations, set to
* true. This will cause a fresh {@link PluginManager} to be created for
* this test. Leaving this to false enables the test harness to use a
* pre-loaded plugin manager, which runs faster.
*/
public boolean useLocalPluginManager = true; // FIXME: At the new smoothie container needs the real plugin manager
public ComputerConnectorTester computerConnectorTester = new ComputerConnectorTester(this);
protected HudsonTestCase(String name) {
super(name);
}
protected HudsonTestCase() {
}
@Override
public void runBare() throws Throwable {
// override the thread name to make the thread dump more useful.
Thread t = Thread.currentThread();
String o = getClass().getName() + '.' + t.getName();
t.setName("Executing " + getName());
try {
super.runBare();
} finally {
t.setName(o);
}
}
@Override
protected void setUp() throws Exception {
//System.setProperty("hudson.PluginStrategy", "hudson.ClassicPluginStrategy");
env.pin();
recipe();
AbstractProject.WORKSPACE.toString();
User.clear();
// Bootstrap the container with details about our testing classes, so it can figure out what to scan/include
SmoothieUtil.reset(); // force-reset, some tests may not properly hit the tear-down to reset so do it again here
new SmoothieContainerBootstrap().bootstrap(getClass().getClassLoader(), Hudson.class, Smoothie.class, HudsonTestCase.class, getClass());
try {
hudson = newHudson();
} catch (Exception e) {
// 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.
Field f = Hudson.class.getDeclaredField("theInstance");
f.setAccessible(true);
f.set(null, null);
throw e;
}
hudson.setNoUsageStatistics(true); // collecting usage stats from tests are pointless.
hudson.setCrumbIssuer(new TestCrumbIssuer());
final WebAppController controller = WebAppController.get();
try {
controller.setContext(hudson.servletContext);
} catch (IllegalStateException e) {
// handle tests which run several times inside the same JVM
Field f = WebAppController.class.getDeclaredField("context");
f.setAccessible(true);
f.set(controller, hudson.servletContext);
}
try {
controller.setInstallStrategy(new DefaultInstallStrategy());
} catch (IllegalStateException e) {
// strategy already set ignore
}
controller.install(hudson);
hudson.servletContext.setAttribute("version", "?");
HudsonServletContextListener.installExpressionFactory(new ServletContextEvent(hudson.servletContext));
// set a default JDK to be the one that the harness is using.
hudson.getJDKs().add(new JDK("default", System.getProperty("java.home")));
configureUpdateCenter();
// expose the test instance as a part of URL tree.
// this allows tests to use a part of the URL space for itself.
hudson.getActions().add(this);
// cause all the descriptors to reload.
// ideally we'd like to reset them to properly emulate the behavior, but that's not possible.
Mailer.descriptor().setHudsonUrl(null);
for (Descriptor d : hudson.getExtensionList(Descriptor.class)) {
d.load();
}
}
/**
* Configures the update center setting for the test. By default, we load
* updates from local proxy to avoid network traffic as much as possible.
*/
protected void configureUpdateCenter() throws Exception {
final String updateCenterUrl = "http://localhost:" + JavaNetReverseProxy.getInstance().localPort + "/update-center.json";
// don't waste bandwidth talking to the update center
DownloadService.neverUpdate = true;
UpdateSite.neverUpdate = true;
PersistedList sites = hudson.getUpdateCenter().getSites();
sites.clear();
sites.add(new UpdateSite("default", updateCenterUrl));
}
@Override
protected void tearDown() throws Exception {
try {
// cancel pending asynchronous operations, although this doesn't really seem to be working
for (WeakReference client : clients) {
WebClient c = client.get();
if (c == null) {
continue;
}
// unload the page to cancel asynchronous operations
c.getPage("about:blank");
}
clients.clear();
} finally {
server.stop();
for (LenientRunnable r : tearDowns) {
r.run();
}
hudson.cleanUp();
ExtensionList.clearLegacyInstances();
DescriptorExtensionList.clearLegacyInstances();
// Force the container bits to reset
SmoothieUtil.reset();
// Clear hudson refrence
hudson = null;
server = null;
// Clear any filters stored in this singlton
PluginServletFilter.clearFilters();
// Hudson creates ClassLoaders for plugins that hold on to file descriptors of its jar files,
// but because there's no explicit dispose method on ClassLoader, they won't get GC-ed until
// at some later point, leading to possible file descriptor overflow. So encourage GC now.
// see http://bugs.sun.com/view_bug.do?bug_id=4950148
System.gc();
env.dispose();
}
}
@Override
protected void runTest() throws Throwable {
String testName = getClass().getSimpleName() + "." + getName();
System.out.println(">>> Starting " + testName + " >>>");
// so that test code has all the access to the system
SecurityContextHolder.getContext().setAuthentication(ACL.SYSTEM);
try {
super.runTest();
} finally {
System.out.println("<<< Finished " + testName + " <<<");
}
}
public String getIconFileName() {
return null;
}
public String getDisplayName() {
return null;
}
public String getUrlName() {
return "self";
}
/**
* Creates a new instance of {@link Hudson}. If the derived class wants to
* create it in a different way, you can override it.
*/
protected Hudson newHudson() throws Exception {
File home = homeLoader.allocate();
// Create the Security Manager
HudsonSecurityEntitiesHolder.setHudsonSecurityManager(new HudsonSecurityManager(home));
for (Runner r : recipes) {
r.decorateHome(this, home);
}
return new Hudson(home, createWebServer(), useLocalPluginManager ? null : TestPluginManager.INSTANCE);
}
/**
* Prepares a webapp hosting environment to get {@link ServletContext}
* implementation that we need for testing.
*/
protected ServletContext createWebServer() throws Exception {
server = new Server();
WebAppContext context = new WebAppContext(WarExploder.getExplodedDir().getPath(), contextPath);
context.setClassLoader(getClass().getClassLoader());
context.setConfigurations(new Configuration[]{new WebXmlConfiguration(), new NoListenerConfiguration()});
server.setHandler(context);
context.setMimeTypes(MIME_TYPES);
SocketConnector connector = new SocketConnector();
server.addConnector(connector);
server.addUserRealm(configureUserRealm());
server.start();
localPort = connector.getLocalPort();
return context.getServletContext();
}
/**
* Configures a security realm for a test.
*/
protected UserRealm configureUserRealm() {
HashUserRealm realm = new HashUserRealm();
realm.setName("default"); // this is the magic realm name to make it effective on everywhere
realm.put("alice", "alice");
realm.put("bob", "bob");
realm.put("charlie", "charlie");
realm.addUserToRole("alice", "female");
realm.addUserToRole("bob", "male");
realm.addUserToRole("charlie", "male");
return realm;
}
// /**
// * Sets guest credentials to access java.net Subversion repo.
// */
// protected void setJavaNetCredential() throws SVNException, IOException {
// // set the credential to access svn.dev.java.net
// hudson.getDescriptorByType(SubversionSCM.DescriptorImpl.class).postCredential("https://svn.dev.java.net/svn/hudson/","guest","",null,new PrintWriter(new NullStream()));
// }
/**
* Returns the older default Maven, while still allowing specification of
* other bundled Mavens.
*/
protected MavenInstallation configureDefaultMaven() throws Exception {
return configureDefaultMaven("apache-maven-2.2.1", MavenInstallation.MAVEN_20);
}
protected MavenInstallation configureMaven3() throws Exception {
MavenInstallation mvn = configureDefaultMaven("apache-maven-3.0.1", MavenInstallation.MAVEN_30);
MavenInstallation m3 = new MavenInstallation("apache-maven-3.0.1", mvn.getHome(), NO_PROPERTIES);
hudson.getDescriptorByType(Maven.DescriptorImpl.class).setInstallations(m3);
return m3;
}
/**
* Locates Maven2 and configure that as the only Maven in the system.
*/
protected MavenInstallation configureDefaultMaven(String mavenVersion, int mavenReqVersion) throws Exception {
// first if we are running inside Maven, pick that Maven, if it meets the criteria we require..
// does it exists in the buildDirectory see maven-junit-plugin systemProperties
// buildDirectory -> ${project.build.directory} (so no reason to be null ;-) )
String buildDirectory = System.getProperty("buildDirectory", "./target/classes/");
File mavenAlreadyInstalled = new File(buildDirectory, mavenVersion);
if (mavenAlreadyInstalled.exists()) {
MavenInstallation mavenInstallation = new MavenInstallation("default", mavenAlreadyInstalled.getAbsolutePath(), NO_PROPERTIES);
hudson.getDescriptorByType(Maven.DescriptorImpl.class).setInstallations(mavenInstallation);
return mavenInstallation;
}
String home = System.getProperty("maven.home");
if (home != null) {
MavenInstallation mavenInstallation = new MavenInstallation("default", home, NO_PROPERTIES);
if (mavenInstallation.meetsMavenReqVersion(createLocalLauncher(), mavenReqVersion)) {
hudson.getDescriptorByType(Maven.DescriptorImpl.class).setInstallations(mavenInstallation);
return mavenInstallation;
}
}
// otherwise extract the copy we have.
// this happens when a test is invoked from an IDE, for example.
LOGGER.warning("Extracting a copy of Maven bundled in the test harness. "
+ "To avoid a performance hit, set the system property 'maven.home' to point to a Maven2 installation.");
FilePath mvn = hudson.getRootPath().createTempFile("maven", "zip");
mvn.copyFrom(HudsonTestCase.class.getClassLoader().getResource(mavenVersion + "-bin.zip"));
File mvnHome = new File(buildDirectory);//createTmpDir();
mvn.unzip(new FilePath(mvnHome));
// TODO: switch to tar that preserves file permissions more easily
if (!Functions.isWindows()) {
Util.chmod(new File(mvnHome, mavenVersion + "/bin/mvn"), 0755);
}
MavenInstallation mavenInstallation = new MavenInstallation("default",
new File(mvnHome, mavenVersion).getAbsolutePath(), NO_PROPERTIES);
hudson.getDescriptorByType(Maven.DescriptorImpl.class).setInstallations(mavenInstallation);
return mavenInstallation;
}
/**
* Extracts Ant and configures it.
*/
protected Ant.AntInstallation configureDefaultAnt() throws Exception {
Ant.AntInstallation antInstallation;
if (System.getenv("ANT_HOME") != null) {
antInstallation = new AntInstallation("default", System.getenv("ANT_HOME"), NO_PROPERTIES);
} else {
LOGGER.warning("Extracting a copy of Ant bundled in the test harness. "
+ "To avoid a performance hit, set the environment variable ANT_HOME to point to an Ant installation.");
FilePath ant = hudson.getRootPath().createTempFile("ant", "zip");
ant.copyFrom(HudsonTestCase.class.getClassLoader().getResource("apache-ant-1.8.1-bin.zip"));
File antHome = createTmpDir();
ant.unzip(new FilePath(antHome));
// TODO: switch to tar that preserves file permissions more easily
if (!Functions.isWindows()) {
Util.chmod(new File(antHome, "apache-ant-1.8.1/bin/ant"), 0755);
}
antInstallation = new AntInstallation("default", new File(antHome, "apache-ant-1.8.1").getAbsolutePath(), NO_PROPERTIES);
}
hudson.getDescriptorByType(Ant.DescriptorImpl.class).setInstallations(antInstallation);
return antInstallation;
}
//
// Convenience methods
//
protected FreeStyleProject createFreeStyleProject() throws IOException {
return createFreeStyleProject(createUniqueProjectName());
}
protected FreeStyleProject createFreeStyleProject(String name) throws IOException {
return hudson.createProject(FreeStyleProject.class, name);
}
protected MatrixProject createMatrixProject() throws IOException {
return createMatrixProject(createUniqueProjectName());
}
protected MatrixProject createMatrixProject(String name) throws IOException {
return hudson.createProject(MatrixProject.class, name);
}
protected String createUniqueProjectName() {
return "test" + hudson.getItems().size();
}
/**
* Creates {@link LocalLauncher}. Useful for launching processes.
*/
protected LocalLauncher createLocalLauncher() {
return new LocalLauncher(StreamTaskListener.fromStdout());
}
/**
* Allocates a new temporary directory for the duration of this test.
*/
public File createTmpDir() throws IOException {
return env.temporaryDirectoryAllocator.allocate();
}
public DumbSlave createSlave() throws Exception {
return createSlave("", null);
}
/**
* Creates and launches a new slave on the local host.
*/
public DumbSlave createSlave(Label l) throws Exception {
return createSlave(l, null);
}
/**
* Creates a test {@link SecurityRealm} that recognizes username==password
* as valid.
*/
public SecurityRealm createDummySecurityRealm() {
return new AbstractPasswordBasedSecurityRealm() {
@Override
protected UserDetails authenticate(String username, String password) throws AuthenticationException {
if (username.equals(password)) {
return loadUserByUsername(username);
}
throw new BadCredentialsException(username);
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException {
return new org.springframework.security.core.userdetails.User(username, "", true, true, true, true, Arrays.asList(new GrantedAuthority[]{AUTHENTICATED_AUTHORITY}));
}
@Override
public GroupDetails loadGroupByGroupname(String groupname) throws UsernameNotFoundException, DataAccessException {
throw new UsernameNotFoundException(groupname);
}
};
}
/**
* Returns the URL of the webapp top page. URL ends with '/'.
*/
public URL getURL() throws IOException {
return new URL("http://localhost:" + localPort + contextPath + "/");
}
public DumbSlave createSlave(EnvVars env) throws Exception {
return createSlave("", env);
}
public DumbSlave createSlave(Label l, EnvVars env) throws Exception {
return createSlave(l == null ? null : l.getExpression(), env);
}
/**
* Creates a slave with certain additional environment variables
*/
public DumbSlave createSlave(String labels, EnvVars env) throws Exception {
synchronized (hudson) {
// this synchronization block is so that we don't end up adding the same slave name more than once.
int sz = hudson.getNodes().size();
DumbSlave slave = new DumbSlave("slave" + sz, "dummy",
createTmpDir().getPath(), "1", Mode.NORMAL, labels == null ? "" : labels, createComputerLauncher(env), RetentionStrategy.NOOP, Collections.EMPTY_LIST);
hudson.addNode(slave);
return slave;
}
}
public DumbSlave createSlave(String nodeName, String labels, EnvVars env) throws Exception {
synchronized (hudson) {
DumbSlave slave = new DumbSlave(nodeName, "dummy",
createTmpDir().getPath(), "1", Node.Mode.NORMAL, labels==null?"":labels, createComputerLauncher(env), RetentionStrategy.NOOP, Collections.EMPTY_LIST);
hudson.addNode(slave);
return slave;
}
}
public PretendSlave createPretendSlave(FakeLauncher faker) throws Exception {
synchronized (hudson) {
int sz = hudson.getNodes().size();
PretendSlave slave = new PretendSlave("slave" + sz, createTmpDir().getPath(), "", createComputerLauncher(null), faker);
hudson.addNode(slave);
return slave;
}
}
/**
* Creates a {@link CommandLauncher} for launching a slave locally.
*
* @param env Environment variables to add to the slave process. Can be
* null.
*/
public CommandLauncher createComputerLauncher(EnvVars env) throws URISyntaxException, MalformedURLException {
int sz = hudson.getNodes().size();
return new CommandLauncher(
String.format("\"%s/bin/java\" %s -jar \"%s\"",
System.getProperty("java.home"),
SLAVE_DEBUG_PORT > 0 ? " -Xdebug -Xrunjdwp:transport=dt_socket,server=y,address=" + (SLAVE_DEBUG_PORT + sz) : "",
new File(hudson.getJnlpJars("slave.jar").getURL().toURI()).getAbsolutePath()),
env);
}
/**
* Create a new slave on the local host and wait for it to come onilne
* before returning.
*/
public DumbSlave createOnlineSlave() throws Exception {
return createOnlineSlave(null);
}
/**
* Create a new slave on the local host and wait for it to come onilne
* before returning.
*/
public DumbSlave createOnlineSlave(Label l) throws Exception {
return createOnlineSlave(l, null);
}
/**
* Create a new slave on the local host and wait for it to come online
* before returning
*/
public DumbSlave createOnlineSlave(Label l, EnvVars env) throws Exception {
final CountDownLatch latch = new CountDownLatch(1);
ComputerListener waiter = new ComputerListener() {
@Override
public void onOnline(Computer C, TaskListener t) {
latch.countDown();
unregister();
}
};
waiter.register();
DumbSlave s = createSlave(l, env);
latch.await();
return s;
}
/**
* Blocks until the ENTER key is hit. This is useful during debugging a test
* so that one can inspect the state of Hudson through the web browser.
*/
public void interactiveBreak() throws Exception {
System.out.println("Hudson is running at http://localhost:" + localPort + "/");
new BufferedReader(new InputStreamReader(System.in)).readLine();
}
/**
* Returns the last item in the list.
*/
protected T last(List items) {
return items.get(items.size() - 1);
}
/**
* Pauses the execution until ENTER is hit in the console. This is often
* very useful so that you can interact with Hudson from an browser, while
* developing a test case.
*/
protected void pause() throws IOException {
new BufferedReader(new InputStreamReader(System.in)).readLine();
}
/**
* Performs a search from the search box.
*/
protected Page search(String q) throws Exception {
return new WebClient().search(q);
}
/**
* Hits the Hudson system configuration and submits without any
* modification.
*/
protected void configRoundtrip() throws Exception {
submit(createWebClient().goTo("configure").getFormByName("config"));
}
/**
* Loads a configuration page and submits it without any modifications, to
* perform a round-trip configuration test.
See
* http://wiki.hudson-ci.org/display/HUDSON/Unit+Test#UnitTest-Configurationroundtriptesting
*/
protected
P configRoundtrip(P job) throws Exception {
submit(createWebClient().getPage(job, "configure").getFormByName("config"));
return job;
}
// protected
P configRoundtrip(P job) throws Exception {
// submit(createWebClient().getPage(job,"configure").getFormByName("config"));
// return job;
// }
/**
* Performs a configuration round-trip testing for a builder.
*/
protected B configRoundtrip(B before) throws Exception {
FreeStyleProject p = createFreeStyleProject();
p.getBuildersList().add(before);
configRoundtrip(p);
return (B) p.getBuildersList().get(before.getClass());
}
/**
* Performs a configuration round-trip testing for a publisher.
*/
protected
P configRoundtrip(P before) throws Exception {
FreeStyleProject p = createFreeStyleProject();
p.getPublishersList().add(before);
configRoundtrip(p);
return (P) p.getPublishersList().get(before.getClass());
}
protected C configRoundtrip(C before) throws Exception {
computerConnectorTester.connector = before;
submit(createWebClient().goTo("self/computerConnectorTester/configure").getFormByName("config"));
return (C) computerConnectorTester.connector;
}
/**
* Asserts that the outcome of the build is a specific outcome.
*/
public R assertBuildStatus(Result status, R r) throws Exception {
if (status == r.getResult()) {
return r;
}
// dump the build output in failure message
String msg = "unexpected build status; build log was:\n------\n" + getLog(r) + "\n------\n";
if (r instanceof MatrixBuild) {
MatrixBuild mb = (MatrixBuild) r;
for (MatrixRun mr : mb.getRuns()) {
msg += "--- " + mr.getParent().getCombination() + " ---\n" + getLog(mr) + "\n------\n";
}
}
assertEquals(msg, status, r.getResult());
return r;
}
/**
* Determines whether the specifed HTTP status code is generally "good"
*/
public boolean isGoodHttpStatus(int status) {
if ((400 <= status) && (status <= 417)) {
return false;
}
if ((500 <= status) && (status <= 505)) {
return false;
}
return true;
}
/**
* Assert that the specifed page can be served with a "good" HTTP status,
* eg, the page is not missing and can be served without a server error
*
* @param page
*/
public void assertGoodStatus(Page page) {
assertTrue(isGoodHttpStatus(page.getWebResponse().getStatusCode()));
}
public R assertBuildStatusSuccess(R r) throws Exception {
assertBuildStatus(Result.SUCCESS, r);
return r;
}
public R assertBuildStatusSuccess(Future extends R> r) throws Exception {
assertNotNull("build was actually scheduled", r);
return assertBuildStatusSuccess(r.get());
}
public , R extends AbstractBuild> R buildAndAssertSuccess(J job) throws Exception {
return assertBuildStatusSuccess(job.scheduleBuild2(0));
}
/**
* Avoids need for cumbersome {@code this.buildAndAssertSuccess(...)}
* type hints under JDK 7 javac (and supposedly also IntelliJ).
*/
public FreeStyleBuild buildAndAssertSuccess(FreeStyleProject job) throws Exception {
return assertBuildStatusSuccess(job.scheduleBuild2(0));
}
/**
* Asserts that the console output of the build contains the given
* substring.
*/
public void assertLogContains(String substring, Run run) throws Exception {
String log = getLog(run);
if (log.contains(substring)) {
return; // good!
}
System.out.println(log);
fail("Console output of " + run + " didn't contain " + substring);
}
/**
* Get entire log file (this method is deprecated in hudson.model.Run, but
* in tests it is OK to load entire log).
*/
protected static String getLog(Run run) throws IOException {
return Util.loadFile(run.getLogFile(), run.getCharset());
}
/**
* Asserts that the XPath matches.
*/
public void assertXPath(HtmlPage page, String xpath) {
assertNotNull("There should be an object that matches XPath:" + xpath,
page.getDocumentElement().selectSingleNode(xpath));
}
/**
* Asserts that the XPath matches the contents of a DomNode page. This
* variant of assertXPath(HtmlPage page, String xpath) allows us to examine
* XmlPages.
*
* @param page
* @param xpath
*/
public void assertXPath(DomNode page, String xpath) {
List< ? extends Object> nodes = page.getByXPath(xpath);
assertFalse("There should be an object that matches XPath:" + xpath, nodes.isEmpty());
}
public void assertXPathValue(DomNode page, String xpath, String expectedValue) {
Object node = page.getFirstByXPath(xpath);
assertNotNull("no node found", node);
assertTrue("the found object was not a Node " + xpath, node instanceof org.w3c.dom.Node);
org.w3c.dom.Node n = (org.w3c.dom.Node) node;
String textString = n.getTextContent();
assertEquals("xpath value should match for " + xpath, expectedValue, textString);
}
public void assertXPathValueContains(DomNode page, String xpath, String needle) {
Object node = page.getFirstByXPath(xpath);
assertNotNull("no node found", node);
assertTrue("the found object was not a Node " + xpath, node instanceof org.w3c.dom.Node);
org.w3c.dom.Node n = (org.w3c.dom.Node) node;
String textString = n.getTextContent();
assertTrue("needle found in haystack", textString.contains(needle));
}
public void assertXPathResultsContainText(DomNode page, String xpath, String needle) {
List extends Object> nodes = page.getByXPath(xpath);
assertFalse("no nodes matching xpath found", nodes.isEmpty());
boolean found = false;
for (Object o : nodes) {
if (o instanceof org.w3c.dom.Node) {
org.w3c.dom.Node n = (org.w3c.dom.Node) o;
String textString = n.getTextContent();
if ((textString != null) && textString.contains(needle)) {
found = true;
break;
}
}
}
assertTrue("needle found in haystack", found);
}
public void assertStringContains(String message, String haystack, String needle) {
if (haystack.contains(needle)) {
// good
return;
} else {
fail(message + " (seeking '" + needle + "')");
}
}
public void assertStringContains(String haystack, String needle) {
if (haystack.contains(needle)) {
// good
return;
} else {
fail("Could not find '" + needle + "'.");
}
}
/**
* Asserts that help files exist for the specified properties of the given
* instance.
*
* @param type The describable class type that should have the associated
* help files.
* @param properties ','-separated list of properties whose help files
* should exist.
*/
public void assertHelpExists(final Class extends Describable> type, final String properties) throws Exception {
executeOnServer(new Callable