/********************************************************************************
*
* 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.BufferedReader;
import java.io.File;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import org.jdom.Element;
import org.junit.Ignore;
import junit.framework.TestCase;
import net.sourceforge.cruisecontrol.CruiseControlException;
import net.sourceforge.cruisecontrol.builders.CMakeBuilder.Option;
import net.sourceforge.cruisecontrol.builders.ExecBuilder;
import net.sourceforge.cruisecontrol.testutil.TestUtil.FilesToDelete;
import net.sourceforge.cruisecontrol.util.Commandline;
import net.sourceforge.cruisecontrol.util.IO;
/**
* Test case for {@link CMakeBuilder}.
*/
public class CMakeBuilderTest extends TestCase
{
/** The name of the binary built */
private static final String binname = "cmakebuilder_testapp";
/** The list of files created during the test */
private final FilesToDelete files2del = new FilesToDelete();
/**
* Setup test environment.
*/
@Override
protected void setUp() throws Exception {
File cmakelist;
File main_c;
/* Create the content of 'main.c' */
main_c = File.createTempFile("main_CMakeBuilderTest", ".c");
main_c.deleteOnExit();
IO.write(main_c, "#include <stdio.h>\n"
+ "\n"
+ "#ifdef ALLOW_INVALID_CODE\n"
+ " This code is invalid and must cause compile error!\n"
+ "#endif\n"
+ "\n"
+ "#ifndef TEXT_TO_PRINT\n"
+ "#define TEXT_TO_PRINT \"Default text to print. To change it, define TEXT_TO_PRINT macro\"\n"
+ "#endif\n"
+ "\n"
+ "int main() {\n"
+ " printf(\"%s\\n\", TEXT_TO_PRINT);\n"
+ " return 0;\n"
+ "}\n");
cmakelist = new File(main_c.getParent(), "CMakeLists.txt"); // Must not be random name!
cmakelist.deleteOnExit();
IO.write(cmakelist, "# The name of project\n"
+ "project(CMakeBuilderTestProject)\n"
+ "\n"
+ "# Minimum required CMake version\n"
+ "cmake_minimum_required(VERSION 2.6)\n"
+ "\n"
+ "# Optional binary name set by -DBINARY_NAME=name\n"
+ "# If not set, binary is named according to the project name\n"
+ "if (NOT DEFINED BINARY_NAME)\n"
+ " set(BINARY_NAME CMakeBuilderTestProject)\n"
+ "endif (NOT DEFINED BINARY_NAME)\n"
+ "\n"
+ "# This option enables invalid code which caused build failure\n"
+ "if (ALLOW_INVALID_CODE)\n"
+ " add_definitions(-DALLOW_INVALID_CODE)\n"
+ "endif (ALLOW_INVALID_CODE)\n"
+ "\n"
+ "# Text to print, if defined\n"
+ "if (TEXT_TO_PRINT)\n"
+ " add_definitions(-DTEXT_TO_PRINT=${TEXT_TO_PRINT})\n"
+ "endif (TEXT_TO_PRINT)\n"
+ "\n"
+ "# Create the executable from given sources\n"
+ "add_executable(${BINARY_NAME} " + main_c.getName() + ")\n"
+ "\n"
+ "# Where to install the application: CMAKE_INSTALL_PREFIX/bin/\n"
+ "install(TARGETS ${BINARY_NAME} DESTINATION bin/)\n");
/* Create new config mock object with all the required attributes correctly set */
config = new ConfigMock();
config.srcroot = cmakelist.getParent();
config.builddir = new File(config.srcroot, "build").getAbsolutePath();
config.addOption("-D BINARY_NAME=" + binname);
config.addOption("-D CMAKE_VERBOSE_MAKEFILE=ON");
/* Set the files to delete */
files2del.add(cmakelist);
files2del.add(main_c);
files2del.add(new File(config.builddir));
}
/**
* Clears test environment.
*/
@Override
protected void tearDown()
{
files2del.delete();
}
/**
* Tests a validate failure - cases when srcroot attribute is not set
*/
public void testValidate_noSrcRoot()
{
CMakeBuilder builder;
try {
/* Set invalid srcroot */
config.srcroot = new File(config.srcroot).getParent();
/* Create builder object and validate it */
builder = config.builderFactory();
builder.validate();
/* Test failed, exception was expected! */
fail("builder was validated even when srcroot was not set");
}
catch (CruiseControlException exc) {
/* correct */
assertTrue(exc.getMessage().contains("srcroot"));
}
}
/**
* Tests a validate failure - cases when srcroot attribute is not set
*/
public void testValidate_invalidSrcRoot()
{
CMakeBuilder builder;
try {
/* Set all except srcroot */
config.srcroot = null;
/* Create builder object and validate it */
builder = config.builderFactory();
builder.validate();
/* Test failed, exception was expected! */
fail("builder was validated even when srcroot was not set");
}
catch (CruiseControlException exc) {
/* correct */
assertTrue(exc.getMessage().contains("srcroot"));
}
}
/**
* Tests a validate failure - cases when builddir attribute is not set
*/
public void testValidate_noBuildDir()
{
CMakeBuilder builder;
try {
/* Set all except builddir */
config.builddir = null;
/* Create builder object and validate it */
builder = config.builderFactory();
builder.validate();
/* Test failed, exception was expected! */
fail("builder was validated even when buildpath was not set");
}
catch (CruiseControlException tExc) {
/* correct */
}
}
/**
* Tests correct build.
* @throws CruiseControlException if failed
*/
@Ignore("Needs CMake package installed")
public void testBuild_buildSuccess() throws CruiseControlException
{
CMakeBuilder builder;
Element buildLog;
config.addBuild("make", "");
/* Build the test project */
builder = config.builderFactory();
builder.validate();
buildLog = builder.build(new HashMap<String, String>(), null);
/* error attribute must not be set and the binary file must exist */
assertNull(buildLog.getAttributeValue("error"));
assertTrue(new File(config.builddir, binname).exists());
/* Try to execute the command */
testBinaryCall(config.builddir, binname);
}
/**
* Tests is values in CMake defines are correctly quoted when contain white characters.
* @throws CruiseControlException if failed
*/
@Ignore("Needs CMake package installed")
public void testBuild_defineQuoting() throws CruiseControlException
{
CMakeBuilder builder;
Element buildLog;
String text2print = "This text is supposed to be printed by the compiled binary";
/* Add special define with white characters and set distdir (result will be executed) */
config.addOption("-D TEXT_TO_PRINT='\"" + text2print + "\"'");
config.addBuild("make", "");
/* Build the test project */
builder = config.builderFactory();
builder.validate();
buildLog = builder.build(new HashMap<String, String>(), null);
/* error attribute must not be set and the binary file must exist */
assertNull(buildLog.getAttributeValue("error"));
assertTrue(new File(config.builddir, binname).exists());
/* Try to execute the command */
assertEquals(text2print, testBinaryCall(config.builddir, binname));
}
/**
* Tests a build failure - enable invalid code in the build .cpp file
* @throws CruiseControlException if failed
*/
@Ignore("Needs CMake package installed")
public void testBuild_invalidMake() throws CruiseControlException
{
CMakeBuilder builder;
Element buildLog;
/* Add option which allows the build of invalid code */
config.addOption("-D ALLOW_INVALID_CODE:BOOL=ON");
config.addBuild("make", "");
/* Build the test project */
builder = config.builderFactory();
builder.validate();
buildLog = builder.build(new HashMap<String, String>(), null);
/* error attribute must be set and the binary file must not exist */
assertNotNull(buildLog.getAttributeValue("error"));
assertFalse(new File(config.builddir, binname).exists());
}
/** @return the instance of {@link CMakeBuilder} */
protected CMakeBuilder createCMakeBuilder() {
return new CMakeBuilder();
}
/**
* Tests the binary built - it execs the binary and checks if it ends with retcode 0. The
* output of the binary is read and returned by the method.
*
* @param workingDir the directory where the binary is placed
* @param binaryName the name of binary to start
* @return everything print on stdout
*/
private static String testBinaryCall(String workingDir, String binaryName)
{
Commandline cmdline = new Commandline();
try {
BufferedReader stdoutReader;
StringBuffer stdoutData;
String line;
Process process;
/* Prepare command to run */
cmdline.setWorkingDir(new File(workingDir));
cmdline.setExecutable(new File(workingDir, binaryName).getCanonicalPath());
/* Run the command */
process = cmdline.execute();
process.waitFor();
/* Must end with 0 retcode */
assertEquals(0, process.exitValue());
/* Read the output of the command */
stdoutReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
stdoutData = new StringBuffer();
/* Read and compare line by line */
while ((line = stdoutReader.readLine()) != null)
stdoutData.append(line);
/* And return it */
return stdoutData.toString();
} catch (Exception exc) {
exc.printStackTrace();
fail("Exception when trying to execute built binary " + cmdline.toString());
/* just for correct compiling */
return "";
}
}
/** The configuration object; filled in {@link #setUp()} method in such a way that all
* required attributes are correctly set */
private ConfigMock config;
/**
* Mock XML configuration element. It contains the same attributes as allowed by <cmake />
* config element for {@link CMakeBuilder} object. The not-<code>null</code> attributes
* are expected to be filled. Note that it does not contain the attributes common to all
* builders!
*
* Isn't something like this (more generic, of course) already available through
* cruisecontrol test package?
*/
private class ConfigMock
{
/** <cmake srcroot="" ... /> attribute */
String srcroot = null;
/** <cmake builddir="" ... /> attribute */
String builddir = null;
/** <cmake timeout="" ... /> attribute */
String timeout = null;
/** The list of child <option name="" value="" /> elements */
List<String> options = new ArrayList<String>();
/** The list of child <build exec="" args="" ... /> elements */
List<String[]> builds = new ArrayList<String[]>();
/** Adds <option /> child element */
void addOption(String option) {
options.add(option);
}
/** Adds <build /> child element */
void addBuild(String exec, String args) {
builds.add(new String[]{ exec, args});
}
/** Creates {@link CMakeBuilder} according to the attributes set (it is like instantiating
* the object from <cmake /> XML config element). */
CMakeBuilder builderFactory()
{
CMakeBuilder builder = createCMakeBuilder();
/* Set individual attributes */
if (srcroot != null)
builder.setSrcRoot(srcroot);
if (builddir != null)
builder.setBuildDir(builddir);
if (timeout != null)
builder.setTimeout(Long.parseLong(timeout));
/* Child configuration elements */
for (String o : options) {
Option O = (Option) builder.createOption();
/* Set filled items */
if (!"".equals(o))
O.setValue(o);
}
/* Child build elements */
for (String[] b : builds) {
ExecBuilder B = builder.createBuild();
/* Set filled items */
B.setCommand(b[0]);
B.setArgs(b[1]);
}
/* Return the filled object */
return builder;
}
}
}