/********************************************************************************
* CruiseControl, a Continuous Integration Toolkit
* Copyright (c) 2001, 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.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import junit.framework.TestCase;
import net.sourceforge.cruisecontrol.Builder;
import net.sourceforge.cruisecontrol.CruiseControlException;
import net.sourceforge.cruisecontrol.util.Commandline;
import net.sourceforge.cruisecontrol.util.IO;
import net.sourceforge.cruisecontrol.util.OSEnvironment;
import net.sourceforge.cruisecontrol.util.Util;
import org.jdom.Element;
/**
* Exec builder test class.
*
* @author <a href="mailto:kevin.lee@buildmeister.com">Kevin Lee</a>
*/
public class ExecBuilderTest extends TestCase {
private static final String MOCK_SUCCESS = "good exec";
private static final String MOCK_EXIT_FAILURE = "exit failure";
private static final String MOCK_OUTPUT_FAILURE = "output failure";
// name of environment variable used in the test; such env variable must
// exist when the test is started (be defined by ant)
private static final String TEST_ENVVAR = "TESTENV";
// private static final String MOCK_TIMEOUT_FAILURE = "timeout failure";
private File goodTestScript = null;
private File exitTestScript = null;
private File outputTestScript = null;
private File envTestScript = null;
// Current environment
private final OSEnvironment env = new OSEnvironment();
/*
* default constructor
*/
public ExecBuilderTest(String name) {
super(name);
} // ExecBuilderTest
/*
* setup test environment
*/
protected void setUp() throws Exception {
// prepare "good" mock files
if (Util.isWindows()) {
goodTestScript = File.createTempFile("ExecBuilderTest.internalTestBuild", "_goodexec.bat");
goodTestScript.deleteOnExit();
makeTestFile(
goodTestScript,
"@rem This is a good exec.bat\n"
+ "@echo output from good exec\n",
true);
} else {
goodTestScript = File.createTempFile("ExecBuilderTest.internalTestBuild", "_goodexec.sh");
goodTestScript.deleteOnExit();
makeTestFile(
goodTestScript,
"#!/bin/sh\n"
+ "\n"
+ "echo good exec\n",
false);
}
// prepare "bad" mock files - with exit value > 0
if (Util.isWindows()) {
exitTestScript = File.createTempFile("ExecBuilderTest.internalTestBuild", "_exitexec.bat");
exitTestScript.deleteOnExit();
makeTestFile(
exitTestScript,
"@rem This is a bad exec.bat\n"
+ "exit 1\n",
true);
} else {
exitTestScript = File.createTempFile("ExecBuilderTest.internalTestBuild", "_exitexec.sh");
exitTestScript.deleteOnExit();
makeTestFile(
exitTestScript,
"#!/bin/sh\n"
+ "\n"
+ "exit 1\n",
false);
}
// prepare "bad" mock files - containing error string
if (Util.isWindows()) {
outputTestScript = File.createTempFile("ExecBuilderTest.internalTestBuild", "_outputexec.bat");
outputTestScript.deleteOnExit();
makeTestFile(
outputTestScript,
"@rem This is a bad exec.bat\n"
+ "@echo some input and then an " + MOCK_OUTPUT_FAILURE + "\n",
true);
} else {
outputTestScript = File.createTempFile("ExecBuilderTest.internalTestBuild", "_outputexec.sh");
outputTestScript.deleteOnExit();
makeTestFile(
outputTestScript,
"#!/bin/sh\n"
+ "\n"
+ "echo some input and then an " + MOCK_OUTPUT_FAILURE + "\n",
false);
}
// prepare "good" mock files for the testing of environment variables
// TODO: create only when needed; actually it does not have to be created for every test,
// but it is done so to be compatible with other test scripts
envTestScript = createEnvTestScript();
} // setUp
/*
* test validation of required attributes
*/
public void testValidate() {
ExecBuilder ebt = createExecBuilder();
// test missing "command" attribute
try {
ebt.validate();
fail("ExecBuilder should throw an exception when the required attributes are not set.");
} catch (CruiseControlException e) {
assertEquals("exception message when required attributes not set",
"'command' is required for "+ebt.getClass().getSimpleName(), e.getMessage());
}
// test no error with all required attributes
ebt.setCommand("dir");
try {
ebt.validate();
} catch (CruiseControlException e) {
fail("ExecBuilder should not throw an exception when the required attributes are set.");
}
} // testValidate
/*
* test a succesful build
*/
public void testBuild_BuildSuccess() {
ExecBuilder eb = createExecBuilder();
internalTestBuild(MOCK_SUCCESS, eb, goodTestScript.toString());
} // testBuild_BuildSuccess
/*
* test a buid failure - exit
*/
public void testBuild_BuildFailure() {
ExecBuilder eb = createExecBuilder();
internalTestBuild(MOCK_EXIT_FAILURE, eb, exitTestScript.toString());
} // testBuild_BuildFailure
/*
* test a build failure - error in output
*/
public void testBuild_OutputFailure() {
ExecBuilder eb = createExecBuilder();
internalTestBuild(MOCK_OUTPUT_FAILURE, eb, outputTestScript.toString());
} // testBuild_OutputFailure
/*
* test environment variables in the build - define new variable
*/
public void testBuild_NewEnvVar() {
String envnew = "TESTENV_NEW";
String envval = "The value of the new environment variable";
Element logout;
// The variable must NOT exist in the current environment
assertNull(env.getVariable(envnew));
ExecBuilder eb = createExecBuilder();
setEnv(eb, envnew, envval);
// Script must not fail and its STDOUT must contain the new variable
logout = internalTestBuild(MOCK_SUCCESS, eb, envTestScript.toString());
assertEquals(String.format("%s=%s", envnew, envval),
findMessage(logout, String.format("^%s.*", envnew)));
} // testBuild_NewEnvVar
/*
* test environment variables in the build - delete the variable
*/
public void testBuild_DelEnvVar() {
Element logout;
// The variable must exist in the current environment
assertNotNull("Value of " + TEST_ENVVAR + " environment variable is not set (if this test fails in your IDE you need to configure your test runner to set this env var with a dummy value)",
env.getVariable(TEST_ENVVAR));
ExecBuilder eb = createExecBuilder();
setEnv(eb, TEST_ENVVAR, null);
// Script must not fail and its STDOUT must not contain the new variable
logout = internalTestBuild(MOCK_SUCCESS, eb, envTestScript.toString());
assertEquals(null, findMessage(logout, String.format("^%s.*", TEST_ENVVAR)));
} // testBuild_NewEnvVar
/*
* test environment variables in the build - sets the empty value to the variable
*/
public void testBuild_EmptyEnvVal() {
Element logout;
// The variable must exist in the current environment
assertNotNull(env.getVariable(TEST_ENVVAR));
ExecBuilder eb = createExecBuilder();
setEnv(eb, TEST_ENVVAR, "");
// Script must not fail and its STDOUT must contain the variable without value
logout = internalTestBuild(MOCK_SUCCESS, eb, envTestScript.toString());
assertEquals(String.format("%s=", TEST_ENVVAR),
findMessage(logout, String.format("^%s.*", TEST_ENVVAR)));
} // testBuild_NewEnvVar
/*
* test environment variables in the build - adds some value to the existing value
* of the environment variable
*/
public void testBuild_AddEnvVal() {
Element logout;
String envval = "/dummy/beg/path" + File.pathSeparator + "${" + TEST_ENVVAR + "}" +
File.pathSeparator +"/dummy/end/path";
// The variable must exist in the current environment
assertNotNull(env.getVariable(TEST_ENVVAR));
String path = env.getVariable(TEST_ENVVAR);
ExecBuilder eb = createExecBuilder();
setEnv(eb, TEST_ENVVAR, envval);
// Script must not fail and its STDOUT must contain the variable
logout = internalTestBuild(MOCK_SUCCESS, eb, envTestScript.toString());
assertEquals(String.format("%s=%s", TEST_ENVVAR, envval.replace("${" + TEST_ENVVAR + "}", path)),
findMessage(logout, String.format("^%s.*", TEST_ENVVAR)));
} // testBuild_NewEnvVar
/** @return the instance of {@link CMakeBuilder} */
protected ExecBuilder createExecBuilder() {
return new ExecBuilder();
}
/*
* execute the build and check results
*/
private Element internalTestBuild(String statusType, ExecBuilder eb, String script) {
Element logElement = null;
try {
eb.setCommand(script);
if (statusType.equals(MOCK_OUTPUT_FAILURE)) {
eb.setErrorStr(MOCK_OUTPUT_FAILURE);
}
eb.validate();
logElement = eb.build(new HashMap<String, String>(), null);
} catch (CruiseControlException e) {
e.printStackTrace();
fail("ExecBuilder should not throw exceptions when build()-ing.");
}
// check whether there was a build error
//System.out.println("error output = " + eb.getBuildError());
if (statusType.equals(MOCK_SUCCESS)) {
assertEquals(statusType, "none", getBuildError(logElement));
} else if (statusType.equals(MOCK_EXIT_FAILURE)) {
assertEquals(statusType, "return code is 1", getBuildError(logElement));
} else if (statusType.equals(MOCK_OUTPUT_FAILURE)) {
assertEquals(statusType, "error string found", getBuildError(logElement));
}
// check the format of the produced log
assertNotNull(statusType, logElement);
List targetTags = logElement.getChildren("target");
assertNotNull(statusType, targetTags);
assertEquals(statusType, 1, targetTags.size());
Element te = (Element) targetTags.get(0);
assertEquals(statusType, "exec", te.getAttribute("name").getValue());
//System.out.println("target name = " + te.getAttribute("name").getValue());
List taskTags = te.getChildren("task");
Element tk = (Element) taskTags.get(0);
assertEquals(statusType, script, tk.getAttribute("name").getValue());
//System.out.println("task name = " + tk.getAttribute("name").getValue());
//TODO: check for contents of messages
//Iterator msgIterator = tk.getChildren("message").iterator();
//while (msgIterator.hasNext()) {
// Element msg = (Element) msgIterator.next();
// System.out.println("message priority = " + msg.getAttribute("priority").getValue());
//}
return logElement;
} // internalTestBuild
/**
* Sets the environment variable for the given exec builder
* @param eb the builder to set the environment variable for
* @param name the name of the environment variable
* @param value the new value of the environment variable, or null if to delete the
* variable
*/
private void setEnv(final ExecBuilder eb, final String name, String value) {
final Builder.EnvConf env = eb.createEnv();
/* Set the env variable, or mark it for delete */
env.setName(name);
if (value != null) {
env.setValue(value);
}
else {
env.setDelete(true);
}
}
/**
* Checks if the STDOUT of a script (collected to build log XML element) contains the
* line matching the given pattern, and returns the line when found.
* @param logOut the XML element
* get by {@link ExecBuilder#build(java.util.Map, net.sourceforge.cruisecontrol.Progress)}.
* @param pattern the pattern to match the STDOUT against
* @return the line matching the pattern, or <code>null</code> if no line matched.
*/
private String findMessage(final Element logOut, final String pattern) {
for (final Object elem : logOut.getChild("target").getChild("task").getChildren("message")) {
if (elem instanceof Element) {
if (((Element)elem).getText().matches(pattern)) {
return ((Element)elem).getText();
}
}
}
return null;
}
/*
* Make a test file with specified content. Assumes the file does not exist.
*/
private static void makeTestFile(File testFile, String content, boolean onWindows) throws CruiseControlException {
IO.write(testFile, content);
if (!onWindows) {
Commandline cmdline = new Commandline();
cmdline.setExecutable("chmod");
cmdline.createArgument("755");
cmdline.createArgument(testFile.getAbsolutePath());
try {
Process p = cmdline.execute();
p.waitFor();
assertEquals(0, p.exitValue());
} catch (Exception e) {
e.printStackTrace();
fail("exception changing permissions on test file " + testFile.getAbsolutePath());
}
}
} // makeTestFile
/**
* Make a test script printing all ENV variables to its STDOUT, when started.
* @return the path to the script file
* @throws IOException if the file creation fail
* @throws CruiseControlException if the file creation fail
*/
public static File createEnvTestScript() throws IOException, CruiseControlException {
final File scriptfile;
if (Util.isWindows()) {
scriptfile = File.createTempFile("ExecBuilderTest.internalTestBuild", "_envexec.bat");
scriptfile.deleteOnExit();
// TODO: vypisovat promennou, ktera je na vstupu zadana jako parametr
makeTestFile(
scriptfile,
"@rem This is a good exec.bat printing environment values\n"
+ "@set",
true);
} else {
scriptfile = File.createTempFile("ExecBuilderTest.internalTestBuild", "_envexec.sh");
scriptfile.deleteOnExit();
makeTestFile(
scriptfile,
"#!/bin/sh\n"
+ "env",
false);
}
return scriptfile;
}
/**
* Get whether there was an error written to the build log returned by
* {@link ExecBuilder#build(Map, Progress)} or {@link ExecBuilder#build(Map, Progress, InputStream)}
* methods.
*
* @param buildLogElement the build log to check for error
* @return the error string otherwise null
*/
public static String getBuildError(Element buildLogElement) {
if (buildLogElement.getAttribute("error") != null) {
return buildLogElement.getAttribute("error").getValue();
} else {
return "none";
}
} // getBuildError
public void testArgumentsShouldHavePropertySubstituion() throws CruiseControlException {
final MockExecScript script = new MockExecScript();
final ExecBuilder builder = new TestExecBuilder(script);
builder.setCommand("cmd");
builder.setArgs("${label}");
builder.validate();
final Map<String, String> buildProperties = new HashMap<String, String>();
buildProperties.put("label", "foo");
builder.build(buildProperties, null);
assertEquals(script.getExecArgs(), "foo");
}
private class TestExecBuilder extends ExecBuilder {
private final MockExecScript script;
public TestExecBuilder(MockExecScript script) {
this.script = script;
}
@Override
protected ExecScript createExecScript() {
return script;
}
@Override
protected boolean runScript(final ExecScript script, final ScriptRunner scriptRunner, final String dir,
final String projectName, final InputStream stdinProvider) {
return true;
}
}
private class MockExecScript extends ExecScript {
private String args;
public String getExecArgs() {
return args;
}
public void setExecArgs(String execArgs) {
args = execArgs;
super.setExecArgs(execArgs);
}
}
} // ExecBuilderTest