提交 6253fb1b 编写于 作者: K kohsuke

Test performance improvement by allowing multiple Hudsons to reuse classloaders.

git-svn-id: https://hudson.dev.java.net/svn/hudson/trunk/hudson/main@30070 71c3de6d-444a-0410-be80-ed276b4c234a
上级 71d28e09
......@@ -389,7 +389,7 @@ THE SOFTWARE.
<dependency>
<groupId>org.jvnet.hudson</groupId>
<artifactId>task-reactor</artifactId>
<version>1.1</version>
<version>1.2</version>
</dependency>
<dependency>
<groupId>org.jvnet.localizer</groupId>
......
......@@ -166,7 +166,7 @@ public class ClassicPluginStrategy implements PluginStrategy {
ClassLoader dependencyLoader = new DependencyClassLoader(getBaseClassLoader(atts), archive, Util.join(dependencies,optionalDependencies));
return new PluginWrapper(archive, manifest, baseResourceURL,
return new PluginWrapper(pluginManager, archive, manifest, baseResourceURL,
createClassLoader(paths, dependencyLoader), disableFile, dependencies, optionalDependencies);
}
......@@ -239,12 +239,6 @@ public class ClassicPluginStrategy implements PluginStrategy {
}
public void load(PluginWrapper wrapper) throws IOException {
loadPluginDependencies(wrapper.getDependencies(),
wrapper.getOptionalDependencies());
if (!wrapper.isActive())
return;
// override the context classloader so that XStream activity in plugin.start()
// will be able to resolve classes in this plugin
ClassLoader old = Thread.currentThread().getContextClassLoader();
......@@ -354,36 +348,6 @@ public class ClassicPluginStrategy implements PluginStrategy {
}
}
/**
* Loads the dependencies to other plugins.
*
* @throws IOException
* thrown if one or several mandatory dependencies doesnt
* exists.
*/
private void loadPluginDependencies(List<Dependency> dependencies,
List<Dependency> optionalDependencies) throws IOException {
List<String> missingDependencies = new ArrayList<String>();
// make sure dependencies exist
for (Dependency d : dependencies) {
if (pluginManager.getPlugin(d.shortName) == null)
missingDependencies.add(d.toString());
}
if (!missingDependencies.isEmpty()) {
StringBuilder builder = new StringBuilder();
builder.append("Dependency ");
builder.append(Util.join(missingDependencies, ", "));
builder.append(" doesn't exist");
throw new IOException(builder.toString());
}
// add the optional dependencies that exists
for (Dependency d : optionalDependencies) {
if (pluginManager.getPlugin(d.shortName) != null)
dependencies.add(d);
}
}
/**
* Used to load classes from dependency plugins.
*/
......
/*
* The MIT License
*
* Copyright (c) 2010, Kohsuke Kawaguchi
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package hudson;
import hudson.model.Hudson;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* {@link PluginManager}
*
* @author Kohsuke Kawaguchi
*/
public class LocalPluginManager extends PluginManager {
private final Hudson hudson;
public LocalPluginManager(Hudson hudson) {
super(hudson.servletContext, new File(hudson.getRootDir(),"plugins"));
this.hudson = hudson;
}
/**
* If the war file has any "/WEB-INF/plugins/*.hpi", extract them into the plugin directory.
*
* @return
* File names of the bundled plugins. Like {"ssh-slaves.hpi","subvesrion.hpi"}
*/
@Override
protected Collection<String> loadBundledPlugins() {
// this is used in tests, when we want to override the default bundled plugins with .hpl versions
if (System.getProperty("hudson.bundled.plugins") != null) {
return Collections.emptySet();
}
Set<String> names = new HashSet<String>();
for( String path : Util.fixNull((Set<String>)hudson.servletContext.getResourcePaths("/WEB-INF/plugins"))) {
String fileName = path.substring(path.lastIndexOf('/')+1);
if(fileName.length()==0) {
// see http://www.nabble.com/404-Not-Found-error-when-clicking-on-help-td24508544.html
// I suspect some containers are returning directory names.
continue;
}
try {
names.add(fileName);
URL url = hudson.servletContext.getResource(path);
copyBundledPlugin(url, fileName);
} catch (IOException e) {
LOGGER.log(Level.SEVERE, "Failed to extract the bundled plugin "+fileName,e);
}
}
return names;
}
private static final Logger LOGGER = Logger.getLogger(LocalPluginManager.class.getName());
}
/*
* The MIT License
*
* Copyright (c) 2010, Kohsuke Kawaguchi
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package hudson;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* Type-safe instance map.
*
* @author Kohsuke Kawaguchi
*/
public class Lookup {
private final Map<Class,Object> data = new ConcurrentHashMap<Class,Object>();
public <T> T get(Class<T> type) {
return type.cast(data.get(type));
}
public <T> T set(Class<T> type, T instance) {
return type.cast(data.put(type,instance));
}
}
......@@ -23,6 +23,7 @@
*/
package hudson;
import hudson.PluginManager.PluginInstanceStore;
import hudson.model.Hudson;
import hudson.model.UpdateCenter;
import hudson.model.UpdateSite;
......@@ -34,6 +35,7 @@ import java.io.IOException;
import java.io.OutputStream;
import java.io.Closeable;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.jar.Manifest;
import java.util.logging.Logger;
......@@ -67,16 +69,15 @@ import org.kohsuke.stapler.HttpResponses;
*/
public final class PluginWrapper {
/**
* Plugin manifest.
* Contains description of the plugin.
* {@link PluginManager} to which this belongs to.
*/
private final Manifest manifest;
public final PluginManager parent;
/**
* Loaded plugin instance.
* Null if disabled.
* Plugin manifest.
* Contains description of the plugin.
*/
private Plugin plugin;
private final Manifest manifest;
/**
* {@link ClassLoader} for loading classes from this plugin.
......@@ -171,9 +172,10 @@ public final class PluginWrapper {
* @param dependencies a list of mandatory dependencies
* @param optionalDependencies a list of optional dependencies
*/
public PluginWrapper(File archive, Manifest manifest, URL baseResourceURL,
public PluginWrapper(PluginManager parent, File archive, Manifest manifest, URL baseResourceURL,
ClassLoader classLoader, File disableFile,
List<Dependency> dependencies, List<Dependency> optionalDependencies) {
this.parent = parent;
this.manifest = manifest;
this.shortName = computeShortName(manifest, archive);
this.baseResourceURL = baseResourceURL;
......@@ -239,7 +241,7 @@ public final class PluginWrapper {
* Gets the instance of {@link Plugin} contributed by this plugin.
*/
public Plugin getPlugin() {
return plugin;
return Hudson.lookup(PluginInstanceStore.class).store.get(this);
}
/**
......@@ -311,17 +313,19 @@ public final class PluginWrapper {
/**
* Terminates the plugin.
*/
void stop() {
public void stop() {
LOGGER.info("Stopping "+shortName);
try {
plugin.stop();
getPlugin().stop();
} catch(Throwable t) {
LOGGER.log(WARNING, "Failed to shut down "+shortName, t);
}
// Work around a bug in commons-logging.
// See http://www.szegedi.org/articles/memleak.html
LogFactory.release(classLoader);
}
public void releaseClassLoader() {
if (classLoader instanceof Closeable)
try {
((Closeable) classLoader).close();
......@@ -371,7 +375,7 @@ public final class PluginWrapper {
}
public void setPlugin(Plugin plugin) {
this.plugin = plugin;
Hudson.lookup(PluginInstanceStore.class).store.put(this,plugin);
plugin.wrapper = this;
}
......@@ -379,6 +383,30 @@ public final class PluginWrapper {
return manifest.getMainAttributes().getValue("Plugin-Class");
}
/**
* Makes sure that all the dependencies exist, and then accept optional dependencies
* as real dependencies.
*
* @throws IOException
* thrown if one or several mandatory dependencies doesn't exists.
*/
/*package*/ void resolvePluginDependencies() throws IOException {
List<String> missingDependencies = new ArrayList<String>();
// make sure dependencies exist
for (Dependency d : dependencies) {
if (parent.getPlugin(d.shortName) == null)
missingDependencies.add(d.toString());
}
if (!missingDependencies.isEmpty())
throw new IOException("Dependency "+Util.join(missingDependencies, ", ")+" doesn't exist");
// add the optional dependencies that exists
for (Dependency d : optionalDependencies) {
if (parent.getPlugin(d.shortName) != null)
dependencies.add(d);
}
}
/**
* If the plugin has {@link #getUpdateInfo() an update},
* returns the {@link UpdateSite.Plugin} object.
......
......@@ -57,6 +57,7 @@ public enum InitMilestone implements Milestone {
* By this milestone, all plugin metadata are loaded and its classloader set up.
*/
PLUGINS_PREPARED("Prepared all plugins"),
/**
* By this milestone, all plugins start executing, all extension points loaded, descriptors instantiated
* and loaded.
......@@ -67,10 +68,12 @@ public enum InitMilestone implements Milestone {
* require all the classes from all the plugins to be loadable.
*/
PLUGINS_STARTED("Started all plugins"),
/**
* By this milestone, all jobs and their build records are loaded from disk.
*/
JOB_LOADED("Loaded all jobs"),
/**
* The very last milestone
*
......
......@@ -35,6 +35,8 @@ import hudson.FilePath;
import hudson.Functions;
import hudson.Launcher;
import hudson.Launcher.LocalLauncher;
import hudson.LocalPluginManager;
import hudson.Lookup;
import hudson.Plugin;
import hudson.PluginManager;
import hudson.PluginWrapper;
......@@ -145,8 +147,6 @@ import org.jvnet.hudson.reactor.Milestone;
import org.jvnet.hudson.reactor.Reactor;
import org.jvnet.hudson.reactor.ReactorListener;
import org.jvnet.hudson.reactor.TaskGraphBuilder.Handle;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.DoNotUse;
import org.kohsuke.stapler.Ancestor;
import org.kohsuke.stapler.HttpRedirect;
import org.kohsuke.stapler.HttpResponse;
......@@ -231,6 +231,11 @@ import java.util.regex.Pattern;
public final class Hudson extends Node implements ItemGroup<TopLevelItem>, StaplerProxy, StaplerFallback, ViewGroup, AccessControlled, DescriptorByNameOwner {
private transient final Queue queue;
/**
* Stores various objects scoped to {@link Hudson}.
*/
public transient final Lookup lookup = new Lookup();
/**
* {@link Computer}s in this Hudson system. Read-only.
*/
......@@ -533,6 +538,14 @@ public final class Hudson extends Node implements ItemGroup<TopLevelItem>, Stapl
private transient final LogRecorderManager log = new LogRecorderManager();
public Hudson(File root, ServletContext context) throws IOException, InterruptedException, ReactorException {
this(root,context,null);
}
/**
* @param pluginManager
* If non-null, use existing plugin manager. Otherwise create a new one.
*/
public Hudson(File root, ServletContext context, PluginManager pluginManager) throws IOException, InterruptedException, ReactorException {
// As hudson is starting, grant this process full control
SecurityContextHolder.getContext().setAuthentication(ACL.SYSTEM);
try {
......@@ -576,7 +589,11 @@ public final class Hudson extends Node implements ItemGroup<TopLevelItem>, Stapl
LOGGER.log(SEVERE, "Failed to load proxy configuration", e);
}
pluginManager = new PluginManager(context);
if (pluginManager==null)
pluginManager = new LocalPluginManager(this);
this.pluginManager = pluginManager;
// JSON binding needs to be able to see all the classes from all the plugins
WebApp.get(servletContext).setClassLoader(pluginManager.uberClassLoader);
adjuncts = new AdjunctManager(servletContext, pluginManager.uberClassLoader,"adjuncts/"+VERSION_HASH);
......@@ -3412,6 +3429,13 @@ public final class Hudson extends Node implements ItemGroup<TopLevelItem>, Stapl
public static final LocalChannel localChannel = new LocalChannel(threadPoolForRemoting);
}
/**
* Shortcut for {@code Hudson.getInstance().lookup.get(type)}
*/
public static <T> T lookup(Class<T> type) {
return Hudson.getInstance().lookup.get(type);
}
/**
* @deprecated since 2007-12-18.
* Use {@link #checkPermission(Permission)}
......
......@@ -30,17 +30,20 @@ import hudson.CloseProofOutputStream;
import hudson.FilePath;
import hudson.Functions;
import hudson.Main;
import hudson.PluginManager;
import hudson.WebAppMain;
import hudson.EnvVars;
import hudson.ExtensionList;
import hudson.DescriptorExtensionList;
import hudson.Util;
import hudson.model.AbstractBuild;
import hudson.model.Computer;
import hudson.model.Executor;
import hudson.model.*;
import hudson.model.Queue.Executable;
import hudson.security.AbstractPasswordBasedSecurityRealm;
import hudson.security.GroupDetails;
import hudson.security.SecurityRealm;
import hudson.slaves.ComputerLauncher;
import hudson.tools.ToolProperty;
import hudson.remoting.Which;
import hudson.Launcher.LocalLauncher;
......@@ -97,7 +100,6 @@ import javax.servlet.ServletContextEvent;
import junit.framework.TestCase;
import net.sourceforge.htmlunit.corejs.javascript.Context;
import net.sourceforge.htmlunit.corejs.javascript.ContextFactory;
import net.sourceforge.htmlunit.corejs.javascript.ContextFactory.Listener;
import org.acegisecurity.AuthenticationException;
import org.acegisecurity.BadCredentialsException;
......@@ -112,6 +114,7 @@ import org.apache.maven.artifact.resolver.AbstractArtifactResolutionException;
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.Dispatcher;
import org.kohsuke.stapler.MetaClass;
......@@ -192,6 +195,14 @@ public abstract class HudsonTestCase extends TestCase implements RootAction {
*/
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;
protected HudsonTestCase(String name) {
super(name);
......@@ -325,7 +336,7 @@ public abstract class HudsonTestCase extends TestCase implements RootAction {
File home = homeLoader.allocate();
for (Runner r : recipes)
r.decorateHome(this,home);
return new Hudson(home, createWebServer());
return new Hudson(home, createWebServer(), useLocalPluginManager ? null : TestPluginManager.INSTANCE);
}
/**
......@@ -1027,79 +1038,76 @@ public abstract class HudsonTestCase extends TestCase implements RootAction {
* packaging is <tt>hpi</tt>.
*/
protected void recipeLoadCurrentPlugin() throws Exception {
Enumeration<URL> e = getClass().getClassLoader().getResources("the.hpl");
final Enumeration<URL> e = getClass().getClassLoader().getResources("the.hpl");
if(!e.hasMoreElements()) return; // nope
final URL hpl = e.nextElement();
if(e.hasMoreElements()) {
// this happens if one plugin produces a test jar and another plugin depends on it.
// I can't think of a good way to make this work, so for now, just detect that and report an error.
URL hpl2 = e.nextElement();
throw new Error("We have both "+hpl+" and "+hpl2);
}
recipes.add(new Runner() {
@Override
public void decorateHome(HudsonTestCase testCase, File home) throws Exception {
// make the plugin itself available
Manifest m = new Manifest(hpl.openStream());
String shortName = m.getMainAttributes().getValue("Short-Name");
if(shortName==null)
throw new Error(hpl+" doesn't have the Short-Name attribute");
FileUtils.copyURLToFile(hpl,new File(home,"plugins/"+shortName+".hpl"));
// make dependency plugins available
// TODO: probably better to read POM, but where to read from?
// TODO: this doesn't handle transitive dependencies
// Tom: plugins are now searched on the classpath first. They should be available on
// the compile or test classpath. As a backup, we do a best-effort lookup in the Maven repository
// For transitive dependencies, we could evaluate Plugin-Dependencies transitively.
String dependencies = m.getMainAttributes().getValue("Plugin-Dependencies");
if(dependencies!=null) {
MavenEmbedder embedder = new MavenEmbedder(null);
embedder.setClassLoader(getClass().getClassLoader());
embedder.start();
for( String dep : dependencies.split(",")) {
String[] tokens = dep.split(":");
String artifactId = tokens[0];
String version = tokens[1];
File dependencyJar=null;
// need to search multiple group IDs
// TODO: extend manifest to include groupID:artifactID:version
Exception resolutionError=null;
for (String groupId : new String[]{"org.jvnet.hudson.plugins","org.jvnet.hudson.main"}) {
// first try to find it on the classpath.
// this takes advantage of Maven POM located in POM
URL dependencyPomResource = getClass().getResource("/META-INF/maven/"+groupId+"/"+artifactId+"/pom.xml");
if (dependencyPomResource != null) {
// found it
dependencyJar = Which.jarFile(dependencyPomResource);
break;
} else {
Artifact a;
a = embedder.createArtifact(groupId, artifactId, version, "compile"/*doesn't matter*/, "hpi");
try {
embedder.resolve(a, Arrays.asList(embedder.createRepository("http://maven.glassfish.org/content/groups/public/","repo")),embedder.getLocalRepository());
dependencyJar = a.getFile();
} catch (AbstractArtifactResolutionException x) {
// could be a wrong groupId
resolutionError = x;
while (e.hasMoreElements()) {
final URL hpl = e.nextElement();
// make the plugin itself available
Manifest m = new Manifest(hpl.openStream());
String shortName = m.getMainAttributes().getValue("Short-Name");
if(shortName==null)
throw new Error(hpl+" doesn't have the Short-Name attribute");
FileUtils.copyURLToFile(hpl,new File(home,"plugins/"+shortName+".hpl"));
// make dependency plugins available
// TODO: probably better to read POM, but where to read from?
// TODO: this doesn't handle transitive dependencies
// Tom: plugins are now searched on the classpath first. They should be available on
// the compile or test classpath. As a backup, we do a best-effort lookup in the Maven repository
// For transitive dependencies, we could evaluate Plugin-Dependencies transitively.
String dependencies = m.getMainAttributes().getValue("Plugin-Dependencies");
if(dependencies!=null) {
MavenEmbedder embedder = new MavenEmbedder(null);
embedder.setClassLoader(getClass().getClassLoader());
embedder.start();
for( String dep : dependencies.split(",")) {
String[] tokens = dep.split(":");
String artifactId = tokens[0];
String version = tokens[1];
File dependencyJar=null;
// need to search multiple group IDs
// TODO: extend manifest to include groupID:artifactID:version
Exception resolutionError=null;
for (String groupId : new String[]{"org.jvnet.hudson.plugins","org.jvnet.hudson.main"}) {
// first try to find it on the classpath.
// this takes advantage of Maven POM located in POM
URL dependencyPomResource = getClass().getResource("/META-INF/maven/"+groupId+"/"+artifactId+"/pom.xml");
if (dependencyPomResource != null) {
// found it
dependencyJar = Which.jarFile(dependencyPomResource);
break;
} else {
Artifact a;
a = embedder.createArtifact(groupId, artifactId, version, "compile"/*doesn't matter*/, "hpi");
try {
embedder.resolve(a, Arrays.asList(embedder.createRepository("http://maven.glassfish.org/content/groups/public/","repo")),embedder.getLocalRepository());
dependencyJar = a.getFile();
} catch (AbstractArtifactResolutionException x) {
// could be a wrong groupId
resolutionError = x;
}
}
}
}
if(dependencyJar==null)
throw new Exception("Failed to resolve plugin: "+dep,resolutionError);
if(dependencyJar==null)
throw new Exception("Failed to resolve plugin: "+dep,resolutionError);
File dst = new File(home, "plugins/" + artifactId + ".hpi");
if(!dst.exists() || dst.lastModified()!=dependencyJar.lastModified()) {
FileUtils.copyFile(dependencyJar, dst);
File dst = new File(home, "plugins/" + artifactId + ".hpi");
if(!dst.exists() || dst.lastModified()!=dependencyJar.lastModified()) {
FileUtils.copyFile(dependencyJar, dst);
}
}
embedder.stop();
}
embedder.stop();
}
}
});
......
/*
* The MIT License
*
* Copyright (c) 2010, Kohsuke Kawaguchi
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.jvnet.hudson.test;
import hudson.Plugin;
import hudson.PluginManager;
import hudson.PluginWrapper;
import hudson.Util;
import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* {@link PluginManager} to speed up unit tests.
*
* <p>
* Instead of loading every plugin for every test case, this allows them to reuse a single plugin manager.
*
* <p>
* TODO: {@link Plugin} start/stop/postInitialize invocation semantics gets different. Perhaps
*
* @author Kohsuke Kawaguchi
* @see HudsonTestCase#useLocalPluginManager
*/
public class TestPluginManager extends PluginManager {
public static final PluginManager INSTANCE;
private TestPluginManager() throws IOException {
// TestPluginManager outlives a Jetty server, so can't pass in ServletContext.
super(null, Util.createTempDir());
}
@Override
protected Collection<String> loadBundledPlugins() throws Exception {
Set<String> names = new HashSet<String>();
File[] children = new File(WarExploder.getExplodedDir(),"WEB-INF/plugins").listFiles();
for (File child : children) {
try {
names.add(child.getName());
copyBundledPlugin(child.toURI().toURL(), child.getName());
} catch (IOException e) {
LOGGER.log(Level.SEVERE, "Failed to extract the bundled plugin "+child,e);
}
}
return names;
}
@Override
public void stop() {
for (PluginWrapper p : activePlugins)
p.stop();
}
private static final Logger LOGGER = Logger.getLogger(TestPluginManager.class.getName());
static {
try {
INSTANCE = new TestPluginManager();
} catch (IOException e) {
throw new Error(e);
}
}
}
......@@ -58,6 +58,7 @@ public @interface WithPlugin {
@Override
public void setup(HudsonTestCase testCase, WithPlugin recipe) throws Exception {
a = recipe;
testCase.useLocalPluginManager = true;
}
@Override
......
......@@ -35,6 +35,12 @@ import java.io.File;
* @author Kohsuke Kawaguchi
*/
public class PluginManagerTest extends HudsonTestCase {
@Override
protected void setUp() throws Exception {
useLocalPluginManager = true;
super.setUp();
}
/**
* Manual submission form.
*/
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册