diff --git a/changelog.html b/changelog.html index 2f749666a2..ba00204bf6 100644 --- a/changelog.html +++ b/changelog.html @@ -56,11 +56,17 @@ Upcoming changes diff --git a/core/src/main/java/hudson/Plugin.java b/core/src/main/java/hudson/Plugin.java index 20f671e8dc..e2a061fbda 100644 --- a/core/src/main/java/hudson/Plugin.java +++ b/core/src/main/java/hudson/Plugin.java @@ -48,7 +48,10 @@ import com.thoughtworks.xstream.XStream; * Base class of Hudson plugin. * *

- * A plugin needs to derive from this class. + * A plugin may derive from this class, or it may directly define extension + * points annotated with {@link hudson.Extension}. For a list of extension + * points, see + * https://wiki.jenkins-ci.org/display/JENKINS/Extension+points. * *

* One instance of a plugin is created by Hudson, and used as the entry point diff --git a/core/src/main/java/hudson/model/LoadStatistics.java b/core/src/main/java/hudson/model/LoadStatistics.java index 81ebae78de..b996f040b4 100644 --- a/core/src/main/java/hudson/model/LoadStatistics.java +++ b/core/src/main/java/hudson/model/LoadStatistics.java @@ -233,7 +233,7 @@ public abstract class LoadStatistics { int q=0; for (Queue.BuildableItem bi : bis) { for (SubTask st : Tasks.getSubTasksOf(bi.task)) - if (st.getAssignedLabel()==l) + if (bi.getAssignedLabelFor(st)==l) q++; } return q; diff --git a/core/src/main/java/hudson/model/Queue.java b/core/src/main/java/hudson/model/Queue.java index 830eb851f6..da478515f4 100644 --- a/core/src/main/java/hudson/model/Queue.java +++ b/core/src/main/java/hudson/model/Queue.java @@ -834,11 +834,11 @@ public class Queue extends ResourceController implements Saveable { int r = 0; for (BuildableItem bi : buildables.values()) for (SubTask st : bi.task.getSubTasks()) - if (null==l || st.getAssignedLabel()==l) + if (null==l || bi.getAssignedLabelFor(st)==l) r++; for (BuildableItem bi : pendings.values()) for (SubTask st : bi.task.getSubTasks()) - if (null==l || st.getAssignedLabel()==l) + if (null==l || bi.getAssignedLabelFor(st)==l) r++; return r; } @@ -1414,6 +1414,21 @@ public class Queue extends ResourceController implements Saveable { } return task.getAssignedLabel(); } + + /** + * Test if the specified {@link SubTask} needs to be run on a node with a particular label, and + * return that {@link Label}. Otherwise null, indicating it can run on anywhere. + * + *

+ * This code takes {@link LabelAssignmentAction} into account, then falls back to {@link SubTask#getAssignedLabel()} + */ + public Label getAssignedLabelFor(SubTask st) { + for (LabelAssignmentAction laa : getActions(LabelAssignmentAction.class)) { + Label l = laa.getAssignedLabel(st); + if (l!=null) return l; + } + return st.getAssignedLabel(); + } /** * Convenience method that returns a read only view of the {@link Cause}s associated with this item in the queue. diff --git a/core/src/main/java/hudson/model/labels/LabelAtomProperty.java b/core/src/main/java/hudson/model/labels/LabelAtomProperty.java index e0dfd53259..efaad2ab77 100644 --- a/core/src/main/java/hudson/model/labels/LabelAtomProperty.java +++ b/core/src/main/java/hudson/model/labels/LabelAtomProperty.java @@ -42,6 +42,7 @@ import java.util.Collections; * with the {@link LabelAtom} object. * * @author Kohsuke Kawaguchi + * @see hudson.model.labels.LabelAtomPropertyDescriptor * @since 1.373 */ @ExportedBean diff --git a/core/src/main/java/hudson/model/labels/LabelAtomPropertyDescriptor.java b/core/src/main/java/hudson/model/labels/LabelAtomPropertyDescriptor.java index 43724c73c4..82312097dc 100644 --- a/core/src/main/java/hudson/model/labels/LabelAtomPropertyDescriptor.java +++ b/core/src/main/java/hudson/model/labels/LabelAtomPropertyDescriptor.java @@ -31,6 +31,11 @@ import hudson.model.Descriptor; * *

* Put {@link Extension} on your descriptor implementation to have it auto-registered. + * + *

+ * When extending this class, override Descriptor.getDisplayName(). In the + * context of LabelAtomPropertyDescriptor, this function is used to determine the label of + * the checkbox shown in the label's configuration page to activate the extension. * * @author Kohsuke Kawaguchi * @since 1.373 diff --git a/core/src/main/java/hudson/tasks/BatchFile.java b/core/src/main/java/hudson/tasks/BatchFile.java index 0943e9147f..b2506474be 100644 --- a/core/src/main/java/hudson/tasks/BatchFile.java +++ b/core/src/main/java/hudson/tasks/BatchFile.java @@ -26,9 +26,11 @@ package hudson.tasks; import hudson.FilePath; import hudson.Extension; import hudson.model.AbstractProject; +import hudson.util.LineEndingConversion; import net.sf.json.JSONObject; import org.kohsuke.stapler.DataBoundConstructor; import org.kohsuke.stapler.StaplerRequest; +import java.io.ObjectStreamException; /** * Executes commands by using Windows batch file. @@ -38,7 +40,7 @@ import org.kohsuke.stapler.StaplerRequest; public class BatchFile extends CommandInterpreter { @DataBoundConstructor public BatchFile(String command) { - super(command); + super(LineEndingConversion.convertEOL(command, LineEndingConversion.EOLType.Windows)); } public String[] buildCommandLine(FilePath script) { @@ -46,13 +48,17 @@ public class BatchFile extends CommandInterpreter { } protected String getContents() { - return command+"\r\nexit %ERRORLEVEL%"; + return LineEndingConversion.convertEOL(command+"\r\nexit %ERRORLEVEL%",LineEndingConversion.EOLType.Windows); } protected String getFileExtension() { return ".bat"; } + private Object readResolve() throws ObjectStreamException { + return new BatchFile(command); + } + @Extension public static final class DescriptorImpl extends BuildStepDescriptor { @Override diff --git a/core/src/main/java/hudson/tasks/Shell.java b/core/src/main/java/hudson/tasks/Shell.java index 5a3a2053db..17ce0b8819 100644 --- a/core/src/main/java/hudson/tasks/Shell.java +++ b/core/src/main/java/hudson/tasks/Shell.java @@ -32,6 +32,8 @@ import hudson.remoting.Callable; import hudson.remoting.VirtualChannel; import hudson.util.FormValidation; import java.io.IOException; +import java.io.ObjectStreamException; +import hudson.util.LineEndingConversion; import net.sf.json.JSONObject; import org.kohsuke.stapler.DataBoundConstructor; import org.kohsuke.stapler.StaplerRequest; @@ -50,29 +52,7 @@ import java.util.logging.Logger; public class Shell extends CommandInterpreter { @DataBoundConstructor public Shell(String command) { - super(fixCrLf(command)); - } - - /** - * Fix CR/LF and always make it Unix style. - */ - private static String fixCrLf(String s) { - // eliminate CR - int idx; - while((idx=s.indexOf("\r\n"))!=-1) - s = s.substring(0,idx)+s.substring(idx+1); - - //// add CR back if this is for Windows - //if(isWindows()) { - // idx=0; - // while(true) { - // idx = s.indexOf('\n',idx); - // if(idx==-1) break; - // s = s.substring(0,idx)+'\r'+s.substring(idx); - // idx+=2; - // } - //} - return s; + super(LineEndingConversion.convertEOL(command, LineEndingConversion.EOLType.Unix)); } /** @@ -80,7 +60,7 @@ public class Shell extends CommandInterpreter { * makes the shell think the file is a binary file and not a script. Adding * a leading line feed works around this problem. */ - private static String addCrForNonASCII(String s) { + private static String addLineFeedForNonASCII(String s) { if(!s.startsWith("#!")) { if (s.indexOf('\n')!=0) { return "\n" + s; @@ -105,7 +85,7 @@ public class Shell extends CommandInterpreter { } protected String getContents() { - return addCrForNonASCII(fixCrLf(command)); + return addLineFeedForNonASCII(LineEndingConversion.convertEOL(command,LineEndingConversion.EOLType.Unix)); } protected String getFileExtension() { @@ -117,6 +97,10 @@ public class Shell extends CommandInterpreter { return (DescriptorImpl)super.getDescriptor(); } + private Object readResolve() throws ObjectStreamException { + return new Shell(command); + } + @Extension public static class DescriptorImpl extends BuildStepDescriptor { /** diff --git a/core/src/main/java/hudson/tools/AbstractCommandInstaller.java b/core/src/main/java/hudson/tools/AbstractCommandInstaller.java index 07d91c6c0c..18431fcda1 100644 --- a/core/src/main/java/hudson/tools/AbstractCommandInstaller.java +++ b/core/src/main/java/hudson/tools/AbstractCommandInstaller.java @@ -48,7 +48,7 @@ public abstract class AbstractCommandInstaller extends ToolInstaller { public AbstractCommandInstaller(String label, String command, String toolHome) { super(label); - this.command = fixCrLf(command); + this.command = command; this.toolHome = toolHome; } @@ -84,19 +84,6 @@ public abstract class AbstractCommandInstaller extends ToolInstaller { return dir.child(getToolHome()); } - /** - * Fix CR/LF and always make it Unix style. - */ - //TODO: replace by a Windows style - private static String fixCrLf(String s) { - // eliminate CR - int idx; - while ((idx = s.indexOf("\r\n")) != -1) { - s = s.substring(0, idx) + s.substring(idx + 1); - } - return s; - } - public static abstract class Descriptor extends ToolInstallerDescriptor { diff --git a/core/src/main/java/hudson/tools/BatchCommandInstaller.java b/core/src/main/java/hudson/tools/BatchCommandInstaller.java index 415dbb700f..2085fe7fa0 100644 --- a/core/src/main/java/hudson/tools/BatchCommandInstaller.java +++ b/core/src/main/java/hudson/tools/BatchCommandInstaller.java @@ -26,7 +26,9 @@ package hudson.tools; import hudson.Extension; import hudson.FilePath; +import hudson.util.LineEndingConversion; import org.kohsuke.stapler.DataBoundConstructor; +import java.io.ObjectStreamException; /** * Installs tool via script execution of Batch script. @@ -37,7 +39,7 @@ public class BatchCommandInstaller extends AbstractCommandInstaller { @DataBoundConstructor public BatchCommandInstaller(String label, String command, String toolHome) { - super(label, command, toolHome); + super(label, LineEndingConversion.convertEOL(command, LineEndingConversion.EOLType.Windows), toolHome); } @Override @@ -50,6 +52,10 @@ public class BatchCommandInstaller extends AbstractCommandInstaller { String[] cmd = {"cmd", "/c", "call", script.getRemote()}; return cmd; } + + private Object readResolve() throws ObjectStreamException { + return new BatchCommandInstaller(getLabel(), getCommand(), getToolHome()); + } @Extension public static class DescriptorImpl extends Descriptor { diff --git a/core/src/main/java/hudson/tools/CommandInstaller.java b/core/src/main/java/hudson/tools/CommandInstaller.java index b02137f0f5..6f2506dd04 100644 --- a/core/src/main/java/hudson/tools/CommandInstaller.java +++ b/core/src/main/java/hudson/tools/CommandInstaller.java @@ -26,7 +26,9 @@ package hudson.tools; import hudson.Extension; import hudson.FilePath; +import hudson.util.LineEndingConversion; import org.kohsuke.stapler.DataBoundConstructor; +import java.io.ObjectStreamException; /** * Installs a tool by running an arbitrary shell command. @@ -36,7 +38,7 @@ public class CommandInstaller extends AbstractCommandInstaller { @DataBoundConstructor public CommandInstaller(String label, String command, String toolHome) { - super(label, command, toolHome); + super(label, LineEndingConversion.convertEOL(command, LineEndingConversion.EOLType.Unix), toolHome); } @Override @@ -50,6 +52,10 @@ public class CommandInstaller extends AbstractCommandInstaller { return cmd; } + private Object readResolve() throws ObjectStreamException { + return new CommandInstaller(getLabel(), getCommand(), getToolHome()); + } + @Extension public static class DescriptorImpl extends Descriptor { diff --git a/core/src/main/java/hudson/util/LineEndingConversion.java b/core/src/main/java/hudson/util/LineEndingConversion.java new file mode 100644 index 0000000000..744ff4a4ea --- /dev/null +++ b/core/src/main/java/hudson/util/LineEndingConversion.java @@ -0,0 +1,66 @@ +package hudson.util; + +/** + * Converts line endings of a string. + * + * @since TODO + * @author David Ruhmann + */ +public class LineEndingConversion { + + /** + * Supported line ending types for conversion + */ + public enum EOLType { + CR, + CRLF, + LF, + LFCR, + Mac, + Unix, + Windows + } + + /** + * Convert line endings of a string to the given type. Default to Unix type. + * + * @param input + * The string containing line endings to be converted. + * @param type + * Type of line endings to convert the string into. + * @return + * String updated with the new line endings or null if given null. + */ + public static String convertEOL(String input, EOLType type) { + if (null == input || 0 == input.length()) { + return input; + } + // Convert line endings to Unix LF, + // which also sets up the string for other conversions + input = input.replace("\r\n","\n"); + input = input.replace("\r","\n"); + switch (type) { + case CR: + case Mac: + // Convert line endings to CR + input = input.replace("\n", "\r"); + break; + case CRLF: + case Windows: + // Convert line endings to Windows CR/LF + input = input.replace("\n", "\r\n"); + break; + default: + case LF: + case Unix: + // Conversion already completed + return input; + case LFCR: + // Convert line endings to LF/CR + input = input.replace("\n", "\n\r"); + break; + } + return input; + } +} + diff --git a/core/src/main/java/jenkins/model/GlobalConfiguration.java b/core/src/main/java/jenkins/model/GlobalConfiguration.java index b6e873f952..92eabc4ea7 100644 --- a/core/src/main/java/jenkins/model/GlobalConfiguration.java +++ b/core/src/main/java/jenkins/model/GlobalConfiguration.java @@ -6,17 +6,28 @@ import hudson.model.Describable; import hudson.model.Descriptor; /** - * Convenient base class for extensions that contributes to the system configuration page but nothing else. + * Convenient base class for extensions that contributes to the system configuration page but nothing + * else, or to manage the global configuration of a plugin implementing several extension points. * *

- * All {@link Descriptor}s are capable of contributing fragment to the system config page, so - * this extension point is is really only for those who don't want to contribute anything else. - * If you are implementing other extension points and that would like to expose some global configuration, - * you can do so with global.groovy from your {@link Descriptor} instance. + * All {@link Descriptor}s are capable of contributing fragment to the system config page. If you are + * implementing other extension points that need to expose some global configuration, you can do so + * with global.groovy or global.jelly from your {@link Descriptor} instance. However + * each global.* file will appear as its own section in the global configuration page. + * + *

+ * An option to present a single section for your plugin in the Jenkins global configuration page is + * to use this class to manage the configuration for your plugin and its extension points. To access + * properties defined in your GlobalConfiguration subclass, here are two possibilities: + *

* *

Views

*

- * Subtypes of this class should define config.groovy that gets pulled into the system configuration page. + * Subtypes of this class should define a config.groovy file or config.jelly file + * that gets pulled into the system configuration page. * * * @author Kohsuke Kawaguchi diff --git a/test/src/test/java/hudson/model/LabelLoadStatisticsQueueLengthTest.java b/test/src/test/java/hudson/model/LabelLoadStatisticsQueueLengthTest.java new file mode 100644 index 0000000000..18a957f3db --- /dev/null +++ b/test/src/test/java/hudson/model/LabelLoadStatisticsQueueLengthTest.java @@ -0,0 +1,240 @@ +package hudson.model; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import hudson.model.Descriptor.FormException; +import hudson.model.LoadStatistics.LoadStatisticsUpdater; +import hudson.model.MultiStageTimeSeries.TimeScale; +import hudson.model.Node.Mode; +import hudson.model.Queue.WaitingItem; +import hudson.model.labels.LabelAssignmentAction; +import hudson.model.queue.SubTask; +import hudson.slaves.DumbSlave; +import hudson.slaves.NodeProperty; +import hudson.slaves.RetentionStrategy; + +import java.io.IOException; +import java.util.Collections; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.jvnet.hudson.test.Bug; +import org.jvnet.hudson.test.JenkinsRule; + +/** + * Test that a {@link Label}'s {@link LoadStatistics#queueLength} correctly + * reflects the queued builds. + */ +public class LabelLoadStatisticsQueueLengthTest { + private static final String LABEL_STRING = LabelLoadStatisticsQueueLengthTest.class + .getSimpleName(); + + private static final String ALT_LABEL_STRING = LABEL_STRING + "alt"; + + private static final String PROJECT_NAME = LabelLoadStatisticsQueueLengthTest.class + .getSimpleName(); + + private static final String PARAMETER_NAME = "parameter"; + + private static final Cause CAUSE = new Cause() { + @Override + public String getShortDescription() { + return "Build caused by test."; + } + }; + + @Rule + public JenkinsRule j = new JenkinsRule(); + + /** + * Setup + */ + @Before + public void createNodeWithLabels() throws IOException, FormException { + // Node with this test's labels is required in order for the labels to + // be considered valid. + DumbSlave node = new DumbSlave( + LabelLoadStatisticsQueueLengthTest.class.getSimpleName(), "", + "", "1", Mode.NORMAL, LABEL_STRING + " " + ALT_LABEL_STRING, + null, RetentionStrategy.NOOP, + Collections.> emptyList()); + j.getInstance().addNode(node); + } + + /** + * Teardown + */ + @After + public void clearQueue() { + j.getInstance().getQueue().clear(); + } + + /** + * Verify that when a {@link Label} is assigned to a queued build using a + * {@link LabelAssignmentAction}, that label's + * {@link LoadStatistics#queueLength} reflects the number of items in the + * queue, and continues to do so if the {@link Project}'s label is changed. + */ + @Test + public void queueLengthReflectsBuildableItemsAssignedLabel() + throws Exception { + final Label label = Label.get(LABEL_STRING); + final Label altLabel = Label.get(ALT_LABEL_STRING); + + FreeStyleProject project = createTestProject(); + + // Before queueing the builds the rolling queue length should be 0. + assertTrue( + "Initially the rolling queue length for the label is 0.", + label.loadStatistics.queueLength.getLatest(TimeScale.SEC10) == 0f); + + // Add the job to the build queue several times with an assigned label. + for (int i = 0; i < 3; i++) { + project.scheduleBuild(0, CAUSE, new LabelAssignmentActionImpl(), + new ParametersAction(new StringParameterValue( + PARAMETER_NAME, String.valueOf(i)))); + } + + // Verify that the real queue length is 3. + assertEquals("The job is queued as often as it was scheduled.", 3, j + .getInstance().getQueue().getItems(project).size()); + + maintainQueueAndForceRunOfLoadStatisticsUpdater(project); + + float labelQueueLength = label.loadStatistics.queueLength + .getLatest(TimeScale.SEC10); + assertTrue( + "After LoadStatisticsUpdater runs, the queue length load statistic for the label is greater than 0.", + labelQueueLength > 0f); + + // Assign an alternate label to the project and update the load stats. + project.setAssignedLabel(altLabel); + maintainQueueAndForceRunOfLoadStatisticsUpdater(project); + + // Verify that the queue length load stat continues to reflect the labels assigned to the items in the queue. + float labelQueueLengthNew = label.loadStatistics.queueLength + .getLatest(TimeScale.SEC10); + assertTrue( + "After assigning an alternate label to the job, the queue length load statistic for the queued builds should not decrease.", + labelQueueLengthNew >= labelQueueLength); + } + + /** + * Verify that when a {@link Label} is assigned to a {@link Project}, that + * label's {@link LoadStatistics#queueLength} reflects the number of items + * in the queue scheduled for that project, and updates if the project's + * label is changed. + */ + @Test + public void queueLengthReflectsJobsAssignedLabel() throws Exception { + final Label label = Label.get(LABEL_STRING); + final Label altLabel = Label.get(ALT_LABEL_STRING); + + FreeStyleProject project = createTestProject(); + // Assign a label to the job. + project.setAssignedLabel(label); + + // Before queueing the builds the rolling queue lengths should be 0. + assertTrue( + "Initially the rolling queue length for the label is 0.", + label.loadStatistics.queueLength.getLatest(TimeScale.SEC10) == 0f); + assertTrue( + "Initially the rolling queue length for the alt label is 0.", + altLabel.loadStatistics.queueLength.getLatest(TimeScale.SEC10) == 0f); + + // Add the job to the build queue several times. + for (int i = 0; i < 3; i++) { + project.scheduleBuild(0, CAUSE, + new ParametersAction(new StringParameterValue( + PARAMETER_NAME, String.valueOf(i)))); + } + + // Verify that the real queue length is 3. + assertEquals("The job is queued as often as it was scheduled.", 3, j + .getInstance().getQueue().getItems(project).size()); + + maintainQueueAndForceRunOfLoadStatisticsUpdater(project); + + float labelQueueLength = label.loadStatistics.queueLength + .getLatest(TimeScale.SEC10); + assertTrue( + "After LoadStatisticsUpdater runs, the queue length load statistic for the label is greater than 0.", + labelQueueLength > 0f); + + // Assign an alternate label to the job and update the load stats. + project.setAssignedLabel(altLabel); + maintainQueueAndForceRunOfLoadStatisticsUpdater(project); + + // Verify that the queue length load stats of the labels reflect the newly project's newly assigned label. + float labelQueueLengthNew = label.loadStatistics.queueLength + .getLatest(TimeScale.SEC10); + assertTrue( + "After assigning an alternate label to the job, the queue length load statistic for the queued builds should decrease.", + labelQueueLengthNew < labelQueueLength); + + float altLabelQueueLength = altLabel.loadStatistics.queueLength + .getLatest(TimeScale.SEC10); + assertTrue( + "After assigning an alternate label to the job, the queue length load statistic for the alternate label should be greater than 0.", + altLabelQueueLength > 0f); + } + + private FreeStyleProject createTestProject() throws IOException { + FreeStyleProject project = j.createFreeStyleProject(PROJECT_NAME); + // In order to queue multiple builds of the job it needs to be + // parameterised. + project.addProperty(new ParametersDefinitionProperty( + new StringParameterDefinition(PARAMETER_NAME, "0"))); + // Prevent builds from being queued as blocked by allowing concurrent + // builds. + project.setConcurrentBuild(true); + return project; + } + + private void maintainQueueAndForceRunOfLoadStatisticsUpdater( + FreeStyleProject project) throws InterruptedException { + Queue queue = j.getInstance().getQueue(); + + // ensure the queued items are in the right state, i.e. buildable rather + // than waiting + int count = 0; + while (queue.getItem(project) instanceof WaitingItem && ++count < 100) { + queue.maintain(); + Thread.sleep(10); + } + + assertTrue( + "After waiting there are buildable items in the build queue.", + queue.getBuildableItems().size() > 0); + + // create a LoadStatisticsUpdater, and run it in order to update the + // load stats for all the labels + LoadStatisticsUpdater updater = new LoadStatisticsUpdater(); + updater.doRun(); + } + + private static class LabelAssignmentActionImpl implements + LabelAssignmentAction { + @Override + public String getIconFileName() { + return null; + } + + @Override + public String getDisplayName() { + return LABEL_STRING + " LabelAssignmentAction"; + } + + @Override + public String getUrlName() { + return null; + } + + @Override + public Label getAssignedLabel(SubTask p_task) { + return Label.get(LABEL_STRING); + } + } +} diff --git a/test/src/test/java/hudson/tasks/BatchFileTest.java b/test/src/test/java/hudson/tasks/BatchFileTest.java new file mode 100644 index 0000000000..8cc16fd10a --- /dev/null +++ b/test/src/test/java/hudson/tasks/BatchFileTest.java @@ -0,0 +1,30 @@ +package hudson.tasks; + +import org.junit.Rule; +import org.junit.Test; +import org.jvnet.hudson.test.Issue; +import org.jvnet.hudson.test.JenkinsRule; + +/** + * Tests for the BatchFile tasks class. + * + * @author David Ruhmann + */ +public class BatchFileTest { + + @Rule + public JenkinsRule rule = new JenkinsRule(); + + @Issue("JENKINS-7478") + @Test + public void validateBatchFileCommandEOL() throws Exception { + BatchFile obj = new BatchFile("echo A\necho B\recho C"); + rule.assertStringContains(obj.getCommand(), "echo A\r\necho B\r\necho C"); + } + + @Test + public void validateBatchFileContents() throws Exception { + BatchFile obj = new BatchFile("echo A\necho B\recho C"); + rule.assertStringContains(obj.getContents(), "echo A\r\necho B\r\necho C\r\nexit %ERRORLEVEL%"); + } +} diff --git a/test/src/test/java/hudson/tasks/ShellTest.java b/test/src/test/java/hudson/tasks/ShellTest.java index e1e0d7d867..8aadb4f4ba 100644 --- a/test/src/test/java/hudson/tasks/ShellTest.java +++ b/test/src/test/java/hudson/tasks/ShellTest.java @@ -7,7 +7,6 @@ import hudson.model.FreeStyleBuild; import hudson.model.FreeStyleProject; import org.apache.commons.io.FileUtils; import org.jvnet.hudson.test.FakeLauncher; -import org.jvnet.hudson.test.HudsonTestCase; import org.jvnet.hudson.test.PretendSlave; import java.io.File; @@ -15,23 +14,47 @@ import java.io.IOException; import java.io.PrintStream; import java.util.List; +import org.junit.Assert; +import org.junit.Rule; +import org.junit.Test; +import org.jvnet.hudson.test.JenkinsRule; + /** + * Tests for the Shell tasks class + * * @author Kohsuke Kawaguchi */ -public class ShellTest extends HudsonTestCase { +public class ShellTest { + + @Rule + public JenkinsRule rule = new JenkinsRule(); + + @Test + public void validateShellCommandEOL() throws Exception { + Shell obj = new Shell("echo A\r\necho B\recho C"); + rule.assertStringContains(obj.getCommand(), "echo A\necho B\necho C"); + } + + @Test + public void validateShellContents() throws Exception { + Shell obj = new Shell("echo A\r\necho B\recho C"); + rule.assertStringContains(obj.getContents(), "\necho A\necho B\necho C"); + } + + @Test public void testBasic() throws Exception { // If we're on Windows, don't bother doing this. if (Functions.isWindows()) return; // TODO: define a FakeLauncher implementation with easymock so that this kind of assertions can be simplified. - PretendSlave s = createPretendSlave(new FakeLauncher() { + PretendSlave s = rule.createPretendSlave(new FakeLauncher() { public Proc onLaunch(ProcStarter p) throws IOException { // test the command line argument. List cmds = p.cmds(); - assertEquals("/bin/sh",cmds.get(0)); - assertEquals("-xe",cmds.get(1)); - assertTrue(new File(cmds.get(2)).exists()); + rule.assertStringContains("/bin/sh",cmds.get(0)); + rule.assertStringContains("-xe",cmds.get(1)); + Assert.assertTrue(new File(cmds.get(2)).exists()); // fake the execution PrintStream ps = new PrintStream(p.stdout()); @@ -41,13 +64,13 @@ public class ShellTest extends HudsonTestCase { return new FinishedProc(0); } }); - FreeStyleProject p = createFreeStyleProject(); + FreeStyleProject p = rule.createFreeStyleProject(); p.getBuildersList().add(new Shell("echo abc")); p.setAssignedNode(s); - FreeStyleBuild b = assertBuildStatusSuccess(p.scheduleBuild2(0).get()); + FreeStyleBuild b = rule.assertBuildStatusSuccess(p.scheduleBuild2(0).get()); - assertEquals(1,s.numLaunch); - assertTrue(FileUtils.readFileToString(b.getLogFile()).contains("Hudson was here")); + Assert.assertEquals(1,s.numLaunch); + Assert.assertTrue(FileUtils.readFileToString(b.getLogFile()).contains("Hudson was here")); } } diff --git a/test/src/test/java/hudson/tools/BatchCommandInstallerTest.java b/test/src/test/java/hudson/tools/BatchCommandInstallerTest.java new file mode 100644 index 0000000000..e14a90b405 --- /dev/null +++ b/test/src/test/java/hudson/tools/BatchCommandInstallerTest.java @@ -0,0 +1,22 @@ +package hudson.tools; + +import org.junit.Rule; +import org.junit.Test; +import org.jvnet.hudson.test.JenkinsRule; + +/** + * Tests for the BatchCommandInstaller tools class. + * + * @author David Ruhmann + */ +public class BatchCommandInstallerTest { + + @Rule + public JenkinsRule rule = new JenkinsRule(); + + @Test + public void validateBatchCommandInstallerCommandEOL() throws Exception { + BatchCommandInstaller obj = new BatchCommandInstaller("", "echo A\necho B\recho C", ""); + rule.assertStringContains(obj.getCommand(), "echo A\r\necho B\r\necho C"); + } +} diff --git a/test/src/test/java/hudson/tools/CommandInstallerTest.java b/test/src/test/java/hudson/tools/CommandInstallerTest.java new file mode 100644 index 0000000000..7a58c1ec8c --- /dev/null +++ b/test/src/test/java/hudson/tools/CommandInstallerTest.java @@ -0,0 +1,22 @@ +package hudson.tools; + +import org.junit.Rule; +import org.junit.Test; +import org.jvnet.hudson.test.JenkinsRule; + +/** + * Tests for the CommandInstaller tools class. + * + * @author David Ruhmann + */ +public class CommandInstallerTest { + + @Rule + public JenkinsRule rule = new JenkinsRule(); + + @Test + public void validateCommandInstallerCommandEOL() throws Exception { + CommandInstaller obj = new CommandInstaller("", "echo A\r\necho B\recho C", ""); + rule.assertStringContains(obj.getCommand(), "echo A\necho B\necho C"); + } +} diff --git a/test/src/test/java/hudson/util/LineEndingConversionTest.java b/test/src/test/java/hudson/util/LineEndingConversionTest.java new file mode 100644 index 0000000000..66773fbbb9 --- /dev/null +++ b/test/src/test/java/hudson/util/LineEndingConversionTest.java @@ -0,0 +1,28 @@ +package hudson.util; + +import org.junit.Rule; +import org.junit.Test; +import org.jvnet.hudson.test.Issue; +import org.jvnet.hudson.test.JenkinsRule; + +/** + * Tests for the LineEndingConversion util class. + * + * @author David Ruhmann + */ +public class LineEndingConversionTest { + + @Rule + public JenkinsRule rule = new JenkinsRule(); + + @Issue("JENKINS-7478") + @Test + public void validateWindowsEOL() throws Exception { + rule.assertStringContains(LineEndingConversion.convertEOL("echo A\necho B\recho C", LineEndingConversion.EOLType.Windows), "echo A\r\necho B\r\necho C"); + } + + @Test + public void validateUnixEOL() throws Exception { + rule.assertStringContains(LineEndingConversion.convertEOL("echo A\r\necho B\recho C", LineEndingConversion.EOLType.Unix), "echo A\necho B\necho C"); + } +}