/****************************************************************************
* Copyright (c) 2008, 2012 Andreas Unger and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Andreas Unger - initial API and implementation
****************************************************************************/
package org.yakindu.sct.generator.c.gtest;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import junit.framework.AssertionFailedError;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.FileLocator;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Platform;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.runner.Description;
import org.junit.runner.Runner;
import org.junit.runner.notification.Failure;
import org.junit.runner.notification.RunNotifier;
import org.junit.runners.model.InitializationError;
import org.osgi.framework.Bundle;
import org.osgi.framework.FrameworkUtil;
/**
* @author Andreas Unger
*
*/
public class GTestRunner extends Runner {
private static final Pattern TEST_PATTERN = Pattern.compile("TEST(?:_F)?\\s*\\(\\s*(\\w+)\\s*,\\s*(\\w+)\\s*\\)");
private static final Pattern SL_COMMENT_PATTERN = Pattern.compile("//.*(?:\\r?\\n|\\z)");
private static final Pattern ML_COMMENT_PATTERN = Pattern.compile("(?:/\\*(?:[^*]|(?:\\*+[^*/]))*\\*+/)|(?://.*)");
private static final Pattern TEST_OUTPUT_PATTERN = Pattern.compile("\\[\\s*(\\w+)\\s*\\] (\\w+)\\.(\\w+)");
private Class<?> testClass;
private boolean ignore;
private Map<String, List<String>> testCases = new LinkedHashMap<String, List<String>>();
/**
*
*/
public GTestRunner(Class<?> testClass) throws InitializationError {
this.testClass = testClass;
GTest annotation = testClass.getAnnotation(GTest.class);
if (annotation == null) {
throw new InitializationError("Test class must specify " + GTest.class.getCanonicalName() + " annotation");
}
ignore = testClass.getAnnotation(Ignore.class) != null;
String sourceFile = annotation.sourceFile();
try {
CharSequence charSequence = readSourceFile(sourceFile);
String s = ML_COMMENT_PATTERN.matcher(SL_COMMENT_PATTERN.matcher(charSequence).replaceAll(""))
.replaceAll("");
Matcher matcher = TEST_PATTERN.matcher(s);
while (matcher.find()) {
String testCaseName = matcher.group(1);
String testName = matcher.group(2);
List<String> testCase = testCases.get(testCaseName);
if (testCase == null) {
testCase = new ArrayList<String>();
testCases.put(testCaseName, testCase);
}
testCase.add(testName);
}
} catch (IOException e) {
throw new InitializationError(e);
}
if (testCases.isEmpty()) {
throw new InitializationError("No tests specified");
}
}
private CharSequence readSourceFile(String sourceFile) throws IOException {
Bundle bundle = getTestBundle();
// System.out.println("[GTestRunner] Loaded bundle " + bundle.getSymbolicName());
InputStream is = FileLocator.openStream(bundle, new Path(sourceFile), false);
Reader reader = new InputStreamReader(is);
char[] buffer = new char[4096];
StringBuilder sb = new StringBuilder(buffer.length);
int count;
while ((count = reader.read(buffer)) != -1) {
sb.append(buffer, 0, count);
}
reader.close();
is.close();
return sb;
}
protected Bundle getTestBundle() {
String testProject = testClass.getAnnotation(GTest.class).testBundle();
if (!testProject.isEmpty()) {
Bundle testBundle = Platform.getBundle(testProject);
if (testBundle != null) {
return testBundle;
}
}
return FrameworkUtil.getBundle(testClass);
}
/*
* (non-Javadoc)
*
* @see org.junit.runner.Runner#getDescription()
*/
@Override
public Description getDescription() {
Description description = Description.createSuiteDescription(testClass);
for (Entry<String, List<String>> entry : testCases.entrySet()) {
for (String test : entry.getValue()) {
String testCase = entry.getKey();
Description childDescription = createDescription(testCase, test);
description.addChild(childDescription);
}
}
return description;
}
private Description createDescription(String testCase, String test) {
String name;
if (testCases.size() == 1) {
name = test;
} else {
name = testCase + "." + test;
}
return Description.createTestDescription(testClass, name);
}
/*
* (non-Javadoc)
*
* @see
* org.junit.runner.Runner#run(org.junit.runner.notification.RunNotifier)
*/
@Override
public void run(RunNotifier notifier) {
if (ignore) {
notifier.fireTestIgnored(getDescription());
} else {
try {
Object test = testClass.newInstance();
Method[] methods = testClass.getMethods();
for (Method method : methods) {
if (method.isAnnotationPresent(Before.class)) {
method.invoke(test);
}
}
runGTests(notifier);
for (Method method : methods) {
if (method.isAnnotationPresent(After.class)) {
method.invoke(test);
}
}
} catch (InvocationTargetException e) {
Throwable targetException = ((InvocationTargetException) e).getTargetException();
notifier.fireTestFailure(new Failure(getDescription(), targetException));
} catch (Throwable throwable) {
notifier.fireTestFailure(new Failure(getDescription(), throwable));
}
}
}
private void runGTests(RunNotifier notifier) throws IOException, InterruptedException {
String program = testClass.getAnnotation(GTest.class).program();
if (Platform.getOS().equalsIgnoreCase(Platform.OS_WIN32)) {
program += ".exe";
}
String targetProject = testClass.getAnnotation(GTest.class).testBundle();
IPath programPath = new Path(targetProject).append(program);
IResource programFile = ResourcesPlugin.getWorkspace().getRoot().findMember(programPath);
IContainer programContainer = programFile.getParent();
if (!programContainer.isAccessible()) {
throw new RuntimeException(
"Test program container " + programContainer.getLocation().toOSString() + " inaccessible");
}
File directory = programContainer.getLocation().toFile();
Process process = new ProcessBuilder(programFile.getLocation().toOSString()).redirectErrorStream(true)
.directory(directory).start();
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
boolean started = false;
boolean running = false;
StringBuilder message = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
if (line.startsWith("[====")) {
if (started) {
started = false;
// Eat remaining input
char[] buffer = new char[4096];
while (reader.read(buffer) != -1);
break;
}
started = true;
} else {
TestOutput testOutput = parseTestOutput(line);
if (testOutput != null) {
Description description = testOutput.toDescription();
switch (testOutput.getStatus()) {
case TestOutput.RUN :
running = true;
message.setLength(0);
notifier.fireTestStarted(description);
break;
case TestOutput.OK :
running = false;
notifier.fireTestFinished(description);
break;
default :
running = false;
notifier.fireTestFailure(
new Failure(description, new AssertionFailedError(message.toString())));
notifier.fireTestFinished(description);
break;
}
} else if (running) {
message.append(line);
message.append("\n");
}
}
}
process.waitFor();
if (started) {
throw new RuntimeException("Test quit unexpectedly (exit status " + process.exitValue() + "):\n" + message);
}
}
private TestOutput parseTestOutput(String s) {
Matcher matcher = TEST_OUTPUT_PATTERN.matcher(s);
if (matcher.find()) {
String statusString = matcher.group(1);
int status;
if ("RUN".equals(statusString)) {
status = TestOutput.RUN;
} else if ("OK".equals(statusString)) {
status = TestOutput.OK;
} else {
status = TestOutput.FAILED;
}
String testCaseName = matcher.group(2);
String testName = matcher.group(3);
return new TestOutput(status, testCaseName, testName);
}
return null;
}
private class TestOutput {
public static final int RUN = 0;
public static final int OK = 1;
public static final int FAILED = 2;
private int status;
private String testCaseName;
private String testName;
public TestOutput(int status, String testCaseName, String testName) {
this.status = status;
this.testCaseName = testCaseName;
this.testName = testName;
}
/**
* @return the status
*/
public int getStatus() {
return status;
}
public Description toDescription() {
String name;
if (testCases.size() == 1) {
name = testName;
} else {
name = testCaseName + "." + testName;
}
return Description.createTestDescription(testClass, name);
}
}
}