提交 49ace474 编写于 作者: K kohsuke

Added automatic tool installation for Ant.

- Most of the installer logic is made reusable in DownloadFromUrlInstaller.
- ToolDescriptor can now contribute global.jelly to the system configuration page.

git-svn-id: https://hudson.dev.java.net/svn/hudson/trunk/hudson/main@18260 71c3de6d-444a-0410-be80-ed276b4c234a
上级 46b540e5
......@@ -5,6 +5,7 @@ import hudson.ExtensionList;
import hudson.ExtensionPoint;
import hudson.util.QuotedStringTokenizer;
import hudson.util.TextFile;
import hudson.util.TimeUnit2;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.Stapler;
......@@ -39,7 +40,7 @@ public class DownloadService extends PageDecorator {
long now = System.currentTimeMillis();
for (Downloadable d : Downloadable.all()) {
if(d.getDue()<now) {
buf.append("<script>downloadService.download(")
buf.append("<script>downloadService.download(")
.append(QuotedStringTokenizer.quote(d.getId()))
.append(',')
.append(QuotedStringTokenizer.quote(d.getUrl()))
......@@ -75,7 +76,7 @@ public class DownloadService extends PageDecorator {
*
* @since 1.305
*/
public static abstract class Downloadable implements ExtensionPoint {
public static class Downloadable implements ExtensionPoint {
private final String id;
private final String url;
private final long interval;
......@@ -91,12 +92,27 @@ public class DownloadService extends PageDecorator {
* For security and privacy reasons, we don't allow the retrieval
* from random locations.
*/
protected Downloadable(String id, String url, long interval) {
public Downloadable(String id, String url, long interval) {
this.id = id;
this.url = url;
this.interval = interval;
}
/**
* Uses the class name as an ID.
*/
public Downloadable(Class id) {
this(id.getName().replace('$','.'));
}
public Downloadable(String id) {
this(id,id+".json");
}
public Downloadable(String id, String url) {
this(id,url,TimeUnit2.DAYS.toMillis(1));
}
public String getId() {
return id;
}
......@@ -105,7 +121,7 @@ public class DownloadService extends PageDecorator {
* URL to download.
*/
public String getUrl() {
return Hudson.getInstance().getUpdateCenter().getUrl()+url;
return Hudson.getInstance().getUpdateCenter().getUrl()+"updates/"+url;
}
/**
......@@ -167,6 +183,17 @@ public class DownloadService extends PageDecorator {
return Hudson.getInstance().getExtensionList(Downloadable.class);
}
/**
* Returns the {@link Downloadable} that has the given ID.
*/
public static Downloadable get(String id) {
for (Downloadable d : all()) {
if(d.id.equals(id))
return d;
}
return null;
}
private static final Logger LOGGER = Logger.getLogger(Downloadable.class.getName());
}
}
......@@ -45,6 +45,8 @@ import hudson.ExtensionPoint;
import hudson.DescriptorExtensionList;
import hudson.ExtensionListView;
import hudson.Extension;
import hudson.tools.ToolInstallation;
import hudson.tools.ToolDescriptor;
import hudson.cli.CliEntryPoint;
import hudson.cli.CLICommand;
import hudson.cli.HelpCommand;
......@@ -2209,6 +2211,9 @@ public final class Hudson extends Node implements ItemGroup<TopLevelItem>, Stapl
for( PageDecorator d : PageDecorator.all() )
result &= configureDescriptor(req,json,d);
for( ToolDescriptor d : ToolInstallation.all() )
result &= configureDescriptor(req,json,d);
for( JSONObject o : StructuredForm.toList(json,"plugin"))
pluginManager.getPlugin(o.getString("name")).getPlugin().configure(req, o);
......
......@@ -229,5 +229,5 @@ public class UsageStatistics extends PageDecorator {
private static final long DAY = DAYS.toMillis(1);
public static boolean DISABLED = Boolean.getBoolean(UsageStatistics.class.getName()+".disable");
public static boolean DISABLED = Boolean.getBoolean(UsageStatistics.class.getName()+".disabled");
}
......@@ -41,6 +41,9 @@ import hudson.remoting.Callable;
import hudson.slaves.NodeSpecific;
import hudson.tools.ToolDescriptor;
import hudson.tools.ToolInstallation;
import hudson.tools.DownloadFromUrlInstaller;
import hudson.tools.ToolInstaller;
import hudson.tools.ToolProperty;
import hudson.util.ArgumentListBuilder;
import hudson.util.VariableResolver;
import hudson.util.FormValidation;
......@@ -53,6 +56,8 @@ import java.io.File;
import java.io.IOException;
import java.util.Map;
import java.util.Properties;
import java.util.List;
import java.util.Collections;
/**
* Ant launcher.
......@@ -185,7 +190,7 @@ public class Ant extends Builder {
args.addTokenized(targets.replaceAll("[\t\r\n]+"," "));
if(ai!=null)
env.put("ANT_HOME",ai.getAntHome());
env.put("ANT_HOME",ai.getHome());
if(antOpts!=null)
env.put("ANT_OPTS",env.expand(antOpts));
......@@ -253,14 +258,21 @@ public class Ant extends Builder {
load();
}
public boolean isApplicable(Class<? extends AbstractProject> jobType) {
return true;
}
protected DescriptorImpl(Class<? extends Ant> clazz) {
super(clazz);
}
/**
* Obtains the {@link AntInstallation.DescriptorImpl} instance.
*/
public AntInstallation.DescriptorImpl getToolDescriptor() {
return ToolInstallation.all().get(AntInstallation.DescriptorImpl.class);
}
public boolean isApplicable(Class<? extends AbstractProject> jobType) {
return true;
}
protected void convert(Map<String,Object> oldPropertyBag) {
if(oldPropertyBag.containsKey("installations"))
installations = (AntInstallation[]) oldPropertyBag.get("installations");
......@@ -278,56 +290,36 @@ public class Ant extends Builder {
return installations;
}
@Override
public boolean configure(StaplerRequest req, JSONObject json) throws FormException {
installations = req.bindJSONToList(
AntInstallation.class, json.get("ant")).toArray(new AntInstallation[0]);
save();
return true;
}
public Ant newInstance(StaplerRequest req, JSONObject formData) throws FormException {
return (Ant)req.bindJSON(clazz,formData);
}
//
// web methods
//
/**
* Checks if the ANT_HOME is valid.
*/
public FormValidation doCheckAntHome(@QueryParameter File value) {
// this can be used to check the existence of a file on the server, so needs to be protected
if(!Hudson.getInstance().hasPermission(Hudson.ADMINISTER))
return FormValidation.ok();
if(!value.isDirectory())
return FormValidation.error(Messages.Ant_NotADirectory(value));
File antJar = new File(value,"lib/ant.jar");
if(!antJar.exists())
return FormValidation.error(Messages.Ant_NotAntDirectory(value));
return FormValidation.ok();
}
public void setInstallations(AntInstallation... antInstallations) {
this.installations = antInstallations;
save();
}
}
/**
* Represents the Ant installation on the system.
*/
public static final class AntInstallation extends ToolInstallation implements
EnvironmentSpecific<AntInstallation>, NodeSpecific<AntInstallation> {
// to remain backward compatible with earlier Hudson that stored this field here.
private final String antHome;
@DataBoundConstructor
public AntInstallation(String name, String home, List<? extends ToolProperty<?>> properties) {
super(name, launderHome(home), properties);
this.antHome = super.getHome();
}
/**
* @deprecated as of 1.308
* Use {@link #AntInstallation(String, String, List)}
*/
public AntInstallation(String name, String home) {
super(name, launderHome(home));
if(home.endsWith("/") || home.endsWith("\\"))
// see https://issues.apache.org/bugzilla/show_bug.cgi?id=26947
// Ant doesn't like the trailing slash, especially on Windows
home = home.substring(0,home.length()-1);
this.antHome = home;
this(name,home,Collections.<ToolProperty<?>>emptyList());
}
private static String launderHome(String home) {
......@@ -342,6 +334,8 @@ public class Ant extends Builder {
/**
* install directory.
*
* @deprecated as of 1.307. Use {@link #getHome()}.
*/
public String getAntHome() {
return getHome();
......@@ -388,11 +382,11 @@ public class Ant extends Builder {
private static final long serialVersionUID = 1L;
public AntInstallation forEnvironment(EnvVars environment) {
return new AntInstallation(getName(), environment.expand(antHome));
return new AntInstallation(getName(), environment.expand(antHome), getProperties().toList());
}
public AntInstallation forNode(Node node, TaskListener log) throws IOException, InterruptedException {
return new AntInstallation(getName(), translateFor(node, log));
return new AntInstallation(getName(), translateFor(node, log), getProperties().toList());
}
@Extension
......@@ -403,6 +397,7 @@ public class Ant extends Builder {
return "Ant";
}
// for compatibility reasons, the persistence is done by Ant.DescriptorImpl
@Override
public AntInstallation[] getInstallations() {
return Hudson.getInstance().getDescriptorByType(Ant.DescriptorImpl.class).getInstallations();
......@@ -412,7 +407,58 @@ public class Ant extends Builder {
public void setInstallations(AntInstallation... installations) {
Hudson.getInstance().getDescriptorByType(Ant.DescriptorImpl.class).setInstallations(installations);
}
@Override
public List<? extends ToolInstaller> getDefaultInstallers() {
return Collections.singletonList(new AntInstaller(null));
}
@Override
public boolean configure(StaplerRequest req, JSONObject json) throws FormException {
setInstallations(req.bindJSONToList(
AntInstallation.class, json.get("ant")).toArray(new AntInstallation[0]));
return true;
}
/**
* Checks if the ANT_HOME is valid.
*/
public FormValidation doCheckHome(@QueryParameter File value) {
// this can be used to check the existence of a file on the server, so needs to be protected
if(!Hudson.getInstance().hasPermission(Hudson.ADMINISTER))
return FormValidation.ok();
if(!value.isDirectory())
return FormValidation.error(Messages.Ant_NotADirectory(value));
File antJar = new File(value,"lib/ant.jar");
if(!antJar.exists())
return FormValidation.error(Messages.Ant_NotAntDirectory(value));
return FormValidation.ok();
}
}
}
/**
* Automatic Ant installer from apache.org.
*/
public static class AntInstaller extends DownloadFromUrlInstaller {
@DataBoundConstructor
public AntInstaller(String id) {
super(id);
}
@Extension
public static final class DescriptorImpl extends DownloadFromUrlInstaller.DescriptorImpl<AntInstaller> {
public String getDisplayName() {
return "Install from Apache";
}
}
@Override
public boolean isApplicable(Class<? extends ToolInstallation> toolType) {
return toolType==AntInstallation.class;
}
}
}
}
package hudson.tools;
import hudson.FilePath;
import hudson.model.DownloadService.Downloadable;
import hudson.model.Node;
import hudson.model.TaskListener;
import net.sf.json.JSONObject;
import org.kohsuke.stapler.DataBoundConstructor;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.net.URL;
/**
* Partial convenience implementation of {@link ToolInstaller} that just downloads
* an archive from the URL and extracts it.
*
* @author Kohsuke Kawaguchi
* @since 1.308
*/
public abstract class DownloadFromUrlInstaller extends ToolInstaller {
public final String id;
@DataBoundConstructor
protected DownloadFromUrlInstaller(String id) {
// this installer implementation is designed for platform independent binary,
// and as such we don't provide the label support
super(null);
this.id = id;
}
/**
* Checks if the specified expected location already contains the installed version of the tool.
*
* This check needs to run fairly efficiently. The current implementation uses the souce URL of {@link Installable},
* based on the assumption that released bits do not change its content.
*/
protected boolean isUpToDate(FilePath expectedLocation, Installable i, ToolInstallation tool, Node node, TaskListener log) throws IOException, InterruptedException {
FilePath marker = expectedLocation.child(".installedFrom");
return marker.exists() && marker.readToString().equals(i.url);
}
/**
* Gets the {@link Installable} identified by {@link #id}.
*
* @return null if no such ID is found.
*/
public Installable getInstallable() throws IOException {
for (Installable i : ((DescriptorImpl<?>)getDescriptor()).getInstallables())
if(id.equals(i.id))
return i;
return null;
}
public FilePath performInstallation(ToolInstallation tool, Node node, TaskListener log) throws IOException, InterruptedException {
FilePath expected = node.createPath(tool.getHome());
Installable inst = getInstallable();
if(inst==null) {
log.getLogger().println("Invalid tool ID "+id);
return expected;
}
if(isUpToDate(expected,inst,tool,node,log))
return expected;
if(expected.installIfNecessaryFrom(new URL(inst.url), log, "Unpacking " + inst.url + " to " + expected + " on " + node.getDisplayName())) {
expected.child(".timestamp").delete(); // we don't use the timestamp
FilePath base = findPullUpDirectory(expected);
if(base!=null && base!=expected)
base.moveAllChildrenTo(expected);
// leave a record for the next up-to-date check
expected.child(".installedFrom").write(inst.url,"UTF-8");
}
return expected;
}
/**
* Often an archive contains an extra top-level directory that's unnecessary when extracted on the disk
* into the expected location. If your installation sources provide that kind of archives, override
* this method to find the real root location.
*
* <p>
* The caller will "pull up" the discovered real root by throw away the intermediate directory,
* so that the user-configured "tool home" directory contains the right files.
*
* <p>
* The default implementation applies some heuristics to auto-determine if the pull up is necessary.
* This should work for typical archive files.
*
* @param root
* The directory that contains the extracted archive. This directory contains nothing but the
* extracted archive. For example, if the user installed
* http://archive.apache.org/dist/ant/binaries/jakarta-ant-1.1.zip , this directory would contain
* a single directory "jakarta-ant".
*
* @return
* Return the real top directory inside {@code root} that contains the meat. In the above example,
* <tt>root.child("jakarta-ant")</tt> should be returned. If there's no directory to pull up,
* return null.
*/
protected FilePath findPullUpDirectory(FilePath root) throws IOException, InterruptedException {
// if the directory just contains one directory and that alone, assume that's the pull up subject
// otherwise leave it as is.
List<FilePath> children = root.list();
if(children.size()!=1) return null;
if(children.get(0).isDirectory())
return children.get(0);
return null;
}
public static abstract class DescriptorImpl<T extends DownloadFromUrlInstaller> extends ToolInstallerDescriptor<T> {
protected DescriptorImpl() {
Downloadable.all().add(createDownloadable());
}
protected Downloadable createDownloadable() {
return new Downloadable(getId());
}
/**
* This ID needs to be unique, and needs to match the ID token in the JSON update file.
* <p>
* By default we use the fully-qualified class name of the {@link DownloadFromUrlInstaller} subtype.
*/
protected String getId() {
return clazz.getName().replace('$','.');
}
/**
* List of installable tools.
*
* <p>
* The UI uses this information to populate the drop-down. Subtypes can override this method
* if it wants to change the way the list is filled.
*
* @return never null.
*/
public List<? extends Installable> getInstallables() throws IOException {
JSONObject d = Downloadable.get(getId()).getData();
if(d==null) return Collections.emptyList();
return Arrays.asList(((InstallableList)JSONObject.toBean(d,InstallableList.class)).list);
}
}
/**
* Used for JSON databinding to parse the obtained list.
*/
public static class InstallableList {
// initialize with an empty array just in case JSON doesn't have the list field (which shouldn't happen.)
public Installable[] list = new Installable[0];
}
public static class Installable {
/**
* Used internally to uniquely identify the name.
*/
public String id;
/**
* This is the human readable name.
*/
public String name;
/**
* URL.
*/
public String url;
}
}
......@@ -34,15 +34,12 @@ import hudson.Extension;
import hudson.FilePath;
import hudson.Util;
import hudson.util.FormValidation;
import hudson.util.TimeUnit2;
import hudson.util.ArgumentListBuilder;
import hudson.FilePath.FileCallable;
import hudson.model.Node;
import hudson.model.TaskListener;
import hudson.model.DownloadService.Downloadable;
import static hudson.tools.JDKInstaller.Preference.*;
import hudson.remoting.Callable;
import hudson.remoting.VirtualChannel;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.QueryParameter;
import org.apache.commons.io.IOUtils;
......@@ -128,11 +125,8 @@ public class JDKInstaller extends ToolInstaller {
if(paths.size()!=1)
throw new AbortException("Failed to find the extracted JDKs: "+paths);
paths.get(0).act(PULLUP_DIRECTORY);
// clean up
paths.get(0).delete();
// remove the intermediate directory
paths.get(0).moveAllChildrenTo(expectedLocation);
break;
case WINDOWS:
/*
......@@ -218,21 +212,6 @@ public class JDKInstaller extends ToolInstaller {
}
};
/**
* Moves all the contents of this directory into ".."
*/
private static final FileCallable<Void> PULLUP_DIRECTORY = new FileCallable<Void>() {
public Void invoke(File f, VirtualChannel channel) throws IOException {
File p = f.getParentFile();
for(File child : f.listFiles()) {
File target = new File(p, child.getName());
if(!child.renameTo(target))
throw new IOException("Failed to rename "+child+" to "+target);
}
return null;
}
};
/**
* Performs a license click through and obtains the one-time URL for downloading bits.
*
......@@ -446,7 +425,7 @@ public class JDKInstaller extends ToolInstaller {
@Extension
public static final class JDKList extends Downloadable {
public JDKList() {
super(JDKInstaller.class.getName(), "jdk.json", TimeUnit2.DAYS.toMillis(1));
super(JDKInstaller.class);
}
public JDKFamilyList toList() throws IOException {
......
......@@ -122,7 +122,7 @@ THE SOFTWARE.
<d:taglib uri="local">
<!-- display global config pages for the given descriptors -->
<d:tag name="globalConfig">
<j:getStatic var="descriptors" className="${className}" field="${field}" />
<j:invokeStatic var="descriptors" className="${className}" method="all" />
<j:forEach var="idx" begin="0" end="${size(descriptors)-1}">
<j:set var="descriptor" value="${descriptors[idx]}" />
<j:set var="instance" value="${descriptor}" /><!-- this makes the <f:textbox field=.../> work -->
......@@ -133,13 +133,14 @@ THE SOFTWARE.
</d:tag>
</d:taglib>
<local:globalConfig className="hudson.triggers.Triggers" field="TRIGGERS" />
<local:globalConfig className="hudson.tasks.BuildWrappers" field="WRAPPERS" />
<local:globalConfig className="hudson.tasks.BuildStep" field="BUILDERS" />
<local:globalConfig className="hudson.scm.SCMS" field="SCMS" />
<local:globalConfig className="hudson.tasks.BuildStep" field="PUBLISHERS" />
<local:globalConfig className="hudson.model.Jobs" field="PROPERTIES" />
<local:globalConfig className="hudson.model.PageDecorator" field="ALL" />
<local:globalConfig className="hudson.tools.ToolInstallation" />
<local:globalConfig className="hudson.triggers.Trigger" />
<local:globalConfig className="hudson.tasks.BuildWrapper" />
<local:globalConfig className="hudson.tasks.Builder" />
<local:globalConfig className="hudson.scm.SCM" />
<local:globalConfig className="hudson.tasks.Publisher" />
<local:globalConfig className="hudson.model.JobPropertyDescriptor" />
<local:globalConfig className="hudson.model.PageDecorator" />
<j:if test="${!empty(h.getCloudDescriptors())}">
<f:section title="${%Cloud}">
......
<!--
The MIT License
Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi
Copyright (c) 2004-2009, Sun Microsystems, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
......@@ -22,24 +22,28 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
-->
<!--
Config page
-->
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">
<f:section title="Ant">
<f:entry title="${%Ant installation}"
description="${%List of Ant installations on this system}">
<f:repeatable name="ant" var="inst" items="${descriptor.installations}">
<f:repeatable name="ant" var="instance" items="${descriptor.installations}" add="${%Add Ant}">
<table width="100%">
<f:entry title="${%name}">
<f:textbox name="ant.name" value="${inst.name}" />
<f:entry title="${%name}" field="name">
<f:textbox />
</f:entry>
<f:entry title="ANT_HOME">
<f:textbox name="ant.home" value="${inst.antHome}"
checkUrl="'builder/Ant/checkAntHome?value='+escape(this.value)"/>
<f:entry title="ANT_HOME" field="home">
<f:textbox />
</f:entry>
<f:descriptorList descriptors="${descriptor.propertyDescriptors}" field="properties" />
<f:entry title="">
<div align="right">
<f:repeatableDeleteButton />
<f:repeatableDeleteButton value="${%Delete Ant}" />
</div>
</f:entry>
</table>
......
<!--
The MIT License
Copyright (c) 2009, Sun Microsystems, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
-->
<j:jelly xmlns:j="jelly:core" xmlns:f="/lib/form">
<f:entry title="${%Version}" field="id">
<j:choose>
<j:set var="tools" value="${descriptor.installables}"/>
<j:when test="${empty(tools)}">
<!-- if the list is not available, fall back to text box -->
<f:textbox />
</j:when>
<j:otherwise>
<select name="_.id">
<j:forEach var="tool" items="${tools}">
<f:option value="${tool.id}" selected="${tool.id==instance.id}">${tool.name}</f:option>
</j:forEach>
</select>
</j:otherwise>
</j:choose>
</f:entry>
</j:jelly>
......@@ -618,6 +618,14 @@ public abstract class HudsonTestCase extends TestCase {
}
}
/**
* Gets the descriptor instance of the current Hudson by its type.
*/
protected <T extends Descriptor<?>> T get(Class<T> d) {
return hudson.getDescriptorByType(d);
}
//
// recipe methods. Control the test environments.
......
......@@ -83,7 +83,7 @@ public class HudsonTest extends HudsonTestCase {
private void assertAnt(AntInstallation ant, String name, String home) {
assertEquals(ant.getName(),name);
assertEquals(ant.getAntHome(),home);
assertEquals(ant.getHome(),home);
}
private void assertJDK(JDK jdk, String name, String home) {
......
......@@ -25,7 +25,17 @@ package hudson.tasks;
import com.gargoylesoftware.htmlunit.html.HtmlForm;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
import com.gargoylesoftware.htmlunit.html.HtmlButton;
import com.gargoylesoftware.htmlunit.html.HtmlElement;
import com.gargoylesoftware.htmlunit.html.HtmlInput;
import hudson.model.FreeStyleProject;
import hudson.tasks.Ant.AntInstallation;
import hudson.tasks.Ant.AntInstaller;
import hudson.tasks.Ant.AntInstallation.DescriptorImpl;
import hudson.tools.ToolProperty;
import hudson.tools.ToolPropertyDescriptor;
import hudson.tools.InstallSourceProperty;
import hudson.util.DescribableList;
import org.jvnet.hudson.test.HudsonTestCase;
/**
......@@ -53,4 +63,45 @@ public class AntTest extends HudsonTestCase {
assertEquals("c.xml",a.getBuildFile());
assertEquals("d=e",a.getProperties());
}
/**
* Simulates the addition of the new Ant via UI and makes sure it works.
*/
public void testGlobalConfigAjax() throws Exception {
HtmlPage p = new WebClient().goTo("configure");
HtmlForm f = p.getFormByName("config");
HtmlButton b = getButtonByCaption(f, "Add Ant");
b.click();
((HtmlInput)b.selectSingleNode("(preceding::input[@name='_.name'])[last()]")).setValueAttribute("myAnt");
((HtmlInput)b.selectSingleNode("(preceding::input[@name='_.home'])[last()]")).setValueAttribute("/tmp/foo");
submit(f);
verify();
// another submission and verfify it survives a roundtrip
p = new WebClient().goTo("configure");
f = p.getFormByName("config");
submit(f);
verify();
}
private void verify() throws Exception {
AntInstallation[] l = get(DescriptorImpl.class).getInstallations();
assertEquals(1,l.length);
assertEqualBeans(l[0],new AntInstallation("myAnt","/tmp/foo"),"name,home");
// by default we should get the auto installer
DescribableList<ToolProperty<?>,ToolPropertyDescriptor> props = l[0].getProperties();
assertEquals(1,props.size());
InstallSourceProperty isp = props.get(InstallSourceProperty.class);
assertEquals(1,isp.installers.size());
assertNotNull(isp.installers.get(AntInstaller.class));
}
private HtmlButton getButtonByCaption(HtmlForm f, String s) {
for (HtmlElement b : f.getHtmlElementsByTagName("button")) {
if(b.getTextContent().trim().equals(s))
return (HtmlButton)b;
}
return null;
}
}
......@@ -31,7 +31,6 @@ public class ToolLocationTest extends HudsonTestCase {
Ant.AntInstallation[] ant = Hudson.getInstance().getDescriptorByType(Ant.DescriptorImpl.class).getInstallations();
assertEquals(ant.length, 1);
assertEquals(ant[0].getHome(), "foo");
assertEquals(ant[0].getAntHome(), "foo");
assertEquals(ant[0].getName(), "Ant 1");
JDK[] jdk = Hudson.getInstance().getDescriptorByType(JDK.DescriptorImpl.class).getInstallations();
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册