提交 2c6bc734 编写于 作者: T tfennelly

Merge branch 'master' into extensionlist-listener

* master: (61 commits)
  [JENKINS-28384] Noting merge of #1700.
  update changelog: PR 1682 -> issue 28233, fix markup issue
  Remove duplicated 1.613 section from changelog
  [FIXED JENKINS-28384] NPE when Node.toComputer → null.
  Refactor fingerprint classes: Javadoc and annotations
  fixup ce747f94
  Refactor ArgumentListBuilder#toWindowsCommand
  Improve ArgumentListBuilder#toWindowsCommand test coverage
  Forgotten @test annotation
  updated changelog for release
  updated changelog for release
  [maven-release-plugin] prepare release jenkins-1.613
  [maven-release-plugin] prepare for next development iteration
  Report launcher exited before establishing the channel
  Remove erroneous '+ ' from log message.
  Increase visibility of Java 7 requirement
  Expose SlaveComputer#getLogDir() explicitly
  [FIXED JENKINS-28227] Switch to Enblish locale in RunTest#getDurationString to test messages.
  Noting #1591
  Noting #1682
  ...
# Contributing to Jenkins
For information on contributing to Jenkins, check out the https://wiki.jenkins-ci.org/display/JENKINS/contributing and https://wiki.jenkins-ci.org/display/JENKINS/Extend+Jenkins wiki pages over at the official https://wiki.jenkins-ci.org . They will help you get started with contributing to Jenkins.
......@@ -10,7 +10,7 @@ Non-source downloads such as WAR files and several Linux packages can be found o
Our latest and greatest source of Jenkins CI can be found on [GitHub]. Fork us!
# Contributing to Jenkins
For information on contributing to Jenkins, check out the [Contributing] and [Extend Jenkins] wiki pages over at the official [wiki]. They will help you get started with contributing to Jenkins.
Follow [contributing](CONTRIBUTING.md) file.
# News and Website
All information about Jenkins CI can be found on our [website]. Follow us on Twitter [@jenkinsci].
......
......@@ -39,11 +39,11 @@ Some tips:
Help other Jenkins users by letting the community know which releases you've used,
and whether they had any significant issues. <br>
Legend: <br>
<img src="http://ci.jenkins-ci.org/images/16x16/health-80plus.gif" width="16" height="16"
<img src="//ci.jenkins-ci.org/images/16x16/health-80plus.gif" width="16" height="16"
alt="Sunny"> = I use it on my production site without major issues. <br>
<img src="http://ci.jenkins-ci.org/images/16x16/health-40to59.gif" width="16" height="16"
<img src="//ci.jenkins-ci.org/images/16x16/health-40to59.gif" width="16" height="16"
alt="Cloudy"> = I don't recommend it. <br>
<img src="http://ci.jenkins-ci.org/images/16x16/health-00to19.gif" width="16" height="16"
<img src="//ci.jenkins-ci.org/images/16x16/health-00to19.gif" width="16" height="16"
alt="Lightning"> = I tried it but rolled back to a previous version. <br>
View ratings below, and click one of the icons next to your version to provide your input.
</div>
......@@ -55,6 +55,44 @@ Upcoming changes</a>
<!-- Record your changes in the trunk here. -->
<div id="trunk" style="display:none"><!--=TRUNK-BEGIN=-->
<ul class=image>
<li class=bug>
<code>NullPointerException</code> computing load statistics under some conditions.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-28384">issue 28384</a>)
<li class=bug>
Plugins using class loader masking did not work properly over the slave channel.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-27289">issue 27289</a>)
</ul>
</div><!--=TRUNK-END=-->
<h3><a name=v1.613>What's new in 1.613</a> (2015/05/10)</h3>
<ul class=image>
<li class=bug>
Update bundled LDAP plugin in order to restore missing help files
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-28233">issue 28233</a>)
<li class=bug>
hudson.model.Run.getLog() throws IndexOutOfBoundsException when called with maxLines=0
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-27441">issue 27441</a>)
</ul>
<h3><a name=v1.612>What's new in 1.612</a> (2015/05/03)</h3>
<ul class=image>
<li class=rfe>
<strong>Jenkins now requires Java 7</strong>.
(<a href="http://jenkins-ci.org/content/good-bye-java6">announcement</a>,
<a href="https://issues.jenkins-ci.org/browse/JENKINS-28120">issue 28120</a>)
<li class=bug>
Handle AbortException publisher status in the same way as deprecated false boolean status
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-26964">issue 26964</a>)
<li class=bug>
Ensures GlobalSettingsProvider does not swallow fatal exceptions
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-26604">issue 26604</a>)
<li class=rfe>
add datestamp to node-offline message
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-23917">issue 23917</a>)
<li class=rfe>
Larger minimum popup menu height.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-27067">issue 27067</a>)
<li class=bug>
<code>Descriptor.getId</code> fix in 1.610 introduced regressions affecting at least the Performance and NodeJS plugins.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-28093">issue 28093</a> and <a href="https://issues.jenkins-ci.org/browse/JENKINS-28110">issue 28110</a>)
<li class=bug>
Under rare conditions Executor.getProgress() can throw a Division by zero exception.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-28115">issue 28115</a>)
......@@ -63,7 +101,6 @@ Upcoming changes</a>
configured JVM options.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-28111">issue 28111</a>)
</ul>
</div><!--=TRUNK-END=-->
<h3><a name=v1.611>What's new in 1.611</a> (2015/04/26)</h3>
<ul class=image>
<li class=bug>
......
......@@ -5,7 +5,7 @@
<parent>
<groupId>org.jenkins-ci.main</groupId>
<artifactId>pom</artifactId>
<version>1.612-SNAPSHOT</version>
<version>1.614-SNAPSHOT</version>
</parent>
<artifactId>cli</artifactId>
......
......@@ -29,7 +29,7 @@ THE SOFTWARE.
<parent>
<groupId>org.jenkins-ci.main</groupId>
<artifactId>pom</artifactId>
<version>1.612-SNAPSHOT</version>
<version>1.614-SNAPSHOT</version>
</parent>
<artifactId>jenkins-core</artifactId>
......@@ -749,7 +749,7 @@ THE SOFTWARE.
<configuration>
<forkCount>0.5C</forkCount>
<reuseForks>true</reuseForks>
<argLine>-XX:MaxPermSize=128m</argLine>
<argLine>-XX:MaxPermSize=128m -noverify</argLine> <!-- some versions of JDK7/8 causes VerifyError during mock tests: http://code.google.com/p/powermock/issues/detail?id=504 -->
</configuration>
</plugin>
<plugin><!-- set main class -->
......
......@@ -715,6 +715,8 @@ public abstract class AbstractBuild<P extends AbstractProject<P,R>,R extends Abs
*
* @param phase
* true for the post build processing, and false for the final "run after finished" execution.
*
* @return false if any build step failed
*/
protected final boolean performAllBuildSteps(BuildListener listener, Iterable<? extends BuildStep> buildSteps, boolean phase) throws InterruptedException, IOException {
boolean r = true;
......@@ -724,20 +726,33 @@ public abstract class AbstractBuild<P extends AbstractProject<P,R>,R extends Abs
if (!perform(bs,listener)) {
LOGGER.log(Level.FINE, "{0} : {1} failed", new Object[] {AbstractBuild.this, bs});
r = false;
if (phase) {
setResult(Result.FAILURE);
}
}
} catch (Exception e) {
reportError(bs, e, listener, phase);
r = false;
} catch (LinkageError e) {
reportError(bs, e, listener, phase);
r = false;
}
}
return r;
}
private void reportError(BuildStep bs, Throwable e, BuildListener listener, boolean phase) {
String msg = "Publisher " + bs.getClass().getName() + " aborted due to exception";
e.printStackTrace(listener.error(msg));
LOGGER.log(WARNING, msg, e);
final String publisher = ((Publisher) bs).getDescriptor().getDisplayName();
if (e instanceof AbortException) {
LOGGER.log(Level.FINE, "{0} : {1} failed", new Object[] {AbstractBuild.this, publisher});
listener.error("Publisher '" + publisher + "' failed: " + e.getMessage());
} else {
String msg = "Publisher '" + publisher + "' aborted due to exception: ";
e.printStackTrace(listener.error(msg));
LOGGER.log(WARNING, msg, e);
}
if (phase) {
setResult(Result.FAILURE);
}
......
......@@ -271,14 +271,25 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces
/**
* This is where the log from the remote agent goes.
* The method also creates a log directory if required.
* @see #relocateOldLogs()
* @see #getLogDir(), #relocateOldLogs()
*/
public File getLogFile() {
public @Nonnull File getLogFile() {
return new File(getLogDir(),"slave.log");
}
/**
* Directory where rotated slave logs are stored.
*
* The method also creates a log directory if required.
*
* @since 1.613
*/
protected @Nonnull File getLogDir() {
File dir = new File(Jenkins.getInstance().getRootDir(),"logs/slaves/"+nodeName);
if (!dir.exists() && !dir.mkdirs()) {
LOGGER.severe("Failed to create slave log directory " + dir.getAbsolutePath());
}
return new File(dir,"slave.log");
return dir;
}
/**
......
......@@ -918,17 +918,29 @@ public abstract class Descriptor<T extends Describable<T>> implements Saveable {
// Descriptors, so we prefer 'kind' if it's present.
String kind = jo.optString("kind", null);
if (kind != null) {
// Only applies when Descriptor.getId is overridden.
// Note that kind is only supported here,
// *not* inside the StaplerRequest.bindJSON which is normally called by newInstance
// (since Descriptor.newInstance is not itself available to Stapler).
// If you merely override getId for some reason, but use @DataBoundConstructor on your Describable,
// there is no problem; but you can only rely on newInstance being called at top level.
d = findById(descriptors, kind);
}
if (d == null) {
kind = jo.getString("$class");
d = findByDescribableClassName(descriptors, kind);
if (d == null) d = findByClassName(descriptors, kind);
kind = jo.optString("$class");
if (kind != null) { // else we will fall through to the warning
// This is the normal case.
d = findByDescribableClassName(descriptors, kind);
if (d == null) {
// Deprecated system where stapler-class was the Descriptor class name (rather than Describable class name).
d = findByClassName(descriptors, kind);
}
}
}
if (d != null) {
items.add(d.newInstance(req, jo));
} else {
LOGGER.warning("Received unexpected formData for descriptor " + kind);
LOGGER.log(Level.WARNING, "Received unexpected form data element: {0}", jo);
}
}
}
......
......@@ -71,6 +71,7 @@ import java.util.TreeMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import org.xmlpull.v1.XmlPullParserException;
/**
......@@ -761,13 +762,16 @@ public class Fingerprint implements ModelObject, Saveable {
private static final DateConverter DATE_CONVERTER = new DateConverter();
private final Date timestamp;
/**
* Time when the fingerprint has been captured.
*/
private final @Nonnull Date timestamp;
/**
* Null if this fingerprint is for a file that's
* apparently produced outside.
*/
private final BuildPtr original;
private final @CheckForNull BuildPtr original;
private final byte[] md5sum;
......@@ -785,12 +789,12 @@ public class Fingerprint implements ModelObject, Saveable {
*/
private transient volatile List<FingerprintFacet> transientFacets = null;
public Fingerprint(Run build, String fileName, byte[] md5sum) throws IOException {
public Fingerprint(@CheckForNull Run build, @Nonnull String fileName, @Nonnull byte[] md5sum) throws IOException {
this(build==null ? null : new BuildPtr(build), fileName, md5sum);
save();
}
Fingerprint(BuildPtr original, String fileName, byte[] md5sum) {
Fingerprint(@CheckForNull BuildPtr original, @Nonnull String fileName, @Nonnull byte[] md5sum) {
this.original = original;
this.md5sum = md5sum;
this.fileName = fileName;
......@@ -809,11 +813,11 @@ public class Fingerprint implements ModelObject, Saveable {
* if the file is apparently created outside Hudson.
*/
@Exported
public BuildPtr getOriginal() {
public @CheckForNull BuildPtr getOriginal() {
return original;
}
public String getDisplayName() {
public @Nonnull String getDisplayName() {
return fileName;
}
......@@ -821,7 +825,7 @@ public class Fingerprint implements ModelObject, Saveable {
* The file name (like "foo.jar" without path).
*/
@Exported
public String getFileName() {
public @Nonnull String getFileName() {
return fileName;
}
......@@ -829,7 +833,7 @@ public class Fingerprint implements ModelObject, Saveable {
* Gets the MD5 hash string.
*/
@Exported(name="hash")
public String getHashString() {
public @Nonnull String getHashString() {
return Util.toHexString(md5sum);
}
......@@ -837,7 +841,7 @@ public class Fingerprint implements ModelObject, Saveable {
* Gets the timestamp when this record is created.
*/
@Exported
public Date getTimestamp() {
public @Nonnull Date getTimestamp() {
return timestamp;
}
......@@ -847,7 +851,7 @@ public class Fingerprint implements ModelObject, Saveable {
* @return
* string like "3 minutes" "1 day" etc.
*/
public String getTimestampString() {
public @Nonnull String getTimestampString() {
long duration = System.currentTimeMillis()-timestamp.getTime();
return Util.getPastTimeString(duration);
}
......@@ -857,8 +861,9 @@ public class Fingerprint implements ModelObject, Saveable {
*
* <p>
* These builds of this job has used this file.
* @return may be empty but not null.
*/
public RangeSet getRangeSet(String jobFullName) {
public @Nonnull RangeSet getRangeSet(String jobFullName) {
RangeSet r = usages.get(jobFullName);
if(r==null) r = new RangeSet();
return r;
......@@ -871,14 +876,14 @@ public class Fingerprint implements ModelObject, Saveable {
/**
* Gets the sorted list of job names where this jar is used.
*/
public List<String> getJobs() {
public @Nonnull List<String> getJobs() {
List<String> r = new ArrayList<String>();
r.addAll(usages.keySet());
Collections.sort(r);
return r;
}
public Hashtable<String,RangeSet> getUsages() {
public @Nonnull Hashtable<String,RangeSet> getUsages() {
return usages;
}
......@@ -897,34 +902,39 @@ public class Fingerprint implements ModelObject, Saveable {
// this is for remote API
@Exported(name="usage")
public List<RangeItem> _getUsages() {
public @Nonnull List<RangeItem> _getUsages() {
List<RangeItem> r = new ArrayList<RangeItem>();
for (Entry<String, RangeSet> e : usages.entrySet())
r.add(new RangeItem(e.getKey(),e.getValue()));
return r;
}
/**
* @deprecated Use {@link #addFor(hudson.model.Run)}
*/
@Deprecated
public synchronized void add(AbstractBuild b) throws IOException {
public synchronized void add(@Nonnull AbstractBuild b) throws IOException {
addFor((Run) b);
}
/**
* Adds a usage reference to the build.
* @param b {@link Run} to be referenced in {@link #usages}
* @since 1.577
*/
public synchronized void addFor(Run b) throws IOException {
public synchronized void addFor(@Nonnull Run b) throws IOException {
add(b.getParent().getFullName(), b.getNumber());
}
/**
* Records that a build of a job has used this file.
*/
public synchronized void add(String jobFullName, int n) throws IOException {
public synchronized void add(@Nonnull String jobFullName, int n) throws IOException {
addWithoutSaving(jobFullName, n);
save();
}
void addWithoutSaving(String jobFullName, int n) {
void addWithoutSaving(@Nonnull String jobFullName, int n) {
synchronized(usages) { // TODO why not synchronized (this) like some, though not all, other accesses?
RangeSet r = usages.get(jobFullName);
if(r==null) {
......@@ -968,6 +978,8 @@ public class Fingerprint implements ModelObject, Saveable {
*
* @return true
* if this record was modified.
*
* @throws IOException Save failure
*/
public synchronized boolean trim() throws IOException {
boolean modified = false;
......@@ -1041,7 +1053,7 @@ public class Fingerprint implements ModelObject, Saveable {
*
* @since 1.421
*/
public Collection<FingerprintFacet> getFacets() {
public @Nonnull Collection<FingerprintFacet> getFacets() {
if (transientFacets==null) {
List<FingerprintFacet> transientFacets = new ArrayList<FingerprintFacet>();
for (TransientFingerprintFacetFactory fff : TransientFingerprintFacetFactory.all()) {
......@@ -1079,7 +1091,11 @@ public class Fingerprint implements ModelObject, Saveable {
};
}
public Collection<FingerprintFacet> getSortedFacets() {
/**
* Sorts {@link FingerprintFacet}s by their timestamps.
* @return Sorted list of {@link FingerprintFacet}s
*/
public @Nonnull Collection<FingerprintFacet> getSortedFacets() {
List<FingerprintFacet> r = new ArrayList<FingerprintFacet>(getFacets());
Collections.sort(r,new Comparator<FingerprintFacet>() {
public int compare(FingerprintFacet o1, FingerprintFacet o2) {
......@@ -1095,8 +1111,11 @@ public class Fingerprint implements ModelObject, Saveable {
/**
* Finds a facet of the specific type (including subtypes.)
* @param <T> Class of the {@link FingerprintFacet}
* @return First matching facet of the specified class
* @since 1.556
*/
public <T extends FingerprintFacet> T getFacet(Class<T> type) {
public @CheckForNull <T extends FingerprintFacet> T getFacet(Class<T> type) {
for (FingerprintFacet f : getFacets()) {
if (type.isInstance(f))
return type.cast(f);
......@@ -1107,7 +1126,7 @@ public class Fingerprint implements ModelObject, Saveable {
/**
* Returns the actions contributed from {@link #getFacets()}
*/
public List<Action> getActions() {
public @Nonnull List<Action> getActions() {
List<Action> r = new ArrayList<Action>();
for (FingerprintFacet ff : getFacets())
ff.createActions(r);
......@@ -1116,6 +1135,7 @@ public class Fingerprint implements ModelObject, Saveable {
/**
* Save the settings to a file.
* @throws IOException Save error
*/
public synchronized void save() throws IOException {
if(BulkChange.contains(this)) return;
......@@ -1218,14 +1238,14 @@ public class Fingerprint implements ModelObject, Saveable {
/**
* The file we save our configuration.
*/
private static XmlFile getConfigFile(File file) {
private static @Nonnull XmlFile getConfigFile(@Nonnull File file) {
return new XmlFile(XSTREAM,file);
}
/**
* Determines the file name from md5sum.
*/
private static File getFingerprintFile(byte[] md5sum) {
private static @Nonnull File getFingerprintFile(@Nonnull byte[] md5sum) {
assert md5sum.length==16;
return new File( Jenkins.getInstance().getRootDir(),
"fingerprints/"+ Util.toHexString(md5sum,0,1)+'/'+Util.toHexString(md5sum,1,1)+'/'+Util.toHexString(md5sum,2,md5sum.length-2)+".xml");
......@@ -1233,11 +1253,13 @@ public class Fingerprint implements ModelObject, Saveable {
/**
* Loads a {@link Fingerprint} from a file in the image.
* @return Loaded {@link Fingerprint}. Null if the config file does not exist or
* malformed.
*/
/*package*/ static @CheckForNull Fingerprint load(byte[] md5sum) throws IOException {
/*package*/ static @CheckForNull Fingerprint load(@Nonnull byte[] md5sum) throws IOException {
return load(getFingerprintFile(md5sum));
}
/*package*/ static @CheckForNull Fingerprint load(File file) throws IOException {
/*package*/ static @CheckForNull Fingerprint load(@Nonnull File file) throws IOException {
XmlFile configFile = getConfigFile(file);
if(!configFile.exists())
return null;
......
......@@ -30,6 +30,8 @@ import jenkins.model.Jenkins;
import java.io.File;
import java.io.IOException;
import java.util.Locale;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
/**
* Cache of {@link Fingerprint}s.
......@@ -56,16 +58,17 @@ public final class FingerprintMap extends KeyedDataStorage<Fingerprint,Fingerpri
* set to non-null if {@link Fingerprint} to be created (if so)
* will have this build as the owner. Otherwise null, to indicate
* an owner-less build.
* @throws IOException Loading error
*/
public Fingerprint getOrCreate(AbstractBuild build, String fileName, byte[] md5sum) throws IOException {
public @Nonnull Fingerprint getOrCreate(@CheckForNull AbstractBuild build, @Nonnull String fileName, @Nonnull byte[] md5sum) throws IOException {
return getOrCreate(build,fileName, Util.toHexString(md5sum));
}
public Fingerprint getOrCreate(AbstractBuild build, String fileName, String md5sum) throws IOException {
public @Nonnull Fingerprint getOrCreate(@CheckForNull AbstractBuild build, @Nonnull String fileName, @Nonnull String md5sum) throws IOException {
return super.getOrCreate(md5sum, new FingerprintParams(build,fileName));
}
public Fingerprint getOrCreate(Run build, String fileName, String md5sum) throws IOException {
public @Nonnull Fingerprint getOrCreate(@CheckForNull Run build, @Nonnull String fileName, @Nonnull String md5sum) throws IOException {
return super.getOrCreate(md5sum, new FingerprintParams(build,fileName));
}
......@@ -86,11 +89,11 @@ public final class FingerprintMap extends KeyedDataStorage<Fingerprint,Fingerpri
return data;
}
protected Fingerprint create(String md5sum, FingerprintParams createParams) throws IOException {
protected @Nonnull Fingerprint create(@Nonnull String md5sum, @Nonnull FingerprintParams createParams) throws IOException {
return new Fingerprint(createParams.build, createParams.fileName, toByteArray(md5sum));
}
protected Fingerprint load(String key) throws IOException {
protected @CheckForNull Fingerprint load(@Nonnull String key) throws IOException {
return Fingerprint.load(toByteArray(key));
}
......@@ -98,10 +101,10 @@ static class FingerprintParams {
/**
* Null if the build isn't claiming to be the owner.
*/
final Run build;
final @CheckForNull Run build;
final String fileName;
public FingerprintParams(Run build, String fileName) {
public FingerprintParams(@CheckForNull Run build, @Nonnull String fileName) {
this.build = build;
this.fileName = fileName;
......
......@@ -51,6 +51,7 @@ import java.io.Serializable;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.List;
import javax.annotation.CheckForNull;
/**
* Utilization statistics for a node or a set of nodes.
......@@ -614,13 +615,17 @@ public abstract class LoadStatistics {
return this;
}
public Builder with(Node node) {
if (node != null)
return with(node.toComputer());
public Builder with(@CheckForNull Node node) {
if (node != null) {
return with(node.toComputer());
}
return this;
}
public Builder with(Computer computer) {
public Builder with(@CheckForNull Computer computer) {
if (computer == null) {
return this;
}
if (computer.isOnline()) {
final List<Executor> executors = computer.getExecutors();
final boolean acceptingTasks = computer.isAcceptingTasks();
......
......@@ -1933,6 +1933,9 @@ public abstract class Run <JobT extends Job<JobT,RunT>,RunT extends Run<JobT,Run
public @Nonnull List<String> getLog(int maxLines) throws IOException {
int lineCount = 0;
List<String> logLines = new LinkedList<String>();
if (maxLines == 0) {
return logLines;
}
BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(getLogFile()),getCharset()));
try {
for (String line = reader.readLine(); line != null; line = reader.readLine()) {
......
......@@ -130,14 +130,7 @@ public class CommandLauncher extends ComputerLauncher {
computer.setChannel(proc.getInputStream(), proc.getOutputStream(), listener.getLogger(), new Channel.Listener() {
@Override
public void onClosed(Channel channel, IOException cause) {
try {
int exitCode = proc.exitValue();
if (exitCode!=0) {
listener.error("Process terminated with exit code "+exitCode);
}
} catch (IllegalThreadStateException e) {
// hasn't terminated yet
}
reportProcessTerminated(proc, listener);
try {
ProcessTree.get().killAll(proc, cookie);
......@@ -167,12 +160,23 @@ public class CommandLauncher extends ComputerLauncher {
LOGGER.log(Level.SEVERE, msg, e);
e.printStackTrace(listener.error(msg));
if(_proc!=null)
if(_proc!=null) {
reportProcessTerminated(_proc, listener);
try {
ProcessTree.get().killAll(_proc, _cookie);
} catch (InterruptedException x) {
x.printStackTrace(listener.error(Messages.ComputerLauncher_abortedLaunch()));
}
}
}
}
private static void reportProcessTerminated(Process proc, TaskListener listener) {
try {
int exitCode = proc.exitValue();
listener.error("Process terminated with exit code " + exitCode);
} catch (IllegalThreadStateException e) {
// hasn't terminated yet
}
}
......
......@@ -29,11 +29,13 @@ import hudson.Functions;
import hudson.model.Computer;
import hudson.model.User;
import org.acegisecurity.Authentication;
import org.jvnet.localizer.Localizable;
import org.kohsuke.stapler.export.ExportedBean;
import org.kohsuke.stapler.export.Exported;
import javax.annotation.Nonnull;
import java.util.Date;
/**
* Represents a cause that puts a {@linkplain Computer#isOffline() computer offline}.
*
......@@ -41,13 +43,34 @@ import org.kohsuke.stapler.export.Exported;
* <p>
* {@link OfflineCause} must have <tt>cause.jelly</tt> that renders a cause
* into HTML. This is used to tell users why the node is put offline.
* This view should render a block element like DIV.
* This view should render a block element like DIV.
*
* @author Kohsuke Kawaguchi
* @since 1.320
*/
@ExportedBean
public abstract class OfflineCause {
protected final long timestamp = System.currentTimeMillis();
/**
* Timestamp in which the event happened.
*
* @since 1.612
*/
@Exported
public long getTimestamp() {
return timestamp;
}
/**
* Same as {@link #getTimestamp()} but in a different type.
*
* @since 1.612
*/
public final @Nonnull Date getTime() {
return new Date(timestamp);
}
/**
* {@link OfflineCause} that renders a static text,
* but without any further UI.
......
......@@ -655,6 +655,12 @@ public class SlaveComputer extends Computer {
super.kill();
closeChannel();
IOUtils.closeQuietly(log);
try {
Util.deleteRecursive(getLogDir());
} catch (IOException ex) {
logger.log(Level.WARNING, "Unable to delete slave logs", ex);
}
}
public RetentionStrategy getRetentionStrategy() {
......
......@@ -585,12 +585,20 @@ public class Maven extends Builder {
}
private File getExeFile(String execName) {
if(File.separatorChar=='\\')
execName += ".bat";
String m2Home = Util.replaceMacro(getHome(),EnvVars.masterEnvVars);
return new File(m2Home, "bin/" + execName);
if(Functions.isWindows()) {
File exeFile = new File(m2Home, "bin/" + execName + ".bat");
// since Maven 3.3 .bat files are replaced with .cmd
if (!exeFile.exists()) {
return new File(m2Home, "bin/" + execName + ".cmd");
}
return exeFile;
} else {
return new File(m2Home, "bin/" + execName);
}
}
/**
......
......@@ -122,16 +122,15 @@ public abstract class DownloadFromUrlInstaller extends ToolInstaller {
}
protected Downloadable createDownloadable() {
return new Downloadable(getDownloadableId());
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.
* @since 1.610
*/
public String getDownloadableId() {
public String getId() {
return clazz.getName().replace('$','.');
}
......@@ -145,7 +144,7 @@ public abstract class DownloadFromUrlInstaller extends ToolInstaller {
* @return never null.
*/
public List<? extends Installable> getInstallables() throws IOException {
JSONObject d = Downloadable.get(getDownloadableId()).getData();
JSONObject d = Downloadable.get(getId()).getData();
if(d==null) return Collections.emptyList();
return Arrays.asList(((InstallableList)JSONObject.toBean(d,InstallableList.class)).list);
}
......
......@@ -64,6 +64,7 @@ public class ConsistentHash<T> {
* All the items in the hash, to their replication factors.
*/
private final Map<T,Point[]> items = new HashMap<T,Point[]>();
private int numPoints;
private final int defaultReplication;
private final Hash<T> hash;
......@@ -100,8 +101,13 @@ public class ConsistentHash<T> {
private final Object[] owner; // really T[]
private Table() {
int r=0;
for (Point[] v : items.values())
r+=v.length;
numPoints = r;
// merge all points from all nodes and sort them into a single array
Point[] allPoints = new Point[countAllPoints()];
Point[] allPoints = new Point[numPoints];
int p=0;
for (Point[] v : items.values()) {
System.arraycopy(v,0,allPoints,p,v.length);
......@@ -186,18 +192,18 @@ public class ConsistentHash<T> {
String hash(T t);
}
static final Hash DEFAULT_HASH = new Hash() {
static final Hash<?> DEFAULT_HASH = new Hash<Object>() {
public String hash(Object o) {
return o.toString();
}
};
public ConsistentHash() {
this(DEFAULT_HASH);
this((Hash<T>) DEFAULT_HASH);
}
public ConsistentHash(int defaultReplication) {
this(DEFAULT_HASH,defaultReplication);
this((Hash<T>) DEFAULT_HASH,defaultReplication);
}
public ConsistentHash(Hash<T> hash) {
......@@ -211,23 +217,20 @@ public class ConsistentHash<T> {
}
public int countAllPoints() {
int r=0;
for (Point[] v : items.values())
r+=v.length;
return r;
return numPoints;
}
/**
* Adds a new node with the default number of replica.
*/
public void add(T node) {
public synchronized void add(T node) {
add(node,defaultReplication);
}
/**
* Calls {@link #add(Object)} with all the arguments.
*/
public void addAll(T... nodes) {
public synchronized void addAll(T... nodes) {
for (T node : nodes)
addInternal(node,defaultReplication);
refreshTable();
......@@ -236,7 +239,7 @@ public class ConsistentHash<T> {
/**
* Calls {@link #add(Object)} with all the arguments.
*/
public void addAll(Collection<? extends T> nodes) {
public synchronized void addAll(Collection<? extends T> nodes) {
for (T node : nodes)
addInternal(node,defaultReplication);
refreshTable();
......@@ -245,7 +248,7 @@ public class ConsistentHash<T> {
/**
* Calls {@link #add(Object,int)} with all the arguments.
*/
public void addAll(Map<? extends T,Integer> nodes) {
public synchronized void addAll(Map<? extends T,Integer> nodes) {
for (Map.Entry<? extends T,Integer> node : nodes.entrySet())
addInternal(node.getKey(),node.getValue());
refreshTable();
......@@ -254,23 +257,20 @@ public class ConsistentHash<T> {
/**
* Removes the node entirely. This is the same as {@code add(node,0)}
*/
public void remove(T node) {
public synchronized void remove(T node) {
add(node, 0);
}
/**
* Adds a new node with the given number of replica.
*
* <p>
* This is the only function that manipulates {@link #items}.
*/
public synchronized void add(T node, int replica) {
addInternal(node, replica);
refreshTable();
}
private void addInternal(T node, int replica) {
if(replica==0) {
private synchronized void addInternal(T node, int replica) {
if (replica==0) {
items.remove(node);
} else {
Point[] points = new Point[replica];
......@@ -281,11 +281,10 @@ public class ConsistentHash<T> {
}
}
private void refreshTable() {
private synchronized void refreshTable() {
table = new Table();
}
/**
* Compresses a string into an integer with MD5.
*/
......
......@@ -31,6 +31,8 @@ import java.lang.ref.SoftReference;
import java.text.MessageFormat;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
/**
* Convenient base class for implementing data storage.
......@@ -69,10 +71,10 @@ public abstract class KeyedDataStorage<T,P> {
* but do so without having a single lock.
*/
private static class Loading<T> {
private T value;
private @CheckForNull T value;
private boolean set;
public synchronized void set(T value) {
public synchronized void set(@CheckForNull T value) {
this.set = true;
this.value = value;
notifyAll();
......@@ -82,7 +84,7 @@ public abstract class KeyedDataStorage<T,P> {
* Blocks until the value is {@link #set(Object)} by another thread
* and returns the value.
*/
public synchronized T get() {
public synchronized @CheckForNull T get() {
try {
while(!set)
wait();
......@@ -100,26 +102,31 @@ public abstract class KeyedDataStorage<T,P> {
* {@link #create(String,Object) create} it and return it.
*
* @return
* Never null.
* Item with the specified {@code key}.
* @param createParams
* Additional parameters needed to create a new data object. Can be null.
* @throws IOException Loading error
*/
public T getOrCreate(String key, P createParams) throws IOException {
public @Nonnull T getOrCreate(String key, P createParams) throws IOException {
return get(key,true,createParams);
}
/**
* Finds the data object that matches the given key if available, or null
* if not found.
* @return Item with the specified {@code key}
* @throws IOException Loading error
*/
public T get(String key) throws IOException {
public @CheckForNull T get(String key) throws IOException {
return get(key,false,null);
}
/**
* Implementation of get/getOrCreate.
* @return Item with the specified {@code key}
* @throws IOException Loading error
*/
protected T get(String key, boolean createIfNotExist, P createParams) throws IOException {
protected @CheckForNull T get(@Nonnull String key, boolean createIfNotExist, P createParams) throws IOException {
while(true) {
totalQuery.incrementAndGet();
Object value = core.get(key);
......@@ -155,7 +162,7 @@ public abstract class KeyedDataStorage<T,P> {
if(t==null && createIfNotExist) {
t = create(key,createParams); // create the new data
if(t==null)
throw new IllegalStateException(); // bug in the derived classes
throw new IllegalStateException("Bug in the derived class"); // bug in the derived classes
}
} catch(IOException e) {
loadFailure.incrementAndGet();
......@@ -192,7 +199,7 @@ public abstract class KeyedDataStorage<T,P> {
* if load operation fails. This exception will be
* propagated to the caller.
*/
protected abstract T load(String key) throws IOException;
protected abstract @CheckForNull T load(String key) throws IOException;
/**
* Creates a new data object.
......@@ -206,13 +213,13 @@ public abstract class KeyedDataStorage<T,P> {
* this method returns a properly initialized "valid" object.
*
* @return
* never null. If construction fails, abort with an exception.
* Created item. If construction fails, abort with an exception.
* @throws IOException
* if the method fails to create a new data object, it can throw
* {@link IOException} (or any other exception) and that will be
* propagated to the caller.
*/
protected abstract T create(String key, P createParams) throws IOException;
protected abstract @Nonnull T create(@Nonnull String key, @Nonnull P createParams) throws IOException;
public void resetPerformanceStats() {
totalQuery.set(0);
......
......@@ -23,6 +23,7 @@
*/
package hudson.util;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
......@@ -41,7 +42,9 @@ public class MaskingClassLoader extends ClassLoader {
/**
* Prefix of the packages that should be hidden.
*/
private List<String> masks;
private final List<String> masksClasses = new ArrayList<String>();
private final List<String> masksResources = new ArrayList<String>();
public MaskingClassLoader(ClassLoader parent, String... masks) {
this(parent, Arrays.asList(masks));
......@@ -49,12 +52,19 @@ public class MaskingClassLoader extends ClassLoader {
public MaskingClassLoader(ClassLoader parent, Collection<String> masks) {
super(parent);
this.masks = new ArrayList<String>(masks);
this.masksClasses.addAll(masks);
/**
* The name of a resource is a '/'-separated path name
*/
for (String mask : masks) {
masksResources.add(mask.replace(".","/"));
}
}
@Override
protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
for (String mask : masks) {
for (String mask : masksClasses) {
if(name.startsWith(mask))
throw new ClassNotFoundException();
}
......@@ -62,7 +72,20 @@ public class MaskingClassLoader extends ClassLoader {
return super.loadClass(name, resolve);
}
@Override
public synchronized URL getResource(String name) {
for (String mask : masksResources) {
if(name.startsWith(mask))
return null;
}
return super.getResource(name);
}
public synchronized void add(String prefix) {
masks.add(prefix);
masksClasses.add(prefix);
if(prefix !=null){
masksResources.add(prefix.replace(".","/"));
}
}
}
......@@ -30,6 +30,7 @@ import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
import java.util.List;
import javax.annotation.Nonnull;
/**
* Plugin-specific additions to fingerprint information.
......@@ -65,9 +66,9 @@ public abstract class FingerprintFacet implements ExtensionPoint {
* @param fingerprint
* {@link Fingerprint} object to which this facet is going to be added to.
* @param timestamp
* Timestamp when the use happened.
* Timestamp when the use happened (when the facet has been created).
*/
protected FingerprintFacet(Fingerprint fingerprint, long timestamp) {
protected FingerprintFacet(@Nonnull Fingerprint fingerprint, long timestamp) {
assert fingerprint!=null;
this.fingerprint = fingerprint;
this.timestamp = timestamp;
......@@ -79,18 +80,20 @@ public abstract class FingerprintFacet implements ExtensionPoint {
* @return
* always non-null.
*/
public Fingerprint getFingerprint() {
public @Nonnull Fingerprint getFingerprint() {
return fingerprint;
}
/**
* Create action objects to be contributed to the owner {@link Fingerprint}.
*
* By default, creates no actions.
* <p>
* {@link Fingerprint} calls this method for every {@link FingerprintFacet} that
* it owns when the rendering is requested.
* @param result Output list
*/
public void createActions(List<Action> result) {
// Create no actions by default
}
/**
......
......@@ -51,11 +51,7 @@ public abstract class GlobalSettingsProvider extends AbstractDescribableImpl<Glo
public static final FilePath getSettingsFilePath(GlobalSettingsProvider settings, AbstractBuild<?, ?> build, TaskListener listener) {
FilePath settingsPath = null;
if (settings != null) {
try {
settingsPath = settings.supplySettings(build, listener);
} catch (Exception e) {
listener.getLogger().print("failed to get the path to the alternate global settings.xml");
}
settingsPath = settings.supplySettings(build, listener);
}
return settingsPath;
}
......
/*
* The MIT License
*
* Copyright (c) 2015, CloudBees, 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.
*/
package jenkins.security;
import org.apache.commons.codec.binary.Base64;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.SecureRandom;
import java.security.interfaces.RSAPrivateCrtKey;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.RSAPublicKeySpec;
/**
* RSA public/private key pair as {@link ConfidentialKey}.
*
* <p>
* As per the design principle of {@link ConfidentialKey}, not exposing {@link PrivateKey} directly.
* Define subtypes for different use cases.
*
* @author Kohsuke Kawaguchi
*/
public abstract class RSAConfidentialKey extends ConfidentialKey {
private RSAPrivateKey priv;
private RSAPublicKey pub;
public RSAConfidentialKey(String id) {
super(id);
}
public RSAConfidentialKey(Class owner, String shortName) {
this(owner.getName() + '.' + shortName);
}
/**
* Obtains the private key (lazily.)
* <p>
* This method is not publicly exposed as per the design principle of {@link ConfidentialKey}.
* Instead of exposing private key, define methods that use them in specific way, such as
* {@link RSADigitalSignatureConfidentialKey}.
*
* @throws Error
* If key cannot be loaded for some reasons, we fail.
*/
protected synchronized RSAPrivateKey getPrivateKey() {
try {
if (priv == null) {
byte[] payload = load();
if (payload == null) {
KeyPairGenerator gen = KeyPairGenerator.getInstance("RSA");
gen.initialize(2048, new SecureRandom()); // going beyond 2048 requires crypto extension
KeyPair keys = gen.generateKeyPair();
priv = (RSAPrivateKey) keys.getPrivate();
pub = (RSAPublicKey) keys.getPublic();
store(priv.getEncoded());
} else {
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
priv = (RSAPrivateKey) keyFactory.generatePrivate(new PKCS8EncodedKeySpec(payload));
RSAPrivateCrtKey pks = (RSAPrivateCrtKey) priv;
pub = (RSAPublicKey) keyFactory.generatePublic(
new RSAPublicKeySpec(pks.getModulus(), pks.getPublicExponent()));
}
}
return priv;
} catch (IOException e) {
throw new Error("Failed to load the key: " + getId(), e);
} catch (GeneralSecurityException e) {
throw new Error("Failed to load the key: " + getId(), e);
}
}
public RSAPublicKey getPublicKey() {
getPrivateKey();
return pub;
}
/**
* Gets base64-encoded public key.
*/
public String getEncodedPublicKey() {
return new String(Base64.encodeBase64(getPublicKey().getEncoded()));
}
}
/*
* The MIT License
*
* Copyright (c) 2015, CloudBees, 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.
*/
package jenkins.security;
import java.io.UnsupportedEncodingException;
import java.security.GeneralSecurityException;
import java.security.Signature;
import java.security.interfaces.RSAPrivateKey;
/**
* RSA digital signature as {@link ConfidentialKey} to prevent accidental leak of private key.
*
* @author Kohsuke Kawaguchi
*/
public class RSADigitalSignatureConfidentialKey extends RSAConfidentialKey {
public RSADigitalSignatureConfidentialKey(String id) {
super(id);
}
public RSADigitalSignatureConfidentialKey(Class owner, String shortName) {
super(owner, shortName);
}
/**
* Sign a message and base64 encode the signature.
*/
public String sign(String msg) {
try {
RSAPrivateKey key = getPrivateKey();
Signature sig = Signature.getInstance(SIGNING_ALGORITHM + "with" + key.getAlgorithm());
sig.initSign(key);
sig.update(msg.getBytes("UTF-8"));
return hudson.remoting.Base64.encode(sig.sign());
} catch (GeneralSecurityException e) {
throw new SecurityException(e);
} catch (UnsupportedEncodingException e) {
throw new AssertionError(e); // UTF-8 is mandatory
}
}
static final String SIGNING_ALGORITHM = "SHA256";
}
......@@ -123,7 +123,7 @@ public class JnlpSlaveAgentProtocol extends AgentProtocol {
@Override
public void onClosed(Channel channel, IOException cause) {
if(cause!=null)
LOGGER.log(Level.WARNING, Thread.currentThread().getName()+" for + " + nodeName + " terminated",cause);
LOGGER.log(Level.WARNING, Thread.currentThread().getName() + " for " + nodeName + " terminated", cause);
try {
socket.close();
} catch (IOException e) {
......
......@@ -23,6 +23,13 @@ THE SOFTWARE.
-->
<?jelly escape-by-default='true'?>
<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">
<p class="warning"><j:out value="${it}" /></p>
<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" xmlns:i="jelly:fmt">
<div class="warning">
<div class="timestamp">
<i:formatDate value="${it.time}" type="both" dateStyle="medium" timeStyle="medium"/>
</div>
<div class="message">
<j:out value="${it}" />
</div>
</div>
</j:jelly>
......@@ -50,10 +50,10 @@ THE SOFTWARE.
<j:set var="port" value="${request.getParameter('debugPort')}"/>
<j:choose>
<j:when test="${port!=null}">
<j2se version="1.5+" java-vm-args="${it.launcher.vmargs} -Xdebug -Xrunjdwp:transport=dt_socket,server=y,address=${port}" />
<j2se version="1.7+" java-vm-args="${it.launcher.vmargs} -Xdebug -Xrunjdwp:transport=dt_socket,server=y,address=${port}" />
</j:when>
<j:otherwise>
<j2se version="1.5+" java-vm-args="${it.launcher.vmargs}"/>
<j2se version="1.7+" java-vm-args="${it.launcher.vmargs}"/>
</j:otherwise>
</j:choose>
<jar href="${rootURL}jnlpJars/remoting.jar"/>
......
......@@ -46,12 +46,12 @@ THE SOFTWARE.
</st:documentation>
<j:set var="clazz" value="${attrs.clazz ?: attrs.descriptor.clazz.name}" />
<f:invisibleEntry>
<input type="hidden" name="stapler-class" value="${clazz}" /> <!-- Legacy: Remove once plugins have been staged onto $class -->
<j:choose>
<j:when test="${attrs.descriptor != null and attrs.descriptor.id != clazz}">
<input type="hidden" name="kind" value="${attrs.descriptor.id}" />
</j:when>
<j:otherwise>
<input type="hidden" name="stapler-class" value="${clazz}" /> <!-- Legacy: Remove once plugins have been staged onto $class -->
<input type="hidden" name="$class" value="${clazz}" />
</j:otherwise>
</j:choose>
......
......@@ -33,7 +33,7 @@ Behaviour.specify("DIV.hetero-list-container", 'hetero-list', -100, function(e)
var menuAlign = (btn.getAttribute("menualign")||"tl-bl");
var menuButton = new YAHOO.widget.Button(btn, { type: "menu", menu: menu, menualignment: menuAlign.split("-") });
var menuButton = new YAHOO.widget.Button(btn, { type: "menu", menu: menu, menualignment: menuAlign.split("-"), menuminscrollheight: 250 });
$(menuButton._button).addClassName(btn.className); // copy class names
$(menuButton._button).setAttribute("suffix",btn.getAttribute("suffix"));
menuButton.getMenu().clickEvent.subscribe(function(type,args,value) {
......
/*
* The MIT License
*
* Copyright (c) 2015, CloudBees, 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.
*/
package jenkins.security
import org.junit.Rule
import org.junit.Test
/**
*
*
* @author Kohsuke Kawaguchi
*/
class RSAConfidentialKeyTest {
@Rule
public ConfidentialStoreRule store = new ConfidentialStoreRule()
def key = new RSAConfidentialKey("test") {}
@Test
void loadingExistingKey() {
// this second key of the same ID will cause it to load the key from the disk
def key2 = new RSAConfidentialKey("test") {}
assert key.privateKey==key2.privateKey;
assert key.publicKey ==key2.publicKey;
}
}
/*
* The MIT License
*
* Copyright (c) 2015, CloudBees, 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.
*/
package jenkins.security
import hudson.remoting.Base64
import org.junit.Rule
import org.junit.Test
import java.security.Signature
/**
*
*
* @author Kohsuke Kawaguchi
*/
class RSADigitalSignatureConfidentialKeyTest {
@Rule
public ConfidentialStoreRule store = new ConfidentialStoreRule()
def key = new RSADigitalSignatureConfidentialKey("test");
@Test
void dsigSignAndVerify() {
def plainText = "Hello world"
def msg = key.sign(plainText);
println msg;
def sig = Signature.getInstance("SHA256withRSA");
sig.initVerify(key.publicKey);
sig.update(plainText.getBytes("UTF-8"));
assert sig.verify(Base64.decode(msg))
}
}
......@@ -25,18 +25,29 @@
package hudson.model;
import java.io.IOException;
import hudson.model.Run.Artifact;
import java.io.File;
import java.io.PrintWriter;
import java.util.List;
import java.util.Locale;
import java.util.TimeZone;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import static org.junit.Assert.*;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.jvnet.hudson.test.Issue;
import org.jvnet.localizer.LocaleProvider;
import org.mockito.Mockito;
public class RunTest {
@Rule public TemporaryFolder tmp = new TemporaryFolder();
@Issue("JENKINS-15816")
@SuppressWarnings({"unchecked", "rawtypes"})
@Test public void timezoneOfID() throws Exception {
......@@ -118,6 +129,16 @@ public class RunTest {
@SuppressWarnings({"unchecked", "rawtypes"})
@Test
public void getDurationString() throws IOException {
LocaleProvider providerToRestore = LocaleProvider.getProvider();
try {
// This test expects English texts.
LocaleProvider.setProvider(new LocaleProvider() {
@Override
public Locale get() {
return Locale.ENGLISH;
}
});
Run r = new Run(new StubJob(), 0) {};
assertEquals("Not started yet", r.getDurationString());
r.onStartBuilding();
......@@ -127,5 +148,25 @@ public class RunTest {
r.onEndBuilding();
msg = r.getDurationString();
assertFalse(msg, msg.endsWith(" and counting"));
} finally {
LocaleProvider.setProvider(providerToRestore);
}
}
@Issue("JENKINS-27441")
@Test
public void getLogReturnsAnEmptyListWhenCalledWith0() throws Exception {
Job j = Mockito.mock(Job.class);
File tempBuildDir = tmp.newFolder();
Mockito.when(j.getBuildDir()).thenReturn(tempBuildDir);
Run r = new Run(j, 0) {};
File f = r.getLogFile();
f.getParentFile().mkdirs();
PrintWriter w = new PrintWriter(f, "utf-8");
w.println("dummy");
w.close();
List<String> logLines = r.getLog(0);
assertTrue(logLines.isEmpty());
}
}
......@@ -122,11 +122,58 @@ public class ConsistentHashTest {
@Test
public void emptyBehavior() {
ConsistentHash<String> hash = new ConsistentHash<String>();
assertEquals(0, hash.countAllPoints());
assertFalse(hash.list(0).iterator().hasNext());
assertNull(hash.lookup(0));
assertNull(hash.lookup(999));
}
@Test
public void countAllPoints() {
ConsistentHash<String> hash = new ConsistentHash<String>();
assertEquals(0, hash.countAllPoints());
hash.add("foo", 10);
assertEquals(10, hash.countAllPoints());
hash.add("bar", 5);
assertEquals(15, hash.countAllPoints());
hash.remove("foo");
assertEquals(5, hash.countAllPoints());
}
@Test
public void defaultReplicationIsOneHundred() {
ConsistentHash<String> hash = new ConsistentHash<String>();
assertEquals(0, hash.countAllPoints());
hash.add("foo");
assertEquals(100, hash.countAllPoints());
}
@Test
public void setCustomDefaultReplication() {
ConsistentHash<String> hash = new ConsistentHash<String>((ConsistentHash.Hash<String>) ConsistentHash.DEFAULT_HASH, 7);
assertEquals(0, hash.countAllPoints());
hash.add("foo");
assertEquals(7, hash.countAllPoints());
}
@Test
public void usesCustomHash() {
final RuntimeException exception = new RuntimeException();
ConsistentHash.Hash<String> hashFunction = new ConsistentHash.Hash<String>() {
public String hash(String str) {
throw exception;
}
};
try {
ConsistentHash<String> hash = new ConsistentHash<String>(hashFunction);
hash.add("foo");
fail("Didn't use custom hash function");
} catch (RuntimeException e) {
assertSame(exception, e);
}
}
/**
* This test doesn't fail but it's written to measure the performance of the consistent hash function with large data set.
*/
......
......@@ -12,7 +12,7 @@
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>plugin</artifactId>
<name>Jenkins plugin POM</name>
<version>1.612-SNAPSHOT</version>
<version>1.614-SNAPSHOT</version>
<packaging>pom</packaging>
<!--
......@@ -41,19 +41,19 @@
<groupId>org.jenkins-ci.main</groupId>
<artifactId>jenkins-war</artifactId>
<type>war</type>
<version>1.612-SNAPSHOT</version>
<version>1.614-SNAPSHOT</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jenkins-ci.main</groupId>
<artifactId>jenkins-core</artifactId>
<version>1.612-SNAPSHOT</version>
<version>1.614-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.jenkins-ci.main</groupId>
<artifactId>jenkins-test-harness</artifactId>
<version>1.612-SNAPSHOT</version>
<version>1.614-SNAPSHOT</version>
<scope>test</scope>
</dependency>
<!--
......
......@@ -33,7 +33,7 @@ THE SOFTWARE.
<groupId>org.jenkins-ci.main</groupId>
<artifactId>pom</artifactId>
<version>1.612-SNAPSHOT</version>
<version>1.614-SNAPSHOT</version>
<packaging>pom</packaging>
<name>Jenkins main module</name>
......@@ -359,6 +359,7 @@ THE SOFTWARE.
<artifactId>maven-surefire-plugin</artifactId>
<version>2.16</version> <!-- ignoring ${maven-surefire-plugin.version} -->
<configuration>
<argLine>-noverify</argLine> <!-- some versions of JDK7/8 causes VerifyError during mock tests: http://code.google.com/p/powermock/issues/detail?id=504 -->
<systemPropertyVariables>
<java.io.tmpdir>${project.build.directory}</java.io.tmpdir>
<forkedProcessTimeoutInSeconds>3600</forkedProcessTimeoutInSeconds>
......@@ -614,7 +615,7 @@ THE SOFTWARE.
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.${java.level}</source>
<target>1.${java.level}</target>
<target>1.7</target> <!-- TODO JENKINS-28120 restore to 1.${java.level} -->
<!-- default reuseCreated is more performant
feel free to uncomment if you have any issues on your platform
<compilerReuseStrategy>alwaysNew</compilerReuseStrategy>
......@@ -633,13 +634,13 @@ THE SOFTWARE.
<configuration>
<rules>
<requireJavaVersion>
<version>1.6.0-18</version>
<version>1.7.0</version>
</requireJavaVersion>
<requireMavenVersion>
<version>3.0</version>
</requireMavenVersion>
<enforceBytecodeVersion>
<maxJdkVersion>1.${java.level}</maxJdkVersion>
<maxJdkVersion>1.7</maxJdkVersion> <!-- TODO JENKINS-28120 restore to 1.${java.level} -->
<ignoreClasses>
<ignoreClass>org.eclipse.jetty.spdy.*</ignoreClass>
</ignoreClasses>
......
......@@ -28,7 +28,7 @@ THE SOFTWARE.
<parent>
<groupId>org.jenkins-ci.main</groupId>
<artifactId>pom</artifactId>
<version>1.612-SNAPSHOT</version>
<version>1.614-SNAPSHOT</version>
</parent>
<artifactId>jenkins-test-harness</artifactId>
......
/*
* The MIT License
*
*
* Copyright (c) 2004-2010, Sun Microsystems, Inc., Kohsuke Kawaguchi,
* Alan Harder
*
*
* 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
......@@ -26,6 +26,7 @@ package hudson;
import org.jvnet.hudson.test.Issue;
import org.jvnet.hudson.test.HudsonTestCase;
import org.jvnet.hudson.test.LenientRunnable;
import org.jvnet.hudson.test.recipes.LocalData;
import java.net.URL;
......@@ -87,4 +88,19 @@ public class ClassicPluginStrategyTest extends HudsonTestCase {
fail("disabled dependency should not be included");
}
}
/**
* Test finding resources under masking.
* "foo1" plugin contains attribute of Mask-Classes: org.apache.http.
*/
@LocalData
@Issue("JENKINS-27289")
public void testMaskResourceClassLoader() throws Exception {
PluginWrapper pw = jenkins.getPluginManager().getPlugin("foo1");
Class<?> clazz = pw.classLoader.loadClass("org.apache.http.impl.io.SocketInputBuffer");
ClassLoader cl = clazz.getClassLoader();
URL url = cl.getResource("org/apache/http/impl/io/SocketInputBuffer.class");
assertNotNull(url);
assertTrue("expected to find the class from foo1 plugin", url.toString().contains("plugins/foo1"));
}
}
/*
* The MIT License
*
* Copyright (c) 2015 Red Hat, 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.
*/
package hudson.model;
import static org.junit.Assert.*;
import java.io.File;
import jenkins.model.Jenkins;
import hudson.slaves.DumbSlave;
import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.JenkinsRule;
public class ComputerTest {
@Rule public JenkinsRule j = new JenkinsRule();
@Test
public void discardLogsAfterDeletion() throws Exception {
DumbSlave delete = j.createOnlineSlave(Jenkins.getInstance().getLabelAtom("delete"));
DumbSlave keep = j.createOnlineSlave(Jenkins.getInstance().getLabelAtom("keep"));
File logFile = delete.toComputer().getLogFile();
assertTrue(logFile.exists());
Jenkins.getInstance().removeNode(delete);
assertFalse("Slave log should be deleted", logFile.exists());
assertFalse("Slave log directory should be deleted", logFile.getParentFile().exists());
assertTrue("Slave log should be kept", keep.toComputer().getLogFile().exists());
}
}
......@@ -30,15 +30,18 @@ import hudson.tasks.BuildStepDescriptor;
import hudson.tasks.Builder;
import hudson.tasks.Shell;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import jenkins.model.Jenkins;
import net.sf.json.JSONObject;
import static org.junit.Assert.*;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.Issue;
import org.jvnet.hudson.test.JenkinsRule;
import org.jvnet.hudson.test.TestExtension;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.StaplerRequest;
@SuppressWarnings({"unchecked", "rawtypes"})
......@@ -114,4 +117,94 @@ public class DescriptorTest {
@TestExtension("overriddenId") public static final BuildStepDescriptor<Builder> builderA = new DescriptorImpl("builder-a");
@TestExtension("overriddenId") public static final BuildStepDescriptor<Builder> builderB = new DescriptorImpl("builder-b");
@Issue("JENKINS-28110")
@Test public void nestedDescribableOverridingId() throws Exception {
FreeStyleProject p = rule.createFreeStyleProject("p");
p.getBuildersList().add(new B1(Arrays.asList(new D1(), new D2())));
rule.configRoundtrip(p);
rule.assertLogContains("[D 1, D 2]", rule.buildAndAssertSuccess(p));
}
public static abstract class D extends AbstractDescribableImpl<D> {
@Override public String toString() {return getDescriptor().getDisplayName();}
}
public static class D1 extends D {
@DataBoundConstructor public D1() {}
@TestExtension("nestedDescribableOverridingId") public static class DescriptorImpl extends Descriptor<D> {
@Override public String getDisplayName() {return "D 1";}
@Override public String getId() {return "D1-id";}
}
}
public static class D2 extends D {
@DataBoundConstructor public D2() {}
@TestExtension("nestedDescribableOverridingId") public static class DescriptorImpl extends Descriptor<D> {
@Override public String getDisplayName() {return "D 2";}
@Override public String getId() {return "D2-id";}
}
}
public static class B1 extends Builder {
public final List<D> ds;
@DataBoundConstructor public B1(List<D> ds) {
this.ds = ds;
}
@Override public boolean perform(AbstractBuild<?,?> build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException {
listener.getLogger().println(ds);
return true;
}
@TestExtension("nestedDescribableOverridingId") public static class DescriptorImpl extends Descriptor<Builder> {
@Override public String getDisplayName() {return "B1";}
}
}
@Ignore("never worked: TypePair.convertJSON looks for @DataBoundConstructor on D3 (Stapler does not grok Descriptor)")
@Issue("JENKINS-28110")
@Test public void nestedDescribableSharingClass() throws Exception {
FreeStyleProject p = rule.createFreeStyleProject("p");
p.getBuildersList().add(new B2(Arrays.asList(new D3("d3a"), new D3("d3b"))));
rule.configRoundtrip(p);
rule.assertLogContains("[d3a, d3b]", rule.buildAndAssertSuccess(p));
}
public static class D3 implements Describable<D3> {
private final String id;
D3(String id) {
this.id = id;
}
@Override public String toString() {
return id;
}
@Override public Descriptor<D3> getDescriptor() {
return Jenkins.getInstance().getDescriptorByName(id);
}
}
public static class D3D extends Descriptor<D3> {
private final String id;
public D3D(String id) {
super(D3.class);
this.id = id;
}
@Override public String getId() {
return id;
}
@Override public D3 newInstance(StaplerRequest req, JSONObject formData) throws Descriptor.FormException {
return new D3(id);
}
@Override public String getDisplayName() {
return id;
}
}
@TestExtension("nestedDescribableSharingClass") public static final Descriptor<D3> d3a = new D3D("d3a");
@TestExtension("nestedDescribableSharingClass") public static final Descriptor<D3> d3b = new D3D("d3b");
public static class B2 extends Builder {
public final List<D3> ds;
@DataBoundConstructor public B2(List<D3> ds) {
this.ds = ds;
}
@Override public boolean perform(AbstractBuild<?,?> build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException {
listener.getLogger().println(ds);
return true;
}
@TestExtension("nestedDescribableSharingClass") public static class DescriptorImpl extends Descriptor<Builder> {
@Override public String getDisplayName() {return "B2";}
}
}
}
package hudson.model;
import hudson.model.FreeStyleBuild;
import hudson.model.FreeStyleProject;
import hudson.model.Result;
import hudson.model.utils.AbortExceptionPublisher;
import hudson.model.utils.IOExceptionPublisher;
import hudson.model.utils.ResultWriterPublisher;
import hudson.model.utils.TrueFalsePublisher;
import hudson.tasks.ArtifactArchiver;
import org.apache.commons.io.FileUtils;
import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.Issue;
import org.jvnet.hudson.test.JenkinsRule;
import java.io.File;
import java.io.IOException;
import java.util.concurrent.ExecutionException;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
/**
* Freestyle publishers statuses tests
*
* @author Kanstantsin Shautsou
*/
public class FreestyleJobPublisherTest {
@Rule
public JenkinsRule j = new JenkinsRule();
/**
* Execute all publishers even one of publishers return false.
*/
@Issue("JENKINS-26964")
@Test
public void testFreestyleWithFalsePublisher() throws Exception {
FreeStyleProject p = j.createFreeStyleProject();
p.getPublishersList().add(new TrueFalsePublisher(true)); // noop
p.getPublishersList().add(new TrueFalsePublisher(false)); // FAIL build with false
p.getPublishersList().add(new ResultWriterPublisher("result.txt")); // catch result to file
final ArtifactArchiver artifactArchiver = new ArtifactArchiver("result.txt");
artifactArchiver.setOnlyIfSuccessful(false);
p.getPublishersList().add(artifactArchiver); // transfer file to build dir
FreeStyleBuild b = p.scheduleBuild2(0).get();
assertEquals("Build must fail, because we used FalsePublisher", b.getResult(), Result.FAILURE);
File file = new File(b.getArtifactsDir(), "result.txt");
assertTrue("ArtifactArchiver is executed even prior publisher fails", file.exists());
assertTrue("Publisher, after publisher with return false status, must see FAILURE status",
FileUtils.readFileToString(file).equals(Result.FAILURE.toString()));
}
/**
* Execute all publishers even one of them throws AbortException.
*/
@Issue("JENKINS-26964")
@Test
public void testFreestyleWithExceptionPublisher() throws Exception {
FreeStyleProject p = j.createFreeStyleProject();
p.getPublishersList().add(new TrueFalsePublisher(true)); // noop
p.getPublishersList().add(new AbortExceptionPublisher()); // FAIL build with AbortException
p.getPublishersList().add(new ResultWriterPublisher("result.txt")); // catch result to file
final ArtifactArchiver artifactArchiver = new ArtifactArchiver("result.txt");
artifactArchiver.setOnlyIfSuccessful(false);
p.getPublishersList().add(artifactArchiver); // transfer file to build dir
FreeStyleBuild b = p.scheduleBuild2(0).get();
assertEquals("Build must fail, because we used AbortExceptionPublisher", b.getResult(), Result.FAILURE);
j.assertLogNotContains("\tat", b); // log must not contain stacktrace
j.assertLogContains("Threw AbortException from publisher!", b); // log must contain exact error message
File file = new File(b.getArtifactsDir(), "result.txt");
assertTrue("ArtifactArchiver is executed even prior publisher fails", file.exists());
assertTrue("Third publisher must see FAILURE status",
FileUtils.readFileToString(file).equals(Result.FAILURE.toString()));
}
/**
* Execute all publishers even one of them throws any Exceptions.
*/
@Issue("JENKINS-26964")
@Test
public void testFreestyleWithIOExceptionPublisher() throws Exception {
FreeStyleProject p = j.createFreeStyleProject();
p.getPublishersList().add(new TrueFalsePublisher(true)); // noop
p.getPublishersList().add(new IOExceptionPublisher()); // fail with IOException
p.getPublishersList().add(new ResultWriterPublisher("result.txt")); //catch result to file
final ArtifactArchiver artifactArchiver = new ArtifactArchiver("result.txt");
artifactArchiver.setOnlyIfSuccessful(false);
p.getPublishersList().add(artifactArchiver); // transfer file to build dir
FreeStyleBuild b = p.scheduleBuild2(0).get();
assertEquals("Build must fail, because we used FalsePublisher", b.getResult(), Result.FAILURE);
j.assertLogContains("\tat hudson.model.utils.IOExceptionPublisher", b); // log must contain stacktrace
j.assertLogContains("Threw IOException from publisher!", b); // log must contain exact error message
File file = new File(b.getArtifactsDir(), "result.txt");
assertTrue("ArtifactArchiver is executed even prior publisher fails", file.exists());
assertTrue("Third publisher must see FAILURE status",
FileUtils.readFileToString(file).equals(Result.FAILURE.toString()));
}
}
......@@ -103,7 +103,7 @@ public class NodeTest {
computer.doToggleOffline("original message");
cause = (UserCause) computer.getOfflineCause();
assertEquals("Disconnected by someone@somewhere.com : original message", cause.toString());
assertTrue(cause.toString(), cause.toString().matches("^.*?Disconnected by someone@somewhere.com : original message"));
assertEquals(someone, cause.getUser());
final User root = User.get("root@localhost");
......@@ -111,7 +111,7 @@ public class NodeTest {
computer.doChangeOfflineCause("new message");
cause = (UserCause) computer.getOfflineCause();
assertEquals("Disconnected by root@localhost : new message", cause.toString());
assertTrue(cause.toString(), cause.toString().matches("^.*?Disconnected by root@localhost : new message"));
assertEquals(root, cause.getUser());
computer.doToggleOffline(null);
......
package hudson.model.utils;
import hudson.AbortException;
import hudson.Extension;
import hudson.Launcher;
import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.model.BuildListener;
import hudson.tasks.BuildStepDescriptor;
import hudson.tasks.BuildStepMonitor;
import hudson.tasks.Publisher;
import hudson.tasks.Recorder;
import java.io.IOException;
/**
* Publisher that throws AbortException
*/
public class AbortExceptionPublisher extends Recorder {
@Override
public boolean perform(AbstractBuild<?, ?> build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException {
throw new AbortException("Threw AbortException from publisher!");
}
@Override
public BuildStepMonitor getRequiredMonitorService() { return BuildStepMonitor.NONE; }
@Extension
public static class DescriptorImpl extends BuildStepDescriptor<Publisher> {
@Override
public String getDisplayName() { return "ThrowAbortExceptionRecorder"; }
@Override
public boolean isApplicable(Class<? extends AbstractProject> jobType) { return true; }
}
}
package hudson.model.utils;
import hudson.Extension;
import hudson.Launcher;
import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.model.BuildListener;
import hudson.tasks.BuildStepDescriptor;
import hudson.tasks.BuildStepMonitor;
import hudson.tasks.Publisher;
import hudson.tasks.Recorder;
import java.io.IOException;
/**
* Publisher that throw IOException
* @author Kanstantsin Shautsou
*/
public class IOExceptionPublisher extends Recorder {
@Override
public boolean perform(AbstractBuild<?, ?> build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException {
throw new IOException("Threw IOException from publisher!");
}
@Override
public BuildStepMonitor getRequiredMonitorService() { return BuildStepMonitor.NONE; }
@Extension
public static class DescriptorImpl extends BuildStepDescriptor<Publisher> {
@Override
public String getDisplayName() { return "Throw IOException Publisher"; }
@Override
public boolean isApplicable(Class<? extends AbstractProject> jobType) { return true; }
}
}
package hudson.model.utils;
import hudson.Extension;
import hudson.FilePath;
import hudson.Launcher;
import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.model.BuildListener;
import hudson.tasks.BuildStepDescriptor;
import hudson.tasks.BuildStepMonitor;
import hudson.tasks.Publisher;
import hudson.tasks.Recorder;
import java.io.IOException;
import java.nio.charset.Charset;
/**
* Wrote build status to file
* @author Kanstantsin Shautsou
*/
public class ResultWriterPublisher extends Recorder {
private final String fileName;
public ResultWriterPublisher(String fileName) {
this.fileName = fileName;
}
@Override
public BuildStepMonitor getRequiredMonitorService() {
return BuildStepMonitor.NONE;
}
@Override
public boolean perform(AbstractBuild<?, ?> build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException {
FilePath file = build.getWorkspace().child(fileName);
file.write(build.getResult().toString(), Charset.defaultCharset().name());
return true;
}
@Extension
public static class DescriptorImpl extends BuildStepDescriptor<Publisher> {
@Override
public String getDisplayName() { return "wrote result to file"; }
@Override
public boolean isApplicable(Class<? extends AbstractProject> jobType) { return true; }
}
}
package hudson.model.utils;
import hudson.Extension;
import hudson.Launcher;
import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.model.BuildListener;
import hudson.tasks.BuildStepDescriptor;
import hudson.tasks.BuildStepMonitor;
import hudson.tasks.Publisher;
import hudson.tasks.Recorder;
import org.kohsuke.stapler.DataBoundConstructor;
import java.io.IOException;
/**
* @author Kanstantsin Shautsou
*/
public class TrueFalsePublisher extends Recorder {
private final boolean b;
@DataBoundConstructor
public TrueFalsePublisher(boolean b) {
this.b = b;
}
@Override
public boolean perform(AbstractBuild<?, ?> build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException {
return b;
}
@Override
public BuildStepMonitor getRequiredMonitorService() { return BuildStepMonitor.NONE; }
@Extension
public static class DescriptorImpl extends BuildStepDescriptor<Publisher> {
@Override
public String getDisplayName() { return "return true or false"; }
@Override
public boolean isApplicable(Class<? extends AbstractProject> jobType) { return true; }
}
}
/*
* The MIT License
*
* Copyright (c) 2015 Red Hat, 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.
*/
package hudson.slaves;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
import hudson.Functions;
import hudson.model.Node;
import java.util.Collections;
import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.JenkinsRule;
public class CommandLauncherTest {
@Rule
public JenkinsRule j = new JenkinsRule();
@Test
public void commandFails() throws Exception {
assumeTrue(!Functions.isWindows());
DumbSlave slave = createSlave("false");
String log = slave.toComputer().getLog();
assertTrue(log, slave.toComputer().isOffline());
assertThat(log, containsString("ERROR: Process terminated with exit code"));
assertThat(log, not(containsString("ERROR: Process terminated with exit code 0")));
}
@Test
public void commandSuceedsWithoutChannel() throws Exception {
assumeTrue(!Functions.isWindows());
DumbSlave slave = createSlave("true");
String log = slave.toComputer().getLog();
assertTrue(log, slave.toComputer().isOffline());
assertThat(log, containsString("ERROR: Process terminated with exit code 0"));
}
public DumbSlave createSlave(String command) throws Exception {
DumbSlave slave;
synchronized (j.jenkins) {
slave = new DumbSlave(
"dummy",
"dummy",
j.createTmpDir().getPath(),
"1",
Node.Mode.NORMAL,
"",
new CommandLauncher(command),
RetentionStrategy.NOOP,
Collections.EMPTY_LIST
);
j.jenkins.addNode(slave);
}
Thread.sleep(100);
return slave;
}
}
......@@ -262,7 +262,7 @@ public class MavenTest {
}
@Issue("JENKINS-18898")
public void testNullHome() throws Exception {
@Test public void testNullHome() {
EnvVars env = new EnvVars();
new MavenInstallation("_", "", Collections.<ToolProperty<?>>emptyList()).buildEnvVars(env);
assertEquals("{}", env.toString());
......
......@@ -23,16 +23,27 @@
*/
package hudson.util;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
import hudson.Functions;
import hudson.Launcher.LocalLauncher;
import hudson.Launcher.RemoteLauncher;
import hudson.Proc;
import hudson.model.Slave;
import org.apache.tools.ant.util.JavaEnvUtils;
import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.Email;
import org.jvnet.hudson.test.JenkinsRule;
import com.google.common.base.Joiner;
import java.io.ByteArrayOutputStream;
import java.io.StringWriter;
/**
......@@ -61,4 +72,41 @@ public class ArgumentListBuilder2Test {
System.out.println(out);
assertTrue(out.toString().contains("$ java ********"));
}
@Test
public void ensureArgumentsArePassedViaCmdExeUnmodified() throws Exception {
assumeTrue(Functions.isWindows());
String[] specials = new String[] {
"~", "!", "@", "#", "$", "%", "^", "&", "*", "(", ")",
"_", "+", "{", "}", "[", "]", ":", ";", "\"", "'", "\\", "|",
"<", ">", ",", ".", "/", "?", " "
};
String out = echoArgs(specials);
String expected = String.format("%n%s", Joiner.on(" ").join(specials));
assertThat(out, containsString(expected));
}
public String echoArgs(String... arguments) throws Exception {
ArgumentListBuilder args = new ArgumentListBuilder(JavaEnvUtils.getJreExecutable("java"), "-cp", "target/test-classes/", "hudson.util.EchoCommand");
args.add(arguments);
args = args.toWindowsCommand();
ByteArrayOutputStream out = new ByteArrayOutputStream();
final StreamTaskListener listener = new StreamTaskListener(out);
Proc p = new LocalLauncher(listener)
.launch()
.stderr(System.err)
.stdout(out)
.cmds(args)
.start()
;
int code = p.join();
listener.close();
assertThat(code, equalTo(0));
return out.toString();
}
}
/*
* The MIT License
*
* Copyright (c) 2015 Red Hat, 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.
*/
package hudson.util;
/**
* Command line utility to print all arguments as provided.
*
* @author ogondza
*/
public final class EchoCommand {
public static void main(String[] args) {
StringBuilder sb = new StringBuilder();
for (String a: args) {
if (sb.length() != 0) sb.append(' ');
sb.append(a);
}
System.out.println(sb.toString());
}
}
<?xml version="1.0" encoding="UTF-8"?>
<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:f="/lib/form">
<f:block>
<f:repeatableHeteroProperty field="ds"/>
</f:block>
</j:jelly>
<?xml version="1.0" encoding="UTF-8"?>
<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:f="/lib/form">
<f:block>
<f:repeatableHeteroProperty field="ds"/>
</f:block>
</j:jelly>
<?xml version="1.0" encoding="UTF-8"?>
<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core"/>
<?xml version="1.0" encoding="UTF-8"?>
<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core"/>
<?xml version="1.0" encoding="UTF-8"?>
<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core"/>
......@@ -28,7 +28,7 @@ THE SOFTWARE.
<parent>
<groupId>org.jenkins-ci.main</groupId>
<artifactId>pom</artifactId>
<version>1.612-SNAPSHOT</version>
<version>1.614-SNAPSHOT</version>
</parent>
<artifactId>jenkins-war</artifactId>
......@@ -47,7 +47,7 @@ THE SOFTWARE.
<dependency>
<groupId>org.jenkins-ci</groupId>
<artifactId>executable-war</artifactId>
<version>1.29</version>
<version>1.30</version>
<scope>provided</scope>
</dependency>
<dependency>
......@@ -322,7 +322,7 @@ THE SOFTWARE.
<artifactItem>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>ldap</artifactId>
<version>1.6</version>
<version>1.11</version>
<type>hpi</type>
</artifactItem>
<artifactItem>
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册