diff --git a/core/src/main/java/hudson/ClassicPluginStrategy.java b/core/src/main/java/hudson/ClassicPluginStrategy.java index bc00b2a0c9..6503d4b639 100644 --- a/core/src/main/java/hudson/ClassicPluginStrategy.java +++ b/core/src/main/java/hudson/ClassicPluginStrategy.java @@ -25,6 +25,8 @@ package hudson; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; + +import java.io.FileNotFoundException; import java.io.InputStream; import java.nio.file.Files; import java.nio.file.InvalidPathException; @@ -110,11 +112,20 @@ public class ClassicPluginStrategy implements PluginStrategy { @Override public String getShortName(File archive) throws IOException { Manifest manifest; + if (!archive.exists()) { + throw new FileNotFoundException("Failed to load " + archive + ". The file does not exist"); + } else if (!archive.isFile()) { + throw new FileNotFoundException("Failed to load " + archive + ". It is not a file"); + } + if (isLinked(archive)) { manifest = loadLinkedManifest(archive); } else { try (JarFile jf = new JarFile(archive, false)) { manifest = jf.getManifest(); + } catch (IOException ex) { + // Mention file name in the exception + throw new IOException("Failed to load " + archive, ex); } } return PluginWrapper.computeShortName(manifest, archive.getName()); diff --git a/core/src/main/java/hudson/model/Run.java b/core/src/main/java/hudson/model/Run.java index 10ec9f6574..a3bc3375be 100644 --- a/core/src/main/java/hudson/model/Run.java +++ b/core/src/main/java/hudson/model/Run.java @@ -1554,6 +1554,10 @@ public abstract class Run ,RunT extends Runwarning: callers are expected to have called {@link ChannelConfigurator} already + * @param channel the channel to use; warning: callers are expected to have called {@link ChannelConfigurator} already. + * @param launchLog Launch log. If not {@code null}, will receive launch log messages + * @param listener Channel event listener to be attached (if not {@code null}) * @since 1.444 */ - public void setChannel(Channel channel, OutputStream launchLog, Channel.Listener listener) throws IOException, InterruptedException { + public void setChannel(@Nonnull Channel channel, + @CheckForNull OutputStream launchLog, + @CheckForNull Channel.Listener listener) throws IOException, InterruptedException { if(this.channel!=null) throw new IllegalStateException("Already connected"); - final TaskListener taskListener = new StreamTaskListener(launchLog); + final TaskListener taskListener = launchLog != null ? new StreamTaskListener(launchLog) : TaskListener.NULL; PrintStream log = taskListener.getLogger(); channel.setProperty(SlaveComputer.class, this); diff --git a/core/src/main/java/hudson/util/Iterators.java b/core/src/main/java/hudson/util/Iterators.java index 74d82950dc..8d93d72262 100644 --- a/core/src/main/java/hudson/util/Iterators.java +++ b/core/src/main/java/hudson/util/Iterators.java @@ -23,6 +23,7 @@ */ package hudson.util; +import com.google.common.annotations.Beta; import com.google.common.base.Predicates; import com.google.common.collect.ImmutableList; @@ -35,6 +36,9 @@ import java.util.AbstractList; import java.util.Arrays; import java.util.Set; import java.util.HashSet; +import javax.annotation.Nonnull; +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.NoExternalUse; /** * Varios {@link Iterator} implementations. @@ -403,4 +407,20 @@ public class Iterators { public interface CountingPredicate { boolean apply(int index, T input); } + + /** + * Similar to {@link com.google.common.collect.Iterators#skip} except not {@link Beta}. + * @param iterator some iterator + * @param count a nonnegative count + */ + @Restricted(NoExternalUse.class) + public static void skip(@Nonnull Iterator iterator, int count) { + if (count < 0) { + throw new IllegalArgumentException(); + } + while (iterator.hasNext() && count-- > 0) { + iterator.next(); + } + } + } diff --git a/core/src/main/java/hudson/util/RunList.java b/core/src/main/java/hudson/util/RunList.java index d9b8adb7dc..bdc9cb61f4 100644 --- a/core/src/main/java/hudson/util/RunList.java +++ b/core/src/main/java/hudson/util/RunList.java @@ -150,7 +150,7 @@ public class RunList extends AbstractList { public List subList(int fromIndex, int toIndex) { List r = new ArrayList(); Iterator itr = iterator(); - Iterators.skip(itr,fromIndex); + hudson.util.Iterators.skip(itr, fromIndex); for (int i=toIndex-fromIndex; i>0; i--) { r.add(itr.next()); } diff --git a/core/src/main/java/hudson/util/StreamTaskListener.java b/core/src/main/java/hudson/util/StreamTaskListener.java index bd2b6dc87e..b2b2e4fd62 100644 --- a/core/src/main/java/hudson/util/StreamTaskListener.java +++ b/core/src/main/java/hudson/util/StreamTaskListener.java @@ -44,6 +44,9 @@ import java.util.logging.Level; import java.util.logging.Logger; import org.kohsuke.stapler.framework.io.WriterOutputStream; +import javax.annotation.CheckForNull; +import javax.annotation.Nonnull; + // TODO: AbstractTaskListener is empty now, but there are dependencies on that e.g. Ruby Runtime - JENKINS-48116) // The change needs API deprecation policy or external usages cleanup. @@ -56,7 +59,9 @@ import org.kohsuke.stapler.framework.io.WriterOutputStream; * @author Kohsuke Kawaguchi */ public class StreamTaskListener extends AbstractTaskListener implements TaskListener, Closeable { + @Nonnull private PrintStream out; + @CheckForNull private Charset charset; /** @@ -66,15 +71,15 @@ public class StreamTaskListener extends AbstractTaskListener implements TaskList * or use {@link #fromStdout()} or {@link #fromStderr()}. */ @Deprecated - public StreamTaskListener(PrintStream out) { + public StreamTaskListener(@Nonnull PrintStream out) { this(out,null); } - public StreamTaskListener(OutputStream out) { + public StreamTaskListener(@Nonnull OutputStream out) { this(out,null); } - public StreamTaskListener(OutputStream out, Charset charset) { + public StreamTaskListener(@Nonnull OutputStream out, @CheckForNull Charset charset) { try { if (charset == null) this.out = (out instanceof PrintStream) ? (PrintStream)out : new PrintStream(out, false); @@ -87,18 +92,18 @@ public class StreamTaskListener extends AbstractTaskListener implements TaskList } } - public StreamTaskListener(File out) throws IOException { + public StreamTaskListener(@Nonnull File out) throws IOException { this(out,null); } - public StreamTaskListener(File out, Charset charset) throws IOException { + public StreamTaskListener(@Nonnull File out, @CheckForNull Charset charset) throws IOException { // don't do buffering so that what's written to the listener // gets reflected to the file immediately, which can then be // served to the browser immediately this(Files.newOutputStream(asPath(out)), charset); } - private static Path asPath(File out) throws IOException { + private static Path asPath(@Nonnull File out) throws IOException { try { return out.toPath(); } catch (InvalidPathException e) { @@ -115,7 +120,7 @@ public class StreamTaskListener extends AbstractTaskListener implements TaskList * @throws IOException if the file could not be opened. * @since 1.651 */ - public StreamTaskListener(File out, boolean append, Charset charset) throws IOException { + public StreamTaskListener(@Nonnull File out, boolean append, @CheckForNull Charset charset) throws IOException { // don't do buffering so that what's written to the listener // gets reflected to the file immediately, which can then be // served to the browser immediately @@ -127,7 +132,7 @@ public class StreamTaskListener extends AbstractTaskListener implements TaskList ); } - public StreamTaskListener(Writer w) throws IOException { + public StreamTaskListener(@Nonnull Writer w) throws IOException { this(new WriterOutputStream(w)); } @@ -155,7 +160,7 @@ public class StreamTaskListener extends AbstractTaskListener implements TaskList @Override public Charset getCharset() { - return charset; + return charset != null ? charset : Charset.defaultCharset(); } private void writeObject(ObjectOutputStream out) throws IOException { diff --git a/core/src/main/java/jenkins/widgets/HistoryPageFilter.java b/core/src/main/java/jenkins/widgets/HistoryPageFilter.java index 269f5e44fc..521ace8b79 100644 --- a/core/src/main/java/jenkins/widgets/HistoryPageFilter.java +++ b/core/src/main/java/jenkins/widgets/HistoryPageFilter.java @@ -24,7 +24,6 @@ package jenkins.widgets; import com.google.common.collect.Iterables; -import com.google.common.collect.Iterators; import hudson.model.AbstractBuild; import hudson.model.Job; import hudson.model.ParameterValue; @@ -32,6 +31,7 @@ import hudson.model.ParametersAction; import hudson.model.Queue; import hudson.model.Run; import hudson.search.UserSearchProperty; +import hudson.util.Iterators; import hudson.widgets.HistoryWidget; import javax.annotation.Nonnull; diff --git a/core/src/test/java/hudson/util/IteratorsTest.java b/core/src/test/java/hudson/util/IteratorsTest.java index 679df76a65..4236ca359d 100644 --- a/core/src/test/java/hudson/util/IteratorsTest.java +++ b/core/src/test/java/hudson/util/IteratorsTest.java @@ -32,6 +32,7 @@ import java.util.Iterator; import java.util.List; import org.junit.Test; +import org.jvnet.hudson.test.Issue; /** * @author Kohsuke Kawaguchi @@ -77,9 +78,27 @@ public class IteratorsTest { assertEquals("[]", com.google.common.collect.Iterators.toString(Iterators.limit(asList(1,2,4,6).iterator(), EVEN))); } - public static final CountingPredicate EVEN = new CountingPredicate() { - public boolean apply(int index, Integer input) { - return input % 2 == 0; - } - }; + public static final CountingPredicate EVEN = (index, input) -> input % 2 == 0; + + @Issue("JENKINS-51779") + @Test + public void skip() { + List lst = Iterators.sequence(1, 4); + Iterator it = lst.iterator(); + Iterators.skip(it, 0); + assertEquals("[1, 2, 3]", com.google.common.collect.Iterators.toString(it)); + it = lst.iterator(); + Iterators.skip(it, 1); + assertEquals("[2, 3]", com.google.common.collect.Iterators.toString(it)); + it = lst.iterator(); + Iterators.skip(it, 2); + assertEquals("[3]", com.google.common.collect.Iterators.toString(it)); + it = lst.iterator(); + Iterators.skip(it, 3); + assertEquals("[]", com.google.common.collect.Iterators.toString(it)); + it = lst.iterator(); + Iterators.skip(it, 4); + assertEquals("[]", com.google.common.collect.Iterators.toString(it)); + } + } diff --git a/pom.xml b/pom.xml index aeba985b12..7b6592c7a0 100644 --- a/pom.xml +++ b/pom.xml @@ -107,7 +107,7 @@ THE SOFTWARE. 3.0.0 - 3.20 + 3.21 2.60 diff --git a/test/src/test/java/hudson/model/RunTest.java b/test/src/test/java/hudson/model/RunTest.java index 92cbef2081..7150aac135 100644 --- a/test/src/test/java/hudson/model/RunTest.java +++ b/test/src/test/java/hudson/model/RunTest.java @@ -23,19 +23,30 @@ */ package hudson.model; +import hudson.FilePath; +import hudson.Launcher; +import hudson.tasks.ArtifactArchiver; +import java.io.IOException; import java.net.HttpURLConnection; import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; +import jenkins.model.ArtifactManager; +import jenkins.model.ArtifactManagerConfiguration; +import jenkins.model.ArtifactManagerFactory; +import jenkins.model.ArtifactManagerFactoryDescriptor; +import jenkins.model.Jenkins; +import jenkins.util.VirtualFile; import static org.junit.Assert.*; 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; -/** - * @author Kohsuke Kawaguchi - */ public class RunTest { @Rule public JenkinsRule j = new JenkinsRule(); @@ -73,4 +84,33 @@ public class RunTest { assertEquals(Run.KeepLogBuildBadge.class, badgeActions.get(0).getClass()); } + @Issue("JENKINS-51819") + @Test public void deleteArtifactsCustom() throws Exception { + ArtifactManagerConfiguration.get().getArtifactManagerFactories().add(new Mgr.Factory()); + FreeStyleProject p = j.createFreeStyleProject(); + j.jenkins.getWorkspaceFor(p).child("f").write("", null); + p.getPublishersList().add(new ArtifactArchiver("f")); + FreeStyleBuild b = j.buildAndAssertSuccess(p); + b.delete(); + assertTrue(Mgr.deleted.get()); + } + private static final class Mgr extends ArtifactManager { + static final AtomicBoolean deleted = new AtomicBoolean(); + @Override public boolean delete() throws IOException, InterruptedException { + return !deleted.getAndSet(true); + } + @Override public void onLoad(Run build) {} + @Override public void archive(FilePath workspace, Launcher launcher, BuildListener listener, Map artifacts) throws IOException, InterruptedException {} + @Override public VirtualFile root() { + return VirtualFile.forFile(Jenkins.get().getRootDir()); // irrelevant + } + public static final class Factory extends ArtifactManagerFactory { + @DataBoundConstructor public Factory() {} + @Override public ArtifactManager managerFor(Run build) { + return new Mgr(); + } + @TestExtension("deleteArtifactsCustom") public static final class DescriptorImpl extends ArtifactManagerFactoryDescriptor {} + } + } + }