Skip to content

Commit

Permalink
Merge pull request #20 from carlpett/merge-fork-jogge
Browse files Browse the repository at this point in the history
Merge Jogge/xUnit-TeamCity
  • Loading branch information
carlpett authored Mar 31, 2017
2 parents 6915177 + 495b0b1 commit 6f1fb84
Show file tree
Hide file tree
Showing 12 changed files with 746 additions and 37 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# xUnit TeamCity plugin
A xUnit runner for TeamCity. Supports tests written with xUnit 1.9.2, 2.0.0 and 2.1.0, also supports wildcard include and exclude patterns.
A xUnit runner for TeamCity. Supports tests written with xUnit 1.9.2, 2.0.0, 2.1.0 and 2.2.0, also supports wildcard include and exclude patterns.

# Download
Get the latest release here: https://github.com/carlpett/xUnit-TeamCity/releases/latest.
Original file line number Diff line number Diff line change
@@ -1,26 +1,25 @@
package se.capeit.dev.xunittestrunner;

import com.intellij.openapi.util.SystemInfo;
import jetbrains.buildServer.agent.AgentRunningBuild;
import jetbrains.buildServer.agent.BuildFinishedStatus;
import jetbrains.buildServer.agent.BuildProgressLogger;
import jetbrains.buildServer.agent.BuildRunnerContext;
import jetbrains.buildServer.messages.DefaultMessagesInfo;
import jetbrains.buildServer.util.AntPatternFileFinder;
import jetbrains.buildServer.util.StringUtil;
import jetbrains.buildServer.util.CollectionsUtil;
import jetbrains.buildServer.util.StringUtil;
import org.jetbrains.annotations.NotNull;
import com.intellij.openapi.util.SystemInfo;

import java.io.File;
import java.io.InputStream;
import java.util.Map;
import java.util.List;
import java.util.Scanner;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

class XUnitBuildProcess extends FutureBasedBuildProcess {
private final AgentRunningBuild buildingAgent;
private final BuildRunnerContext context;
private Process testRunnerProcess;
private HashMap<Process, String> processes = new HashMap<>();

public XUnitBuildProcess(@NotNull final BuildRunnerContext context) {
super(context);
Expand All @@ -29,24 +28,32 @@ public XUnitBuildProcess(@NotNull final BuildRunnerContext context) {
this.buildingAgent = context.getBuild();
}

private String getParameter(@NotNull final String parameterName)
{
private String getParameter(@NotNull final String parameterName) {
final String value = context.getRunnerParameters().get(parameterName);
if (value == null || value.trim().length() == 0) return "";
return value.trim();
}

private List<String> getAssemblies(final String rawAssemblyParameter) {
String withSlashesFixed = rawAssemblyParameter.replace('\\','/');
String withSlashesFixed = rawAssemblyParameter.replace('\\', '/');
List<String> assemblies = StringUtil.split(withSlashesFixed, true, ',', ';', '\n', '\r');
return assemblies;
}

protected void cancelBuild() {
if (testRunnerProcess == null)
return;
for (Map.Entry<Process, String> p : processes.entrySet()) {
p.getKey().destroy();
}
processes.clear();
}

testRunnerProcess.destroy();
private int tryParseInt(String stringValue, int defaultValue) {
try {
return Integer.parseInt(stringValue);
}
catch (NumberFormatException e) {
return defaultValue;
}
}

public BuildFinishedStatus call() throws Exception {
Expand All @@ -57,41 +64,57 @@ public BuildFinishedStatus call() throws Exception {
String platform = getParameter(StringConstants.ParameterName_Platform);
logger.message("Runner parameters { Version = " + version + ", runtime = " + runtime + ", platform = " + platform + "}");

int numberOfParallelProcesses = tryParseInt(getParameter(StringConstants.ParameterName_NumberOfParallelProcesses), 1);
logger.message("Number of parallel processes is set to: " + numberOfParallelProcesses);

File agentToolsDirectory = buildingAgent.getAgentConfiguration().getAgentToolsDirectory();
String runnerPath = new File(agentToolsDirectory, "xunit-runner\\bin\\" + version + "\\" + runner.getRunnerPath(runtime, platform)).getPath();
logger.message("Starting test runner at " + runnerPath);

List<String> assemblies = getAssemblies(getParameter(StringConstants.ParameterName_IncludedAssemblies));
String commandLineArguments = getParameter(StringConstants.ParameterName_CommandLineArguments);
List<String> excludedAssemblies = getAssemblies(getParameter(StringConstants.ParameterName_ExcludedAssemblies));
excludedAssemblies.add("**/obj/**"); // We always exclude **/obj/**

BuildFinishedStatus status = BuildFinishedStatus.FINISHED_SUCCESS;

// Find the files, and run them through the test runner
AntPatternFileFinder finder = new AntPatternFileFinder(
CollectionsUtil.toStringArray(assemblies),
CollectionsUtil.toStringArray(excludedAssemblies),
SystemInfo.isFileSystemCaseSensitive);
CollectionsUtil.toStringArray(assemblies),
CollectionsUtil.toStringArray(excludedAssemblies),
SystemInfo.isFileSystemCaseSensitive);
File[] assemblyFiles = finder.findFiles(context.getWorkingDirectory());
if(assemblyFiles.length == 0) {
if (assemblyFiles.length == 0) {
logger.warning("No assemblies were matched - no tests will be run!");
}
for(File assembly : assemblyFiles) {

for (File assembly : assemblyFiles) {
String activityBlockName = "Testing " + assembly.getName();
logger.activityStarted(activityBlockName, assembly.getAbsolutePath(), DefaultMessagesInfo.BLOCK_TYPE_MODULE);

String filePath = assembly.getAbsolutePath();
String commandLineFlags = getCommandLineFlags(version);
logger.message("Commandline: " + runnerPath + " " + filePath + " " + commandLineFlags);
ProcessBuilder processBuilder = new ProcessBuilder(runnerPath, filePath, commandLineFlags);
logger.message("Commandline: " + runnerPath + " " + filePath + " " + commandLineFlags + " " + commandLineArguments);

List<String> commandLine = new ArrayList<>();
commandLine.add(runnerPath);
commandLine.add(filePath);
commandLine.add(commandLineFlags);

Matcher m = Pattern.compile("([^\"]\\S*|\".+?\")\\s*").matcher(commandLineArguments);
while (m.find())
commandLine.add(m.group(1).replace("\"", ""));

ProcessBuilder processBuilder = new ProcessBuilder(commandLine);

// Copy environment variables
Map<String, String> env = processBuilder.environment();
for(Map.Entry<String, String> kvp : context.getBuildParameters().getEnvironmentVariables().entrySet()) {
for (Map.Entry<String, String> kvp : context.getBuildParameters().getEnvironmentVariables().entrySet()) {
env.put(kvp.getKey(), kvp.getValue());
}

testRunnerProcess = processBuilder.start();
Process testRunnerProcess = processBuilder.start();
processes.put(testRunnerProcess, activityBlockName);

redirectStreamToLogger(testRunnerProcess.getInputStream(), new RedirectionTarget() {
public void redirect(String s) {
Expand All @@ -103,28 +126,56 @@ public void redirect(String s) {
logger.warning(s);
}
});

int exitCode = testRunnerProcess.waitFor();
if(exitCode != 0) {
logger.warning("Test runner exited with non-zero status!");
status = BuildFinishedStatus.FINISHED_FAILED;

while (true) {
int liveProcessCount = 0;
for (Map.Entry<Process, String> p : processes.entrySet()) {
if (isRunning(p.getKey())) {
++liveProcessCount;
}
}
if (liveProcessCount < numberOfParallelProcesses) break;
Thread.sleep(100);
}

logger.activityFinished(activityBlockName, DefaultMessagesInfo.BLOCK_TYPE_MODULE);
}

for (Map.Entry<Process, String> p : processes.entrySet()) {
int exitCode = p.getKey().waitFor();
if (version.charAt(0) == '2' && version.charAt(2) == '2') {
// From 2.2 the exit code actually indicates if there was an error with the command line / runtime. https://github.com/xunit/xunit/issues/659
if(exitCode > 1) {
logger.warning("Test runner exited with runtime error! Returned status code was " + exitCode);
status = BuildFinishedStatus.FINISHED_FAILED;
}
}
// Checking the exit code on versions below 2.2 is actually useless, as they break the TeamCity function to ignore failed tests.
// The exit code on older versions of xunit always indicates the number of failed tests.

logger.activityFinished(p.getValue(), DefaultMessagesInfo.BLOCK_TYPE_MODULE);
}
return status;
}
catch(Exception e) {
} catch (Exception e) {
logger.message("Failed to run tests");
logger.exception(e);
return BuildFinishedStatus.FINISHED_FAILED;
} finally {
processes.clear();
}
}

boolean isRunning(Process process) {
try {
process.exitValue();
return false;
} catch (Exception e) {
return true;
}
}

private interface RedirectionTarget {
void redirect(String s);
}

private void redirectStreamToLogger(final InputStream s, final RedirectionTarget target) {
new Thread(new Runnable() {
public void run() {
Expand All @@ -141,7 +192,7 @@ private String getCommandLineFlags(String version) {
// This is quite crude at the moment, but does the job.
// TODO: Migrate this into RunnerVersion or similar
char majorVersion = version.charAt(0);
if(majorVersion == '1')
if (majorVersion == '1')
return "/teamcity";
return "-teamcity";
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,20 @@ public String getRunnerPath(String runtime, String platform) {
sb.append(".exe");
return sb.toString();
}
});

AvailableRunners.put("2.2.0", new RunnerVersion("2.2.0",
new String[]{Runtime.dotNET45},
new String[]{Platforms.x86, Platforms.MSIL}) {
@Override
public String getRunnerPath(String runtime, String platform) {
StringBuilder sb = new StringBuilder();
sb.append("xunit.console");
if (platform.equals(Platforms.x86))
sb.append(".x86");
sb.append(".exe");
return sb.toString();
}
});
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ public final class StringConstants {
public static final String RunTypeName = "xUnitRunner";
public static final String ToolName = "xunit-runner"; // Should mirror the xunit-runner artifactId

public static final String ParameterName_XUnitVersion = "xUnitVersion";
public static final String ParameterName_XUnitVersion = "xUnitVersion";
public static final String ParameterName_CommandLineArguments = "commandLineArguments";
public static final String ParameterName_NumberOfParallelProcesses = "numberOfParallelProcesses";
public static final String ParameterName_IncludedAssemblies = "includedAssemblies";
public static final String ParameterName_ExcludedAssemblies = "excludedAssemblies";
public static final String ParameterName_Platform = "runnerPlatform";
Expand All @@ -14,6 +16,12 @@ public final class StringConstants {
public String getParameterName_XUnitVersion() {
return ParameterName_XUnitVersion;
}
public String getParameterName_CommandLineArguments() {
return ParameterName_CommandLineArguments;
}
public String getParameterName_NumberOfParallelProcesses() {
return ParameterName_NumberOfParallelProcesses;
}
public String getParameterName_IncludedAssemblies() {
return ParameterName_IncludedAssemblies;
}
Expand Down
Loading

0 comments on commit 6f1fb84

Please sign in to comment.