/********************************************************************************
*
* CruiseControl, a Continuous Integration Toolkit
* Copyright (c) 2003, ThoughtWorks, Inc.
* 200 E. Randolph, 25th Floor
* Chicago, IL 60601 USA
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* + Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* + Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
*
* + Neither the name of ThoughtWorks, Inc., CruiseControl, nor the
* names of its contributors may be used to endorse or promote
* products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
********************************************************************************/
package net.sourceforge.cruisecontrol.builders;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import org.apache.tools.ant.filters.StringInputStream;
import org.jdom.Attribute;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.output.Format;
import org.jdom.output.XMLOutputter;
import net.sourceforge.cruisecontrol.Builder;
import net.sourceforge.cruisecontrol.CruiseControlException;
import net.sourceforge.cruisecontrol.testutil.SysUtilMock;
import net.sourceforge.cruisecontrol.testutil.TestCase;
import net.sourceforge.cruisecontrol.testutil.TestUtil.FilesToDelete;
import net.sourceforge.cruisecontrol.util.StdoutBufferTest;
/**
* The test case of {@link PipedExecBuilder} class.
*/
public final class PipedExecBuilderTest extends TestCase {
/** The characters allowed in the texts passed through the piped commands. */
private static final String LETTERS = "1234567890 abcdefghi jklmnopqr stuvwxz .,;?!";
/** Set it to <code>true</code>to switch debug mode on. In the debug mode, the commands
* executed will print detailed information to theirs STDERR. */
private static final boolean debugMode = false;
/** The list of files created during the test - they are deleted by {@link #tearDown()}
* method ... */
private FilesToDelete files;
/**
* Generates text file filled by the given number of lines with random content and store it into
* <code>randomFile</code> provided. The same content, but sorted and unique store into
* <code>sortedFile</code>.
*
* @param randomFile the file to store the random content
* @param sortedFile the file to store the sorted and "uniqued" random content
* @param numlines the number of lines in the random file
* @throws IOException when the file cannot be created
*/
private void createFiles(File randomFile, File sortedFile, int numlines) throws IOException {
OutputStream out;
/* Reduce the numbed of lines in the debug mode ... */
if (debugMode && numlines > 5) {
numlines = 5;
}
/* Prepare the input and output files (make output unique). Trim the lines */
List<String> randomLines = StdoutBufferTest.generateTexts(LETTERS, numlines / 4, true);
List<String> sortedLines = new ArrayList<String>(new HashSet<String>(randomLines));
/* Sort the output and store it to required output file */
Collections.sort(sortedLines);
/* Repeat the lines 2 times to get the required number of lines */
randomLines.addAll(new ArrayList<String>(randomLines));
randomLines.addAll(new ArrayList<String>(randomLines));
/* Store the random file */
out = new BufferedOutputStream(new FileOutputStream(randomFile));
for (String l : randomLines) {
out.write((l + "\n").getBytes());
}
out.close();
/* And the sorted */
out = new BufferedOutputStream(new FileOutputStream(sortedFile));
for (String l : sortedLines) {
out.write((l + "\n").getBytes());
}
/* Close and return the file */
out.close();
}
/**
* Setup test environment.
*/
@Override
protected void setUp() throws Exception {
super.setUp();
/* Prepare new array of files */
files = new FilesToDelete();
}
/**
* Clears test environment.
*/
@Override
protected void tearDown() throws Exception {
/* Delete the files */
files.delete();
super.tearDown();
}
/**
* Checks the validation when ID of the program is not set (ID is required item). The pipe
* looks like:
* <pre>
* 01 +-> !!??
* +-> 03
* </pre>
*/
public void testValidate_noID() {
PipedExecBuilder builder = new PipedExecBuilder();
setExec(builder.createExec(), "01", "cat", "ZERO");
setExec(builder.createExec(), null, "cat", null, "01"); // corrupted
setExec(builder.createExec(), "03", "cat", ">NULL", "01");
/* Must not be validated */
try {
builder.validate();
fail("PipedExecBuilder should throw an exception when required ID attribute is not set.");
} catch (CruiseControlException e) {
assertEquals("exception message when the required ID attribute is not set",
"'ID' is required for PipedExecBuilder$Script", e.getMessage());
}
}
/**
* Checks the validation when ID of the program is not unique (unique ID is required). The
* pipe looks like:
* <pre>
* 01 --> 02??!!
* </pre>
*/
public void testValidate_noUniqueID() {
PipedExecBuilder builder = new PipedExecBuilder();
setExec(builder.createExec(), "01", "cat", "ZERO");
setExec(builder.createExec(), "02", "cat", null, "01");
setExec(builder.createExec(), "02", "cat", ">NULL", "01"); // corrupted
/* Must not be validated */
try {
builder.validate();
fail("PipedExecBuilder should throw an exception when IDs are not unique.");
} catch (CruiseControlException e) {
assertEquals("exception message when IDs are not unique",
"ID 02 is not unique", e.getMessage());
}
}
/**
* Checks the validation when a script is piped from not existing ID. The pipe
* looks like:
* <pre>
* 01 -- ??!! --> 02 --> 03
* </pre>
*/
public void testValidate_invalidPipeFrom() {
PipedExecBuilder builder = new PipedExecBuilder();
setExec(builder.createExec(), "01", "cat", "ZERO");
setExec(builder.createExec(), "02", "cat", null, "xx"); // corrupted
setExec(builder.createExec(), "03", "cat", ">NULL", "02");
/* Must not be validated */
try {
builder.validate();
fail("PipedExecBuilder should throw an exception when piped from unexisting ID.");
} catch (CruiseControlException e) {
assertEquals("exception message when piped from unexisting ID",
"Script 02 is piped from non-existing script xx", e.getMessage());
}
}
/**
* Checks the validation when a script should wait for not existing ID. The pipe
* looks like:
* <pre>
* 01 -- ??!! --> 02 --> 03
* </pre>
*/
public void testValidate_invalidWaitFor() {
PipedExecBuilder builder = new PipedExecBuilder();
setExec(builder.createExec(), "01", "cat", "ZERO");
setExec(builder.createExec(), "02", "cat", null, "01");
setExec(builder.createExec(), "03", "cat", ">NULL", "02");
/**/
setExec(builder.createExec(), "10", "cat", "ZERO", null, "xx"); // corrupted
setExec(builder.createExec(), "11", "cat", ">NULL", "10");
/* Must not be validated */
try {
builder.validate();
fail("PipedExecBuilder should throw an exception when waiting for unexisting ID.");
} catch (CruiseControlException e) {
assertEquals("exception message when waiting for unexisting ID",
"Script 10 waits for non-existing script xx", e.getMessage());
}
}
/**
* Checks the validation when a loop exists in piped commands. The pipe looks like:
* <pre>
* 01 --> 02 --> 03 --> 04 --> 05
* +-> 10 --> 11 --> 12 +-> 13
* | +-> 20 --+
* +-<------<------<------<------+
* </pre>
*/
public void testValidate_pipeLoop() {
PipedExecBuilder builder = new PipedExecBuilder();
setExec(builder.createExec(), "01", "cat", "ZERO");
setExec(builder.createExec(), "02", "cat", null, "01");
setExec(builder.createExec(), "03", "cat", null, "02");
setExec(builder.createExec(), "04", "cat", null, "03");
setExec(builder.createExec(), "05", "cat", ">NULL", "04");
/**/
setExec(builder.createExec(), "10", "cat", "ZERO", "20"); // piped from 20
setExec(builder.createExec(), "11", "cat", null, "10");
setExec(builder.createExec(), "12", "cat", null, "11");
setExec(builder.createExec(), "13", "cat", ">NULL", "12");
/**/
setExec(builder.createExec(), "20", "cat", null, "12"); // loop start
/* Must not be validated */
try {
builder.validate();
fail("PipedExecBuilder should throw an exception when the pipe loop is detected.");
} catch (CruiseControlException e) {
assertRegex("exception message when pipe loop is detected",
"Loop detected, ID \\d\\d is within loop", e.getMessage());
}
/* Disable ID 11 to break the loop */
disable(builder.createExec(), "11");
/* And validate again. Now it must pass */
try {
builder.validate();
} catch (CruiseControlException e) {
fail(e.getMessage());
}
}
/**
* Checks the validation when a loop exists in waiting. The pipe looks like:
* <pre>
* 01 --> 02(wait for 20) +-> 03 --> 04 --> 05
* +-> 10 --> 11 --> 12 +-> 13
* +-> 20
* </pre>
*/
public void testValidate_waitLoop() {
PipedExecBuilder builder = new PipedExecBuilder();
setExec(builder.createExec(), "01", "cat", "ZERO");
// wait for 20 which depends on output of 02
setExec(builder.createExec(), "02", "cat", null, "01", "20");
setExec(builder.createExec(), "03", "cat", null, "02");
setExec(builder.createExec(), "04", "cat", null, "03");
setExec(builder.createExec(), "05", "cat", ">NULL", "04");
/**/
setExec(builder.createExec(), "10", "cat", null, "02");
setExec(builder.createExec(), "11", "cat", null, "10");
setExec(builder.createExec(), "12", "cat", null, "11");
setExec(builder.createExec(), "13", "cat", ">NULL", "12");
/**/
setExec(builder.createExec(), "20", "cat", null, "12");
/* Must not be validated */
try {
builder.validate();
fail("PipedExecBuilder should throw an exception when the wait loop is detected.");
} catch (CruiseControlException e) {
assertRegex("exception message when wait loop is detected",
"Loop detected, ID \\d\\d is within loop", e.getMessage());
}
}
/**
* Checks the validation when an already existing script is re-piped
* <pre>
* orig: 01 --> 02 --> 03 -->
* repipe: 01 --> 10 --> 11 --> 02 --> 03 -->
* </pre>
* @throws CruiseControlException
*/
public void testValidate_repipe() throws CruiseControlException {
PipedExecBuilder builder = new PipedExecBuilder();
setExec(builder.createExec(), "01", "cat", "/dev/zero");
setExec(builder.createExec(), "02", "cat", null, "01");
setExec(builder.createExec(), "03", "cat", null, "02");
/* add for new pipe */
setExec(builder.createExec(), "10", "cat", null, "01");
setExec(builder.createExec(), "11", "cat", null, "10");
/* repipe now - define only ID (again) and pipe */
repipe (builder.createExec(), "02", "11");
/* Must be validated correctly */
builder.validate();
}
/**
* Checks the correct function of the commands piping.
* <b>This is fundamental test, actually!</b> if this test is not passed, the others will
* fail as well.
*
* It simulates the following pipe: <code>cat ifile.txt | cat | cat > ofile.txt</code>.
*
* @throws IOException if the test fails!
* @throws CruiseControlException if the builder fails!
*/
public void testScript_pipe() throws IOException, CruiseControlException {
PipedExecBuilder builder = new PipedExecBuilder();
File inpFile = files.add(this);
File tmpFile = files.add(this);
/* Create the content */
createFiles(inpFile, tmpFile, 200);
/* First cat - simulated by stream reader */
InputStream cat1 = new BufferedInputStream(new FileInputStream(inpFile));
/* Second cat - real command without arguments */
PipedExecBuilder.Script cat2 = (PipedExecBuilder.Script) builder.createExec();
setExec(cat2, "02", "cat", null);
cat2.initialize();
cat2.setInputProvider(cat1);
cat2.setBuildProperties(new HashMap<String, String>());
cat2.setBuildLogParent(new Element("build"));
cat2.setBinaryOutput(false);
cat2.setGZipStdout(false);
/* Validate and run (not as thread here) */
cat2.validate();
cat2.run();
/* Test the output - it must be the same as the input */
assertStreams(new FileInputStream(inpFile), cat2.getOutputReader());
/* Third cat - real command without arguments */
PipedExecBuilder.Script cat3 = (PipedExecBuilder.Script) builder.createExec();
setExec(cat3, "03", "cat", null);
cat3.initialize();
cat3.setInputProvider(cat2.getOutputReader());
cat3.setBuildProperties(new HashMap<String, String>());
cat3.setBuildLogParent(new Element("build"));
cat3.setBinaryOutput(false);
cat3.setGZipStdout(false);
/* Validate and run (not as thread here) */
cat3.validate();
cat3.run();
/* Test the output - it must be the same as the input */
assertStreams(new FileInputStream(inpFile), cat3.getOutputReader());
}
/**
* Checks the correct function of the whole builder. The pipe is quite complex here:
* <pre>
* 31 --> 32 --> 33(add) +-> 34(shuf) --> 35 --> 36(del) +-> 37
* +-> 35 +-> 38(sort) --> 39(uniq) --> 40
*
* 11 --> 12(+ out of 35) --> 13(shuf) +-> 14(grep) --> 15(del) --------> 16(sort) --> 17(uniq) --> 21
* +-> 18(grep) --> 19(sort+uniq) --> 20
*
* 01(out of 37) --> 02(sort) --> 03(uniq) --> 04
* </pre>
*
* @throws IOException if the test fails!
* @throws CruiseControlException if the builder fails!
*/
public void testBuild_correct() throws IOException, CruiseControlException {
PipedExecBuilder builder = new PipedExecBuilder();
Element buildLog;
/* Input and result files */
File inp1File = files.add(this);
File inp2File = files.add(this);
File res1File = files.add(this);
File res2File = files.add(this);
/* Prepare content */
createFiles(inp1File, res1File, 50);
createFiles(inp2File, res2File, 110);
/* Temporary and output files */
File tmp1File = files.add(this);
File tmp2File = files.add(this);
File out1File = files.add(this);
File out2File = files.add(this);
File out3File = files.add(this);
File out4File = files.add(this);
/* Fill it by commands to run*/
builder.setShowProgress(false);
builder.setTimeout(180);
/* Set commands */
setExec(builder.createExec(), "01", "cat", tmp2File.getAbsolutePath(), null, "37");
setExec(builder.createExec(), "02", "sort", null, "01");
setExec(builder.createExec(), "03", "uniq", null, "02");
setExec(builder.createExec(), "04", "cat", ">"+out4File.getAbsolutePath(), "03");
/**/
setExec(builder.createExec(), "11", "cat", inp2File.getAbsolutePath());
setExec(builder.createExec(), "12", "cat", tmp1File.getAbsolutePath() + " -", "11", "35");
setExec(builder.createExec(), "13", "shuf", null, "12");
setExec(builder.createExec(), "14", "grep", "'^\\s*([0-9]+\\s+){3}'", "13");
setExec(builder.createExec(), "15", "del", null, "14");
setExec(builder.createExec(), "16", "sort", null, "15");
setExec(builder.createExec(), "17", "uniq", null, "16");
setExec(builder.createExec(), "18", "grep", "-v '^\\s*([0-9]+\\s+){3}'", "13");
setExec(builder.createExec(), "19", "sort", "-u", "18");
setExec(builder.createExec(), "20", "cat", ">"+out2File.getAbsolutePath(), "19");
setExec(builder.createExec(), "21", "cat", ">"+out3File.getAbsolutePath(), "17");
/**/
setExec(builder.createExec(), "31", "cat", inp1File.getAbsolutePath());
setExec(builder.createExec(), "32", "cat", null, "31");
setExec(builder.createExec(), "33", "add", null, "32");
setExec(builder.createExec(), "34", "shuf", null, "33");
setExec(builder.createExec(), "35", "cat", ">"+tmp1File.getAbsolutePath(), "33");
setExec(builder.createExec(), "36", "del", null, "34");
setExec(builder.createExec(), "37", "cat", ">"+tmp2File.getAbsolutePath(), "36");
setExec(builder.createExec(), "38", "sort", null, "36");
setExec(builder.createExec(), "39", "uniq", null, "38");
setExec(builder.createExec(), "40", "cat", ">"+out1File.getAbsolutePath(), "39");
/* Validate it and run it */
builder.validate();
buildLog = builder.build(new HashMap<String, String>(), null);
printXML(buildLog);
/* No 'error' attribute must exist in the build log */
assertNull("error attribute was found in build log!", buildLog.getAttribute("error"));
/* And finally compare the files */
assertFiles(res1File, out1File);
assertFiles(res1File, out3File);
assertFiles(res1File, out4File);
assertFiles(res2File, out2File);
/* And once again! */
buildLog = builder.build(new HashMap<String, String>(), null);
printXML(buildLog);
/* No 'error' attribute must exist in the build log */
assertNull("error attribute was found in build log!", buildLog.getAttribute("error"));
/* And finally compare the files */
assertFiles(res1File, out1File);
assertFiles(res1File, out3File);
assertFiles(res1File, out4File);
assertFiles(res2File, out2File);
}
/**
* Checks the function of the whole builder when a command is not found. The pipe looks like:
* <pre>
* 01 --> 02 +-> 03(invalid)
* +-> 04 --> 05
* </pre>
*
* @throws IOException if the test fails!
* @throws CruiseControlException if the builder fails!
*/
public void testBuild_badCommand() throws IOException, CruiseControlException {
PipedExecBuilder builder = new PipedExecBuilder();
Element buildLog;
Attribute error;
/* Fill it by commands to run */
builder.setShowProgress(false);
builder.setTimeout(300000);
/* Output and "somewhere-in-the middle" temporary file, input and corresponding result
* files */
File out1File = files.add(this);
File tmp1File = files.add(this);
File inp1File = files.add(this);
File res1File = files.add(this);
/* Prepare content */
createFiles(inp1File, res1File, 50);
/* Set commands */
setExec(builder.createExec(), "01", "cat", inp1File.getAbsolutePath());
setExec(builder.createExec(), "02", "shuf", "01");
/* corrupted: bad binary name */
setExec(builder.createExec(), "03", "BAD", tmp1File.getAbsolutePath(), "02");
setExec(builder.createExec(), "04", "sort", "-u", "02");
setExec(builder.createExec(), "05", "cat", ">"+out1File.getAbsolutePath(), "04");
/* Validate it and run it */
builder.validate();
buildLog = builder.build(new HashMap<String, String>(), null);
error = buildLog.getAttribute("error");
printXML(buildLog);
/* 'error' attribute must exist in the build log */
assertNotNull("error attribute was not found in build log!", error);
assertRegex("unexpected error message is returned", "(exec error.*|return code .*)", error.getValue());
}
/**
* Checks the function of the whole builder when an option of a command is invalid. The pipe
* looks like:
* <pre>
* 01 +-> 02(bad option)
* +-> 03
* </pre>
*
* @throws IOException if the test fails!
* @throws CruiseControlException if the builder fails!
*/
public void testBuild_badOption() throws IOException, CruiseControlException {
PipedExecBuilder builder = new PipedExecBuilder();
Element buildLog;
Attribute error;
/* Output file, input and corresponding result files */
File out1File = files.add(this);
File inp1File = files.add(this);
File res1File = files.add(this);
/* Prepare content */
createFiles(inp1File, res1File, 50);
/* Fill it by commands to run. */
builder.setShowProgress(false);
builder.setTimeout(180);
/* Set commands */
setExec(builder.createExec(), "01", "cat", inp1File.getAbsolutePath());
/* corrupted: unknown option */
setExec(builder.createExec(), "02", "cat", ">/////", "01");
setExec(builder.createExec(), "03", "cat", ">"+out1File.getAbsolutePath(), "01"); // OK
/* Validate it and run it */
builder.validate();
buildLog = builder.build(new HashMap<String, String>(), null);
error = buildLog.getAttribute("error");
printXML(buildLog);
/* 'error' attribute must exist in the build log */
assertNotNull("error attribute was not found in build log!", error);
assertRegex("unexpected error message is returned", "return code .*", error.getValue());
}
/**
* Checks the timeout function. The pipe looks like:
* <pre>
* 01(infinite read) --> 02 --> 03
* </pre>
*
* @throws IOException if the test fails!
* @throws CruiseControlException if the builder fails!
*/
public void testBuild_timeout() throws CruiseControlException, IOException {
PipedExecBuilder builder = new PipedExecBuilder();
Element buildLog;
Attribute error;
/* Output files, input and corresponding result files */
File out1File = files.add(this);
File out2File = files.add(this);
File inp1File = files.add(this);
File res1File = files.add(this);
/* Prepare content */
createFiles(inp1File, res1File, 50);
/* Fill it by commands to run. */
builder.setTimeout(10);
builder.setShowProgress(false);
/* Read from infinite file (with some waiting not to read large amoutn of data ...
* Option also is to read from STDIN, but not to have the command piped from
* another command; however, it is not going to be worked, as STDIN of the command is
* closed as soon as nothing can be read from. See also #testBuild_stdinClose() */
setExec(builder.createExec(), "01", "cat", "-D 1000 ZERO");
setExec(builder.createExec(), "02", "sort", "-u", "01");
setExec(builder.createExec(), "03", "cat", ">"+out2File.getAbsolutePath(), "02");
/* Validate it and run it */
builder.validate();
buildLog = builder.build(new HashMap<String, String>(), null);
error = buildLog.getAttribute("error");
printXML(buildLog);
/* And finally compare the files */
assertFiles(out1File, out1File);
/* 'error' attribute must exist in the build log, ant it must hold 'timeout' string */
assertNotNull("error attribute was not found in build log!", error);
assertRegex("unexpected error message is returned", "build timeout.*", error.getValue());
}
/**
* Checks if the STDIN of a command is closed when it is not piped from another command.
* When STDIO is not closed <code>cat</code> command should wait for infinite time.
* The pipe looks like:
* <pre>
* 01(no input) --> 02 --> 03
* </pre>
*
* @throws IOException if the test fails!
* @throws CruiseControlException if the builder fails!
*/
public void testBuild_stdinClose() throws CruiseControlException, IOException {
PipedExecBuilder builder = new PipedExecBuilder();
Element buildLog;
long startTime = System.currentTimeMillis();
/* Set 2 minutes long timeout. */
builder.setTimeout(120);
builder.setShowProgress(false);
/* Read from stdin, but do not have it piped. The command must end immediately
* without error, as the stdin MUST BE closed when determined that nothing can be
* read from */
setExec(builder.createExec(), "01", "cat", null);
setExec(builder.createExec(), "02", "cat", ">"+files.add(this).getAbsolutePath(), "01");
/* Validate it and run it */
builder.validate();
buildLog = builder.build(new HashMap<String, String>(), null);
printXML(buildLog);
/* First of all, the command must not run. 20 seconds must be far enough! */
assertTrue("command run far longer than it should!", (System.currentTimeMillis() - startTime) / 1000 < 20);
/* No 'error' attribute must exist in the build log */
assertNull("error attribute was found in build log!", buildLog.getAttribute("error"));
}
/**
* Tests the repining the validation when an already existing script is re-piped
* <pre>
* +-> 06(file)
* orig: 01 --> 02 --> 03 +-> 04 --> 05(file)
*
* repipe: 01 --> 10 --> 11 --> 12 --> 13 +-> 04 --> 05(file)
* +-> SO(file)
* </pre>
* @throws IOException
* @throws CruiseControlException
*/
public void testBuild_repipe() throws IOException, CruiseControlException {
PipedExecBuilder builder = new PipedExecBuilder();
/* Input file (in UTF8 encoding), and output files */
File inpFile = files.add(this);
File ou1File = files.add(this);
File ou2File = files.add(this);
File ou3File = files.add(this);
File tmpFile = files.add(this);
/* Create the content */
createFiles(inpFile, tmpFile, 200);
/* Set 2 minutes long timeout. */
builder.setTimeout(120);
builder.setShowProgress(false);
/* Read text from file in utf8 and try to pass it through various encodings */
setExec(builder.createExec(), "01", "cat", inpFile.getAbsolutePath());
setExec(builder.createExec(), "02", "add", null, "01");
setExec(builder.createExec(), "03", "del", null, "02");
setExec(builder.createExec(), "04", "cat", null, "03");
setExec(builder.createExec(), "05", "cat", ">"+ou1File.getAbsolutePath(), "04");
setExec(builder.createExec(), "06", "cat", ">"+ou3File.getAbsolutePath(), "03");
/* Now define the repipe */
setExec(builder.createExec(), "10", "shuf", null, "01");
setExec(builder.createExec(), "11", "cat", null, "10");
setExec(builder.createExec(), "12", "cat", null, "11");
setExec(builder.createExec(), "13", "sort", "-u", "12");
setExec(builder.createExec(), "S1", "sort", "-u", "01");
setExec(builder.createExec(), "S2", "cat", ">"+ou2File.getAbsolutePath(), "S1");
/* repipe here */
repipe (builder.createExec(), "04", "13");
/* and disable the old path */
disable(builder.createExec(), "02");
/* Validate it and run it */
builder.validate();
builder.build(new HashMap<String, String>(), null);
/* Check to the sorted variant (the first pile does not sort at all) */
assertFiles(tmpFile, ou1File);
assertFiles(tmpFile, ou2File);
}
/**
* Test environment variables in the build - sets some value PipedExecBuilder and check
* if it is propagated to the individual builders
*
* @throws CruiseControlException
* @throws IOException
*/
public void testBuild_SetEnvVal() throws IOException, CruiseControlException {
PipedExecBuilder builder = new PipedExecBuilder();
PipedExecBuilder.Script script;
Builder.EnvConf env;
String envvar = "TESTENV";
String envval = "dummy_value";
File envExec = ExecBuilderTest.createEnvTestScript();
File outFile = files.add(this);
builder.setTimeout(10);
builder.setShowProgress(false);
// set env
env = builder.createEnv();
env.setName(envvar);
env.setValue(envval);
// print env and store it to the file
// Must be configured in a "more difficult" way, since SysUtilMock which does not
// contain 'env' command
script = (PipedExecBuilder.Script) builder.createExec();
script.setID("env");
script.setCommand(envExec.getAbsolutePath());
//
setExec(builder.createExec(), "get", "grep", "^"+envvar+".*", "env");
setExec(builder.createExec(), "out", "cat", ">"+outFile.getAbsolutePath(), "get");
// Validate it and run it
builder.validate();
builder.build(new HashMap<String, String>(), null);
// Test the filtered output. We expects the ENV variable in form TESTENV=dummy_value
// which seems to be the same on both Linux and Windows. If is is not valid, i.e. the
// system the test is running on prints the ENV variables formated in a different way,
// the test must checks the result in a more clever way. For example, SysUtilMock
// class can be extended by command printing ENV variables
assertStreams(new StringInputStream(envvar+"="+envval), new FileInputStream(outFile));
} // testBuild_NewEnvVar
/**
* Method filling the {@link PipedExecBuilder.Script} class no piped from another scripts,
* without working dir and not waiting for another script - {@link PipedExecBuilder.Script#setPipeFrom(String)},
* {@link PipedExecBuilder.Script#setWaitFor(String)} and
* {@link PipedExecBuilder.Script#setWorkingDir(String)} are not called.
*
* @param exec the instance to fill, <b>must not</b> be <code>null</code>.
* @param id {@link PipedExecBuilder.Script#setID(String)}, may be <code>null</code>
* @param command {@link PipedExecBuilder.Script#setCommand(String)}, may be <code>null</code>
* Note that the command must be supported by {@link SysUtilMock} class!
* @param args {@link PipedExecBuilder.Script#setArgs(String)}, may be <code>null</code>
*/
private static void setExec(Object exec, String id, String command, String args) {
setExec(exec, id, command, args, null, null, null);
}
/**
* Method filling the {@link PipedExecBuilder.Script} class without working dir and not
* waiting for another script - {@link PipedExecBuilder.Script#setWaitFor(String)} and
* {@link PipedExecBuilder.Script#setWorkingDir(String)} are not called.
*
* @param exec the instance to fill, <b>must not</b> be <code>null</code>.
* @param id {@link PipedExecBuilder.Script#setID(String)}, may be <code>null</code>
* @param command {@link PipedExecBuilder.Script#setCommand(String)}, may be <code>null</code>
* Note that the command must be supported by {@link SysUtilMock} class!
* @param args {@link PipedExecBuilder.Script#setArgs(String)}, may be <code>null</code>
* @param pipeFrom {@link PipedExecBuilder.Script#setPipeFrom(String)}, may be <code>null</code>
*/
private static void setExec(Object exec, String id, String command, String args, String pipeFrom) {
setExec(exec, id, command, args, pipeFrom, null, null);
}
/**
* Method filling the {@link PipedExecBuilder.Script} class without working dirset -
* {@link PipedExecBuilder.Script#setWorkingDir(String)} is not called.
*
* @param exec the instance to fill, <b>must not</b> be <code>null</code>.
* @param id {@link PipedExecBuilder.Script#setID(String)}, may be <code>null</code>
* @param command {@link PipedExecBuilder.Script#setCommand(String)}, may be <code>null</code>
* Note that the command must be supported by {@link SysUtilMock} class!
* @param args {@link PipedExecBuilder.Script#setArgs(String)}, may be <code>null</code>
* @param pipeFrom {@link PipedExecBuilder.Script#setPipeFrom(String)}, may be <code>null</code>
* @param waitFor {@link PipedExecBuilder.Script#setWaitFor(String)}, may be <code>null</code>
*/
private static void setExec(Object exec, String id, String command, String args, String pipeFrom,
String waitFor) {
setExec(exec, id, command, args, pipeFrom, waitFor, null);
}
/**
* Method filling all the attributes of the {@link PipedExecBuilder.Script} class.
*
* @param exec the instance to fill, <b>must not</b> be <code>null</code>.
* @param id {@link PipedExecBuilder.Script#setID(String)}, may be <code>null</code>
* @param command {@link PipedExecBuilder.Script#setCommand(String)}, may be <code>null</code>
* Note that the command must be supported by {@link SysUtilMock} class!
* @param args {@link PipedExecBuilder.Script#setArgs(String)}, may be <code>null</code>
* @param pipeFrom {@link PipedExecBuilder.Script#setPipeFrom(String)}, may be <code>null</code>
* @param waitFor {@link PipedExecBuilder.Script#setWaitFor(String)}, may be <code>null</code>
* @param workingDir {@link PipedExecBuilder.Script#setWorkingDir(String)}, may be <code>null</code>
*/
private static void setExec(Object exec, String id, String command, String args,
String pipeFrom, String waitFor, String workingDir) {
if (id != null) {
((PipedScript) exec).setID(id);
}
if (command != null) {
/* Find the command among the public attributes of SysUtilMock class */
try {
Field f = SysUtilMock.class.getDeclaredField(command);
String[] c = (String[]) f.get(null);
/* add -V for verbose output in debug mode */
if (debugMode) {
args = " -V " + (args != null ? args : "");
}
/* Update the command and the arguments */
command = c[0];
args = c[1] + "-P 'ID " + id + "' " + (args != null ? args : "");
} catch (NoSuchFieldException e) {
/* Command is not supported, fail the test */
fail(SysUtilMock.class.getName() + " does not suport command '" + command
+ "': " + e.getMessage());
} catch (Exception e) {
fail(e.getMessage());
}
/* Set the command */
((PipedExecBuilder.Script) exec).setCommand(command);
}
if (args != null) {
((PipedExecBuilder.Script) exec).setArgs(args);
}
if (workingDir != null) {
((PipedScript) exec).setWorkingDir(workingDir);
}
if (waitFor != null) {
((PipedScript) exec).setWaitFor(waitFor);
}
if (pipeFrom != null) {
((PipedScript) exec).setPipeFrom(pipeFrom);
}
// in debug mode, print more details
if (debugMode) {
System.out.println("Exec: " + exec);
}
}
/**
* Method filling the "repipe" attributes of the {@link PipedExecBuilder.Script} class.
*
* @param exec the instance to fill, <b>must not</b> be <code>null</code>.
* @param ID {@link PipedScript#setID(String)}, may be <code>null</code>
* @param repipe {@link PipedScript#setRepipe(String)}, may be <code>null</code>
*/
private static void repipe(Object exec, String ID, String repipe) {
if (ID != null) {
((PipedScript) exec).setID(ID);
((PipedScript) exec).setRepipe(repipe);
}
}
/**
* Method filling the "disable" attributes of the {@link PipedExecBuilder.Script} class.
*
* @param exec the instance to fill, <b>must not</b> be <code>null</code>.
* @param ID {@link PipedScript#setID(String)}, may be <code>null</code>
*/
private static void disable(Object exec, String ID) {
if (ID != null) {
((PipedScript) exec).setID(ID);
((PipedScript) exec).setDisable(true);
}
}
/**
* If {@link #debugMode} in on, prints the build log XML element to STDOUT.
* @param buildLog the element to print.
*/
private static void printXML(final Element buildLog)
{
if (! debugMode) {
return;
}
try {
XMLOutputter out = new XMLOutputter(Format.getPrettyFormat());
System.out.println(out.outputString(new Document(buildLog)));
} catch (Exception e) {
e.printStackTrace();
}
}
}