mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-06-28 02:11:43 +00:00
WPILib Reorganization
This is a major restructuring of the WPILib repository to simply build procedures and remove the remnants of Maven from everything except the eclipse plugins. Gradle files have been largely simplified or rewritten, taking advantage of splitting up parts of the build into separate build files for ease of reading. The eclipse plugins are now in a separate project, as is ntcore. All dependencies are resolved via Maven dependencies, with the Jenkins-maintained WPILib repo. Project structures have also been simplified: we no longer have separate subprojects inside wpilibc and wpilibj. Where possible, these changes hav been done with git renames, to make sure we still have full history for all repositories. Other unrelated subprojects have also been broken out: OutlineViewer is now a separate project. Change-Id: Ib4e2a6e1a2f66427a14f16612b0e0d69ed661878
This commit is contained in:
@@ -0,0 +1,230 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) FIRST 2008-2014. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
package edu.wpi.first.wpilibj.test;
|
||||
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Rule;
|
||||
import org.junit.rules.TestWatcher;
|
||||
import org.junit.runner.Description;
|
||||
import org.junit.runners.model.MultipleFailureException;
|
||||
|
||||
import edu.wpi.first.wpilibj.DriverStation;
|
||||
import edu.wpi.first.wpilibj.RobotBase;
|
||||
import edu.wpi.first.wpilibj.Timer;
|
||||
import edu.wpi.first.wpilibj.communication.FRCNetworkCommunicationsLibrary;
|
||||
import edu.wpi.first.wpilibj.livewindow.LiveWindow;
|
||||
|
||||
/**
|
||||
* This class serves as a superclass for all tests that involve the hardware on
|
||||
* the roboRIO. It uses an {@link BeforeClass} statement to initialize network
|
||||
* communications. Any test that needs to use the hardware <b>MUST</b> extend
|
||||
* from this class, to ensure that all of the hardware will be able to run.
|
||||
*$
|
||||
* @author Fredric Silberberg
|
||||
* @author Jonathan Leitschuh
|
||||
*/
|
||||
public abstract class AbstractComsSetup {
|
||||
/** Stores whether network coms have been initialized */
|
||||
private static boolean initialized = false;
|
||||
|
||||
/**
|
||||
* This sets up the network communications library to enable the driver
|
||||
* station. After starting network coms, it will loop until the driver station
|
||||
* returns that the robot is enabled, to ensure that tests will be able to run
|
||||
* on the hardware.
|
||||
*$
|
||||
*/
|
||||
static {
|
||||
if (!initialized) {
|
||||
// Set some implementations so that the static methods work properly
|
||||
RobotBase.initializeHardwareConfiguration();
|
||||
FRCNetworkCommunicationsLibrary.FRCNetworkCommunicationObserveUserProgramStarting();
|
||||
|
||||
LiveWindow.setEnabled(false);
|
||||
TestBench.out().println("Started coms");
|
||||
|
||||
// Wait until the robot is enabled before starting the tests
|
||||
int i = 0;
|
||||
while (!DriverStation.getInstance().isEnabled()) {
|
||||
try {
|
||||
Thread.sleep(100);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
// Prints the message on one line overwriting itself each time
|
||||
TestBench.out().print("\rWaiting for enable: " + i++);
|
||||
}
|
||||
TestBench.out().println();
|
||||
|
||||
// Ready to go!
|
||||
initialized = true;
|
||||
TestBench.out().println("Running!");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected abstract Logger getClassLogger();
|
||||
|
||||
/**
|
||||
* This causes a stack trace to be printed as the test is running as well as
|
||||
* at the end
|
||||
*/
|
||||
@Rule
|
||||
public final TestWatcher getTestWatcher() {
|
||||
return getOverridenTestWatcher();
|
||||
}
|
||||
|
||||
/**
|
||||
* Given as a way to provide a custom test watcher for a test or set of tests
|
||||
*$
|
||||
* @return the test watcher to use
|
||||
*/
|
||||
protected TestWatcher getOverridenTestWatcher() {
|
||||
return new DefaultTestWatcher();
|
||||
}
|
||||
|
||||
protected class DefaultTestWatcher extends TestWatcher {
|
||||
/**
|
||||
* Allows a failure to supply a custom status message to be displayed along
|
||||
* with the stack trace.
|
||||
*/
|
||||
protected void failed(Throwable e, Description description, String status) {
|
||||
TestBench.out().println();
|
||||
// Instance of is the best way I know to retrieve this data.
|
||||
if (e instanceof MultipleFailureException) {
|
||||
/*
|
||||
* MultipleFailureExceptions hold multiple exceptions in one exception.
|
||||
* In order to properly display these stack traces we have to cast the
|
||||
* throwable and work with the list of thrown exceptions stored within
|
||||
* it.
|
||||
*/
|
||||
int i = 1; // Running exception count
|
||||
int failureCount = ((MultipleFailureException) e).getFailures().size();
|
||||
for (Throwable singleThrown : ((MultipleFailureException) e).getFailures()) {
|
||||
getClassLogger().logp(
|
||||
Level.SEVERE,
|
||||
description.getClassName(),
|
||||
description.getMethodName(),
|
||||
(i++) + "/" + failureCount + " " + description.getDisplayName() + " failed "
|
||||
+ singleThrown.getMessage() + "\n" + status, singleThrown);
|
||||
}
|
||||
|
||||
} else {
|
||||
getClassLogger().logp(Level.SEVERE, description.getClassName(),
|
||||
description.getMethodName(),
|
||||
description.getDisplayName() + " failed " + e.getMessage() + "\n" + status, e);
|
||||
}
|
||||
super.failed(e, description);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*$
|
||||
* @see org.junit.rules.TestWatcher#failed(java.lang.Throwable,
|
||||
* org.junit.runner.Description)
|
||||
*/
|
||||
@Override
|
||||
protected void failed(Throwable e, Description description) {
|
||||
failed(e, description, "");
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*$
|
||||
* @see org.junit.rules.TestWatcher#starting(org.junit.runner.Description)
|
||||
*/
|
||||
@Override
|
||||
protected void starting(Description description) {
|
||||
TestBench.out().println();
|
||||
// Wait until the robot is enabled before starting the next tests
|
||||
int i = 0;
|
||||
while (!DriverStation.getInstance().isEnabled()) {
|
||||
try {
|
||||
Thread.sleep(100);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
// Prints the message on one line overwriting itself each time
|
||||
TestBench.out().print("\rWaiting for enable: " + i++);
|
||||
}
|
||||
getClassLogger().logp(Level.INFO, description.getClassName(), description.getMethodName(),
|
||||
"Starting");
|
||||
super.starting(description);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void succeeded(Description description) {
|
||||
simpleLog(Level.INFO, "TEST PASSED!");
|
||||
super.succeeded(description);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Logs a simple message without the logger formatting associated with it.
|
||||
*$
|
||||
* @param level The level to log the message at
|
||||
* @param message The message to log
|
||||
*/
|
||||
protected void simpleLog(Level level, String message) {
|
||||
if (getClassLogger().isLoggable(level)) {
|
||||
TestBench.out().println(message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provided as a replacement to lambda functions to allow for repeatable
|
||||
* checks to see if a correct state is reached
|
||||
*$
|
||||
* @author Jonathan Leitschuh
|
||||
*
|
||||
*/
|
||||
public abstract class BooleanCheck {
|
||||
public BooleanCheck() {}
|
||||
|
||||
/**
|
||||
* Runs the enclosed code and evaluates it to determine what state it should
|
||||
* return.
|
||||
*$
|
||||
* @return true if the code provided within the method returns true
|
||||
*/
|
||||
abstract public boolean getAsBoolean();
|
||||
};
|
||||
|
||||
/**
|
||||
* Delays until either the correct state is reached or we reach the timeout.
|
||||
*$
|
||||
* @param level The level to log the message at.
|
||||
* @param timeout How long the delay should run before it should timeout and
|
||||
* allow the test to continue
|
||||
* @param message The message to accompany the delay. The message will display
|
||||
* 'message' took 'timeout' seconds if it passed.
|
||||
* @param correctState A method to determine if the test has reached a state
|
||||
* where it is valid to continue
|
||||
* @return a double representing how long the delay took to run in seconds.
|
||||
*/
|
||||
public double delayTillInCorrectStateWithMessage(Level level, double timeout, String message,
|
||||
BooleanCheck correctState) {
|
||||
int i = 0;
|
||||
// As long as we are not in the correct state and the timeout has not been
|
||||
// reached then continue to run this loop
|
||||
for (i = 0; i < (timeout * 100) && !correctState.getAsBoolean(); i++) {
|
||||
Timer.delay(.01);
|
||||
}
|
||||
if (correctState.getAsBoolean()) {
|
||||
simpleLog(level, message + " took " + (i * .01) + " seconds");
|
||||
} else {
|
||||
simpleLog(level, message + " timed out after " + (i * .01) + " seconds");
|
||||
}
|
||||
return i * .01;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,280 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) FIRST 2008-2014. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
package edu.wpi.first.wpilibj.test;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
import java.util.Vector;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.Request;
|
||||
import org.junit.runners.Suite.SuiteClasses;
|
||||
import org.junit.runners.model.InitializationError;
|
||||
|
||||
/**
|
||||
* Allows tests suites and tests to be run selectively from the command line
|
||||
* using a regex text pattern.
|
||||
*$
|
||||
* @author jonathanleitschuh
|
||||
*
|
||||
*/
|
||||
public abstract class AbstractTestSuite {
|
||||
private static final Logger logger = Logger.getLogger(AbstractTestSuite.class.getName());
|
||||
|
||||
/**
|
||||
* Gets all of the classes listed within the SuiteClasses annotation. To use
|
||||
* it, annotate a class with <code>@RunWith(Suite.class)</code> and
|
||||
* <code>@SuiteClasses({TestClass1.class, ...})</code>. When you run this
|
||||
* class, it will run all the tests in all the suite classes. When loading the
|
||||
* tests using regex the test list will be generated from this annotation.
|
||||
*$
|
||||
* @return the list of classes listed in the
|
||||
* <code>@SuiteClasses({TestClass1.class, ...})</code> annotation.
|
||||
*/
|
||||
protected List<Class<?>> getAnnotatedTestClasses() {
|
||||
SuiteClasses annotation = getClass().getAnnotation(SuiteClasses.class);
|
||||
List<Class<?>> classes = new Vector<Class<?>>();
|
||||
if (annotation == null) {
|
||||
throw new RuntimeException(String.format("class '%s' must have a SuiteClasses annotation",
|
||||
getClass().getName()));
|
||||
}
|
||||
for (Class<?> c : annotation.value()) {
|
||||
classes.add(c);
|
||||
}
|
||||
return classes;
|
||||
}
|
||||
|
||||
/**
|
||||
*$
|
||||
* @param check
|
||||
* @return
|
||||
*/
|
||||
private boolean areAnySuperClassesOfTypeAbstractTestSuite(Class<?> check) {
|
||||
while (check != null) {
|
||||
if (check.equals(AbstractTestSuite.class)) {
|
||||
return true;
|
||||
}
|
||||
check = check.getSuperclass();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores a method name and method class pair. Used when searching for methods
|
||||
* matching a given regex text.
|
||||
*$
|
||||
* @author jonathanleitschuh
|
||||
*
|
||||
*/
|
||||
protected class ClassMethodPair {
|
||||
public final Class<?> methodClass;
|
||||
public final String methodName;
|
||||
|
||||
public ClassMethodPair(Class<?> klass, Method m) {
|
||||
this.methodClass = klass;
|
||||
this.methodName = m.getName();
|
||||
}
|
||||
|
||||
public Request getMethodRunRequest() {
|
||||
return Request.method(methodClass, methodName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param regex
|
||||
* @return
|
||||
*/
|
||||
protected List<ClassMethodPair> getMethodMatching(final String regex) {
|
||||
List<ClassMethodPair> classMethodPairs = new Vector<ClassMethodPair>();
|
||||
// Get all of the test classes
|
||||
for (Class<?> c : getAllContainedBaseTests()) {
|
||||
for (Method m : c.getMethods()) {
|
||||
// If this is a test method that is not trying to be ignored and it
|
||||
// matches the regex
|
||||
if (m.getAnnotation(Test.class) != null && m.getAnnotation(Ignore.class) == null
|
||||
&& Pattern.matches(regex, m.getName())) {
|
||||
ClassMethodPair pair = new ClassMethodPair(c, m);
|
||||
classMethodPairs.add(pair);
|
||||
}
|
||||
}
|
||||
}
|
||||
return classMethodPairs;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets all of the test classes listed in this suite. Does not include any of
|
||||
* the test suites. All of these classes contain tests.
|
||||
*$
|
||||
* @param runningList the running list of classes to prevent recursion.
|
||||
* @return The list of base test classes.
|
||||
*/
|
||||
private List<Class<?>> getAllContainedBaseTests(List<Class<?>> runningList) {
|
||||
for (Class<?> c : getAnnotatedTestClasses()) {
|
||||
// Check to see if this is a test class or a suite
|
||||
if (areAnySuperClassesOfTypeAbstractTestSuite(c)) {
|
||||
// Create a new instance of this class so that we can retrieve its data
|
||||
try {
|
||||
AbstractTestSuite suite = null;
|
||||
Object o = c.newInstance();
|
||||
suite = (AbstractTestSuite) c.newInstance();
|
||||
// Add the tests from this suite that match the regex to the list of
|
||||
// tests to run
|
||||
runningList = suite.getAllContainedBaseTests(runningList);
|
||||
} catch (InstantiationException | IllegalAccessException e) {
|
||||
// This shouldn't happen unless the constructor is changed in some
|
||||
// way.
|
||||
logger.log(Level.SEVERE, "Test suites can not take paramaters in their constructors.", e);
|
||||
}
|
||||
} else if (c.getAnnotation(SuiteClasses.class) != null) {
|
||||
logger.log(Level.SEVERE,
|
||||
String.format("class '%s' must extend %s to be searchable using regex.", c.getName()),
|
||||
AbstractTestSuite.class.getName());
|
||||
} else { // This is a class containing tests
|
||||
// so add it to the list
|
||||
runningList.add(c);
|
||||
}
|
||||
}
|
||||
return runningList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all of the test classes listed in this suite. Does not include any of
|
||||
* the test suites. All of these classes contain tests.
|
||||
*$
|
||||
* @return The list of base test classes.
|
||||
*/
|
||||
public List<Class<?>> getAllContainedBaseTests() {
|
||||
List<Class<?>> runningBaseTests = new Vector<Class<?>>();
|
||||
return getAllContainedBaseTests(runningBaseTests);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieves all of the classes listed in the
|
||||
* <code>@SuiteClasses<code> annotation that match the given regex text.
|
||||
*$
|
||||
* @param regex the text pattern to retrieve.
|
||||
* @param runningList the running list of classes to prevent recursion
|
||||
* @return The list of classes matching the regex pattern
|
||||
*/
|
||||
private List<Class<?>> getAllClassMatching(final String regex, final List<Class<?>> runningList) {
|
||||
for (Class<?> c : getAnnotatedTestClasses()) {
|
||||
// Compare the regex against the simple name of the class
|
||||
if (Pattern.matches(regex, c.getName()) && !runningList.contains(c)) {
|
||||
runningList.add(c);
|
||||
}
|
||||
}
|
||||
return runningList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all of the classes listed in the
|
||||
* <code>@SuiteClasses<code> annotation that match the given regex text.
|
||||
*$
|
||||
* @param regex the text pattern to retrieve.
|
||||
* @return The list of classes matching the regex pattern
|
||||
*/
|
||||
public List<Class<?>> getAllClassMatching(final String regex) {
|
||||
final List<Class<?>> matchingClasses = new Vector<Class<?>>();
|
||||
return getAllClassMatching(regex, matchingClasses);
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches through all of the suites and tests and loads only the test or
|
||||
* test suites matching the regex text. This method also prevents a single
|
||||
* test from being loaded multiple times by loading the suite first then
|
||||
* loading tests from all non loaded suites.
|
||||
*$
|
||||
* @param regex the regex text to search for
|
||||
* @return the list of suite and/or test classes matching the regex.
|
||||
*/
|
||||
private List<Class<?>> getSuiteOrTestMatchingRegex(final String regex, List<Class<?>> runningList) {
|
||||
// Get any test suites matching the regex using the superclass methods
|
||||
runningList = getAllClassMatching(regex, runningList);
|
||||
|
||||
|
||||
// Then search any test suites not retrieved already for test classes
|
||||
// matching the regex.
|
||||
List<Class<?>> unCollectedSuites = getAllClasses();
|
||||
// If we already have a test suite then we don't want to load the test twice
|
||||
// so remove the suite from the list
|
||||
unCollectedSuites.removeAll(runningList);
|
||||
for (Class<?> c : unCollectedSuites) {
|
||||
// Prevents recursively adding tests/suites that have already been added
|
||||
if (!runningList.contains(c)) {
|
||||
try {
|
||||
AbstractTestSuite suite = null;
|
||||
// Check the class to make sure that it is not a test class
|
||||
if (areAnySuperClassesOfTypeAbstractTestSuite(c)) {
|
||||
// Create a new instance of this class so that we can retrieve its
|
||||
// data.
|
||||
Object o = c.newInstance();
|
||||
suite = (AbstractTestSuite) c.newInstance();
|
||||
// Add the tests from this suite that match the regex to the list of
|
||||
// tests to run
|
||||
runningList = suite.getSuiteOrTestMatchingRegex(regex, runningList);
|
||||
}
|
||||
|
||||
} catch (InstantiationException | IllegalAccessException e) {
|
||||
// This shouldn't happen unless the constructor is changed in some
|
||||
// way.
|
||||
logger.log(Level.SEVERE, "Test suites can not take paramaters in their constructors.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
return runningList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches through all of the suites and tests and loads only the test or
|
||||
* test suites matching the regex text. This method also prevents a single
|
||||
* test from being loaded multiple times by loading the suite first then
|
||||
* loading tests from all non loaded suites.
|
||||
*$
|
||||
* @param regex the regex text to search for
|
||||
* @return the list of suite and/or test classes matching the regex.
|
||||
*/
|
||||
protected List<Class<?>> getSuiteOrTestMatchingRegex(final String regex) {
|
||||
final List<Class<?>> matchingClasses = new Vector<Class<?>>();
|
||||
return getSuiteOrTestMatchingRegex(regex, matchingClasses);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Retrieves all of the classes listed in the
|
||||
* <code>@SuiteClasses<code> annotation.
|
||||
*$
|
||||
* @return
|
||||
* @throws InitializationError If the <code>@SuiteClasses<code> annotation is
|
||||
* missing.
|
||||
*/
|
||||
public List<Class<?>> getAllClasses() {
|
||||
return getAnnotatedTestClasses();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name of all of the classes listed within the
|
||||
* <code>@SuiteClasses<code> annotation.
|
||||
*$
|
||||
* @return the list of classes.
|
||||
* @throws InitializationError If the <code>@SuiteClasses<code> annotation is
|
||||
* missing.
|
||||
*/
|
||||
public List<String> getAllClassName() {
|
||||
List<String> classNames = new Vector<String>();
|
||||
for (Class<?> c : getAnnotatedTestClasses()) {
|
||||
classNames.add(c.getName());
|
||||
}
|
||||
return classNames;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) FIRST 2008-2014. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
package edu.wpi.first.wpilibj.test;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import org.apache.tools.ant.BuildLogger;
|
||||
import org.apache.tools.ant.DefaultLogger;
|
||||
import org.apache.tools.ant.Project;
|
||||
import org.apache.tools.ant.taskdefs.optional.junit.FormatterElement;
|
||||
import org.apache.tools.ant.taskdefs.optional.junit.JUnitTask;
|
||||
import org.apache.tools.ant.taskdefs.optional.junit.JUnitTest;
|
||||
|
||||
/**
|
||||
* Provides an entry point for tests to run with ANT. This allows ant to output
|
||||
* JUnit XML test results for Jenkins.
|
||||
*$
|
||||
* @author jonathanleitschuh
|
||||
*
|
||||
*/
|
||||
public class AntJunitLanucher {
|
||||
|
||||
public static void main(String... args) {
|
||||
if (args.length == 0) {
|
||||
String path =
|
||||
String.format("%s/%s", System.getProperty("user.dir"), "/testResults/AntReports");
|
||||
String pathToReports = path;
|
||||
Project project = new Project();
|
||||
|
||||
try {
|
||||
// Create the file to store the test output
|
||||
new File(pathToReports).mkdirs();
|
||||
JUnitTask task = new JUnitTask();
|
||||
|
||||
project.setProperty("java.io.tmpdir", pathToReports);
|
||||
|
||||
/* Output to screen */
|
||||
FormatterElement.TypeAttribute typeScreen = new FormatterElement.TypeAttribute();
|
||||
typeScreen.setValue("plain");
|
||||
FormatterElement formatToScreen = new FormatterElement();
|
||||
formatToScreen.setType(typeScreen);
|
||||
formatToScreen.setUseFile(false);
|
||||
formatToScreen.setOutput(System.out);
|
||||
task.addFormatter(formatToScreen);
|
||||
|
||||
// add a build listener to the project
|
||||
BuildLogger logger = new DefaultLogger();
|
||||
logger.setOutputPrintStream(System.out);
|
||||
logger.setErrorPrintStream(System.err);
|
||||
logger.setMessageOutputLevel(Project.MSG_INFO);
|
||||
logger.setEmacsMode(true);
|
||||
project.addBuildListener(logger);
|
||||
|
||||
task.setProject(project);
|
||||
|
||||
// Set the output to the XML file
|
||||
FormatterElement.TypeAttribute type = new FormatterElement.TypeAttribute();
|
||||
type.setValue("xml");
|
||||
|
||||
FormatterElement formater = new FormatterElement();
|
||||
formater.setType(type);
|
||||
task.addFormatter(formater);
|
||||
|
||||
// Create the JUnitTest
|
||||
JUnitTest test = new JUnitTest(TestSuite.class.getName());
|
||||
test.setTodir(new File(pathToReports));
|
||||
task.addTest(test);
|
||||
|
||||
TestBench.out().println("Beginning Test Execution With ANT");
|
||||
task.execute();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
} else {
|
||||
TestBench.out().println(
|
||||
"Run will not output XML for Jenkins because " + "tests are not being run with ANT");
|
||||
// This should never return as it makes its own call to
|
||||
// System.exit();
|
||||
TestSuite.main(args);
|
||||
}
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) FIRST 2008-2014. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
package edu.wpi.first.wpilibj.test;
|
||||
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* This class is designated to allow for simple testing of the library without
|
||||
* the overlying testing framework. This test is NOT run as a normal part of the
|
||||
* testing process and must be explicitly selected at runtime by using the
|
||||
* 'quick' argument.
|
||||
*$
|
||||
* This test should never be committed with changes to it but can be used during
|
||||
* development to aid in feature testing.
|
||||
*$
|
||||
* @author Jonathan Leitschuh
|
||||
*/
|
||||
public class QuickTest extends AbstractComsSetup {
|
||||
private static final Logger logger = Logger.getLogger(QuickTest.class.getName());
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*$
|
||||
* @see edu.wpi.first.wpilibj.test.AbstractComsSetup#getClassLogger()
|
||||
*/
|
||||
@Override
|
||||
protected Logger getClassLogger() {
|
||||
return logger;
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) FIRST 2008-2014. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
package edu.wpi.first.wpilibj.test;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.junit.rules.TestRule;
|
||||
import org.junit.runner.Description;
|
||||
import org.junit.runners.model.Statement;
|
||||
|
||||
/**
|
||||
* This JUnit Rule allows you to apply this rule to any test to allow it to run
|
||||
* multiple times. This is important if you have a test that fails only
|
||||
* "sometimes" and needs to be rerun to find the issue.
|
||||
*$
|
||||
* This code was originally found here: <a href=
|
||||
* "http://www.codeaffine.com/2013/04/10/running-junit-tests-repeatedly-without-loops/"
|
||||
* >Running JUnit Tests Repeatedly Without Loops<a>
|
||||
*$
|
||||
* @author Frank Appel
|
||||
*/
|
||||
public class RepeatRule implements TestRule {
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({java.lang.annotation.ElementType.METHOD})
|
||||
public @interface Repeat {
|
||||
public abstract int times();
|
||||
}
|
||||
|
||||
|
||||
private static class RepeatStatement extends Statement {
|
||||
|
||||
private final int times;
|
||||
private final Statement statement;
|
||||
|
||||
private RepeatStatement(int times, Statement statement) {
|
||||
this.times = times;
|
||||
this.statement = statement;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void evaluate() throws Throwable {
|
||||
for (int i = 0; i < times; i++) {
|
||||
statement.evaluate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Statement apply(Statement statement, Description description) {
|
||||
Statement result = statement;
|
||||
Repeat repeat = description.getAnnotation(Repeat.class);
|
||||
if (repeat != null) {
|
||||
int times = repeat.times();
|
||||
result = new RepeatStatement(times, statement);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,441 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) FIRST 2008-2014. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
package edu.wpi.first.wpilibj.test;
|
||||
|
||||
import java.io.PrintStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import edu.wpi.first.wpilibj.AnalogGyro;
|
||||
import edu.wpi.first.wpilibj.AnalogInput;
|
||||
import edu.wpi.first.wpilibj.AnalogOutput;
|
||||
import edu.wpi.first.wpilibj.CANJaguar;
|
||||
import edu.wpi.first.wpilibj.DigitalInput;
|
||||
import edu.wpi.first.wpilibj.DigitalOutput;
|
||||
import edu.wpi.first.wpilibj.Jaguar;
|
||||
import edu.wpi.first.wpilibj.Relay;
|
||||
import edu.wpi.first.wpilibj.Servo;
|
||||
import edu.wpi.first.wpilibj.Talon;
|
||||
import edu.wpi.first.wpilibj.Victor;
|
||||
import edu.wpi.first.wpilibj.fixtures.AnalogCrossConnectFixture;
|
||||
import edu.wpi.first.wpilibj.fixtures.CANMotorEncoderFixture;
|
||||
import edu.wpi.first.wpilibj.fixtures.DIOCrossConnectFixture;
|
||||
import edu.wpi.first.wpilibj.fixtures.MotorEncoderFixture;
|
||||
import edu.wpi.first.wpilibj.fixtures.RelayCrossConnectFixture;
|
||||
import edu.wpi.first.wpilibj.fixtures.TiltPanCameraFixture;
|
||||
import edu.wpi.first.wpilibj.interfaces.Gyro;
|
||||
import edu.wpi.first.wpilibj.mockhardware.FakePotentiometerSource;
|
||||
|
||||
/**
|
||||
* This class provides access to all of the elements on the test bench, for use
|
||||
* in fixtures. This class is a singleton, you should use {@link #getInstance()}
|
||||
* to obtain a reference to the {@code TestBench}.
|
||||
*
|
||||
* TODO: This needs to be updated to the most recent test bench setup.
|
||||
*
|
||||
* @author Fredric Silberberg
|
||||
*/
|
||||
public final class TestBench {
|
||||
|
||||
/**
|
||||
* The time that it takes to have a motor go from rotating at full speed to
|
||||
* completely stopped
|
||||
*/
|
||||
public static final double MOTOR_STOP_TIME = 0.25;
|
||||
|
||||
public static final int kTalonChannel = 10;
|
||||
public static final int kVictorChannel = 1;
|
||||
public static final int kJaguarChannel = 2;
|
||||
|
||||
/* TiltPanCamera Channels */
|
||||
public static final int kGyroChannel = 0;
|
||||
public static final double kGyroSensitivity = 0.013;
|
||||
public static final int kTiltServoChannel = 9;
|
||||
public static final int kPanServoChannel = 8;
|
||||
|
||||
|
||||
/* PowerDistributionPanel channels */
|
||||
public static final int kJaguarPDPChannel = 6;
|
||||
public static final int kVictorPDPChannel = 8;
|
||||
public static final int kTalonPDPChannel = 11;
|
||||
public static final int kCANJaguarPDPChannel = 5;
|
||||
|
||||
/* CAN ASSOICATED CHANNELS */
|
||||
public static final int kCANRelayPowerCycler = 1;
|
||||
public static final int kFakeJaguarPotentiometer = 1;
|
||||
public static final int kFakeJaguarForwardLimit = 20;
|
||||
public static final int kFakeJaguarReverseLimit = 21;
|
||||
public static final int kCANJaguarID = 2;
|
||||
|
||||
// THESE MUST BE IN INCREMENTING ORDER
|
||||
public static final int DIOCrossConnectB2 = 9;
|
||||
public static final int DIOCrossConnectB1 = 8;
|
||||
public static final int DIOCrossConnectA2 = 7;
|
||||
public static final int DIOCrossConnectA1 = 6;
|
||||
|
||||
/** The Singleton instance of the Test Bench */
|
||||
private static TestBench instance = null;
|
||||
|
||||
/**
|
||||
* The single constructor for the TestBench. This method is private in order
|
||||
* to prevent multiple TestBench objects from being allocated
|
||||
*/
|
||||
protected TestBench() {}
|
||||
|
||||
/**
|
||||
* Constructs a new set of objects representing a connected set of Talon
|
||||
* controlled Motors and an encoder
|
||||
*
|
||||
* @return a freshly allocated Talon, Encoder pair
|
||||
*/
|
||||
public MotorEncoderFixture<Talon> getTalonPair() {
|
||||
|
||||
MotorEncoderFixture<Talon> talonPair = new MotorEncoderFixture<Talon>() {
|
||||
@Override
|
||||
protected Talon giveSpeedController() {
|
||||
return new Talon(kTalonChannel);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected DigitalInput giveDigitalInputA() {
|
||||
return new DigitalInput(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected DigitalInput giveDigitalInputB() {
|
||||
return new DigitalInput(1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPDPChannel() {
|
||||
return kTalonPDPChannel;
|
||||
}
|
||||
};
|
||||
return talonPair;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new set of objects representing a connected set of Victor
|
||||
* controlled Motors and an encoder
|
||||
*
|
||||
* @return a freshly allocated Victor, Encoder pair
|
||||
*/
|
||||
public MotorEncoderFixture<Victor> getVictorPair() {
|
||||
|
||||
MotorEncoderFixture<Victor> vicPair = new MotorEncoderFixture<Victor>() {
|
||||
@Override
|
||||
protected Victor giveSpeedController() {
|
||||
return new Victor(kVictorChannel);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected DigitalInput giveDigitalInputA() {
|
||||
return new DigitalInput(2);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected DigitalInput giveDigitalInputB() {
|
||||
return new DigitalInput(3);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPDPChannel() {
|
||||
return kVictorPDPChannel;
|
||||
}
|
||||
};
|
||||
return vicPair;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new set of objects representing a connected set of Jaguar
|
||||
* controlled Motors and an encoder
|
||||
*
|
||||
* @return a freshly allocated Jaguar, Encoder pair
|
||||
*/
|
||||
public MotorEncoderFixture<Jaguar> getJaguarPair() {
|
||||
MotorEncoderFixture<Jaguar> jagPair = new MotorEncoderFixture<Jaguar>() {
|
||||
@Override
|
||||
protected Jaguar giveSpeedController() {
|
||||
return new Jaguar(kJaguarChannel);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected DigitalInput giveDigitalInputA() {
|
||||
return new DigitalInput(4);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected DigitalInput giveDigitalInputB() {
|
||||
return new DigitalInput(5);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPDPChannel() {
|
||||
return kJaguarPDPChannel;
|
||||
}
|
||||
};
|
||||
return jagPair;
|
||||
}
|
||||
|
||||
public class BaseCANMotorEncoderFixture extends CANMotorEncoderFixture {
|
||||
@Override
|
||||
protected CANJaguar giveSpeedController() {
|
||||
return new CANJaguar(kCANJaguarID);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected DigitalInput giveDigitalInputA() {
|
||||
return new DigitalInput(18);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected DigitalInput giveDigitalInputB() {
|
||||
return new DigitalInput(19);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected FakePotentiometerSource giveFakePotentiometerSource() {
|
||||
return new FakePotentiometerSource(kFakeJaguarPotentiometer, 360);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected DigitalOutput giveFakeForwardLimit() {
|
||||
return new DigitalOutput(kFakeJaguarForwardLimit);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected DigitalOutput giveFakeReverseLimit() {
|
||||
return new DigitalOutput(kFakeJaguarReverseLimit);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*$
|
||||
* @see
|
||||
* edu.wpi.first.wpilibj.fixtures.CANMotorEncoderFixture#givePoweCycleRelay
|
||||
* ()
|
||||
*/
|
||||
@Override
|
||||
protected Relay givePoweCycleRelay() {
|
||||
return new Relay(kCANRelayPowerCycler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPDPChannel() {
|
||||
return kCANJaguarPDPChannel;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new set of objects representing a connected set of CANJaguar
|
||||
* controlled Motors and an encoder<br>
|
||||
* Note: The CANJaguar is not freshly allocated because the CANJaguar lacks a
|
||||
* free() method
|
||||
*
|
||||
* @return an existing CANJaguar and a freshly allocated Encoder
|
||||
*/
|
||||
public CANMotorEncoderFixture getCanJaguarPair() {
|
||||
CANMotorEncoderFixture canPair = new BaseCANMotorEncoderFixture();
|
||||
return canPair;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new set of two Servo's and a Gyroscope.
|
||||
*
|
||||
* @return a freshly allocated Servo's and a freshly allocated Gyroscope
|
||||
*/
|
||||
public TiltPanCameraFixture getTiltPanCam() {
|
||||
TiltPanCameraFixture tpcam = new TiltPanCameraFixture() {
|
||||
@Override
|
||||
protected Gyro giveGyro() {
|
||||
AnalogGyro gyro = new AnalogGyro(kGyroChannel);
|
||||
gyro.setSensitivity(kGyroSensitivity);
|
||||
return gyro;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Servo giveTilt() {
|
||||
return new Servo(kTiltServoChannel);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Servo givePan() {
|
||||
return new Servo(kPanServoChannel);
|
||||
}
|
||||
};
|
||||
|
||||
return tpcam;
|
||||
}
|
||||
|
||||
public DIOCrossConnectFixture getDIOCrossConnectFixture(int inputPort, int outputPort) {
|
||||
DIOCrossConnectFixture dio = new DIOCrossConnectFixture(inputPort, outputPort);
|
||||
return dio;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets two lists of possible DIO pairs for the two pairs
|
||||
*$
|
||||
* @return
|
||||
*/
|
||||
private List<List<Integer[]>> getDIOCrossConnect() {
|
||||
List<List<Integer[]>> pairs = new ArrayList<List<Integer[]>>();
|
||||
List<Integer[]> setA =
|
||||
Arrays.asList(new Integer[][] {
|
||||
{new Integer(DIOCrossConnectA1), new Integer(DIOCrossConnectA2)},
|
||||
{new Integer(DIOCrossConnectA2), new Integer(DIOCrossConnectA1)}});
|
||||
pairs.add(setA);
|
||||
|
||||
List<Integer[]> setB =
|
||||
Arrays.asList(new Integer[][] {
|
||||
{new Integer(DIOCrossConnectB1), new Integer(DIOCrossConnectB2)},
|
||||
{new Integer(DIOCrossConnectB2), new Integer(DIOCrossConnectB1)}});
|
||||
pairs.add(setB);
|
||||
// NOTE: IF MORE DIOCROSSCONNECT PAIRS ARE ADDED ADD THEM HERE
|
||||
return pairs;
|
||||
}
|
||||
|
||||
public static AnalogCrossConnectFixture getAnalogCrossConnectFixture() {
|
||||
AnalogCrossConnectFixture analogIO = new AnalogCrossConnectFixture() {
|
||||
@Override
|
||||
protected AnalogOutput giveAnalogOutput() {
|
||||
return new AnalogOutput(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AnalogInput giveAnalogInput() {
|
||||
return new AnalogInput(2);
|
||||
}
|
||||
};
|
||||
return analogIO;
|
||||
}
|
||||
|
||||
public static RelayCrossConnectFixture getRelayCrossConnectFixture() {
|
||||
RelayCrossConnectFixture relay = new RelayCrossConnectFixture() {
|
||||
|
||||
@Override
|
||||
protected Relay giveRelay() {
|
||||
return new Relay(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected DigitalInput giveInputTwo() {
|
||||
return new DigitalInput(18);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected DigitalInput giveInputOne() {
|
||||
return new DigitalInput(19);
|
||||
}
|
||||
};
|
||||
return relay;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a single Collection containing all of the DIOCrossConnectFixtures in
|
||||
* all two pair combinations
|
||||
*$
|
||||
* @return
|
||||
*/
|
||||
public Collection<Integer[]> getDIOCrossConnectCollection() {
|
||||
Collection<Integer[]> pairs = new ArrayList<Integer[]>();
|
||||
for (Collection<Integer[]> collection : getDIOCrossConnect()) {
|
||||
pairs.addAll(collection);
|
||||
}
|
||||
return pairs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an array of pairs for the encoder to use using two different lists
|
||||
*$
|
||||
* @param listA
|
||||
* @param listB
|
||||
* @param flip whether this encoder needs to be flipped
|
||||
* @return A list of different inputs to use for the tests
|
||||
*/
|
||||
private Collection<Integer[]> getPairArray(List<Integer[]> listA, List<Integer[]> listB,
|
||||
boolean flip) {
|
||||
Collection<Integer[]> encoderPortPairs = new ArrayList<Integer[]>();
|
||||
for (Integer[] portPairsA : listA) {
|
||||
ArrayList<Integer[]> construtorInput = new ArrayList<Integer[]>();
|
||||
Integer inputs[] = new Integer[5];
|
||||
inputs[0] = portPairsA[0]; // InputA
|
||||
inputs[1] = portPairsA[1]; // InputB
|
||||
|
||||
for (Integer[] portPairsB : listB) {
|
||||
inputs[2] = portPairsB[0]; // OutputA
|
||||
inputs[3] = portPairsB[1]; // OutputB
|
||||
inputs[4] = flip ? 0 : 1; // The flip bit
|
||||
}
|
||||
|
||||
construtorInput.add(inputs);
|
||||
|
||||
inputs = inputs.clone();
|
||||
for (Integer[] portPairsB : listB) {
|
||||
inputs[2] = portPairsB[1]; // OutputA
|
||||
inputs[3] = portPairsB[0]; // OutputB
|
||||
inputs[4] = flip ? 0 : 1; // The flip bit
|
||||
}
|
||||
construtorInput.add(inputs);
|
||||
encoderPortPairs.addAll(construtorInput);
|
||||
}
|
||||
return encoderPortPairs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs the list of inputs to be used for the encoder test
|
||||
*$
|
||||
* @return A collection of different input pairs to use for the encoder
|
||||
*/
|
||||
public Collection<Integer[]> getEncoderDIOCrossConnectCollection() {
|
||||
Collection<Integer[]> encoderPortPairs = new ArrayList<Integer[]>();
|
||||
assert getDIOCrossConnect().size() == 2;
|
||||
encoderPortPairs.addAll(getPairArray(getDIOCrossConnect().get(0), getDIOCrossConnect().get(1),
|
||||
false));
|
||||
encoderPortPairs.addAll(getPairArray(getDIOCrossConnect().get(1), getDIOCrossConnect().get(0),
|
||||
false));
|
||||
assert (encoderPortPairs.size() == 8);
|
||||
return encoderPortPairs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the singleton of the TestBench. If the TestBench is not already
|
||||
* allocated in constructs an new instance of it. Otherwise it returns the
|
||||
* existing instance.
|
||||
*
|
||||
* @return The Singleton instance of the TestBench
|
||||
*/
|
||||
public static TestBench getInstance() {
|
||||
if (instance == null) {
|
||||
instance = new TestBench();
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides access to the output stream for the test system. This should be
|
||||
* used instead of System.out This is gives us a way to implement changes to
|
||||
* where the output text of this test system is sent.
|
||||
*$
|
||||
* @return The test bench global print stream.
|
||||
*/
|
||||
public static PrintStream out() {
|
||||
return System.out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides access to the error stream for the test system. This should be
|
||||
* used instead of System.err This is gives us a way to implement changes to
|
||||
* where the output text of this test system is sent.
|
||||
*$
|
||||
* @return The test bench global print stream.
|
||||
*/
|
||||
public static PrintStream err() {
|
||||
return System.err;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,382 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) FIRST 2008-2014. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
package edu.wpi.first.wpilibj.test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Vector;
|
||||
import java.util.logging.LogManager;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import junit.framework.JUnit4TestAdapter;
|
||||
import junit.runner.Version;
|
||||
|
||||
import org.junit.runner.JUnitCore;
|
||||
import org.junit.runner.Result;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runner.notification.Failure;
|
||||
import org.junit.runners.Suite;
|
||||
import org.junit.runners.Suite.SuiteClasses;
|
||||
|
||||
import edu.wpi.first.wpilibj.WpiLibJTestSuite;
|
||||
import edu.wpi.first.wpilibj.can.CANTestSuite;
|
||||
import edu.wpi.first.wpilibj.command.CommandTestSuite;
|
||||
import edu.wpi.first.wpilibj.smartdashboard.SmartDashboardTestSuite;
|
||||
|
||||
/**
|
||||
* The WPILibJ Integeration Test Suite collects all of the tests to be run by
|
||||
* junit. In order for a test to be run, it must be added the list of suite
|
||||
* classes below. The tests will be run in the order they are listed in the
|
||||
* suite classes annotation.
|
||||
*/
|
||||
@RunWith(Suite.class)
|
||||
// These are listed on separate lines to prevent merge conflicts
|
||||
@SuiteClasses({WpiLibJTestSuite.class, CANTestSuite.class, CommandTestSuite.class,
|
||||
SmartDashboardTestSuite.class})
|
||||
public class TestSuite extends AbstractTestSuite {
|
||||
static {
|
||||
// Sets up the logging output
|
||||
final InputStream inputStream = TestSuite.class.getResourceAsStream("/logging.properties");
|
||||
try {
|
||||
if (inputStream == null)
|
||||
throw new NullPointerException("./logging.properties was not loaded");
|
||||
LogManager.getLogManager().readConfiguration(inputStream);
|
||||
Logger.getAnonymousLogger().info("Loaded");
|
||||
} catch (final IOException | NullPointerException e) {
|
||||
Logger.getAnonymousLogger().severe("Could not load default logging.properties file");
|
||||
Logger.getAnonymousLogger().severe(e.getMessage());
|
||||
}
|
||||
|
||||
TestBench.out().println("Starting Tests");
|
||||
}
|
||||
private static final Logger WPILIBJ_ROOT_LOGGER = Logger.getLogger("edu.wpi.first.wpilibj");
|
||||
private static final Logger WPILIBJ_COMMAND_ROOT_LOGGER = Logger
|
||||
.getLogger("edu.wpi.first.wpilibj.command");
|
||||
|
||||
|
||||
private static final Class<?> QUICK_TEST = QuickTest.class;
|
||||
private static final String QUICK_TEST_FLAG = "--quick";
|
||||
private static final String HELP_FLAG = "--help";
|
||||
private static final String METHOD_NAME_FILTER = "--methodFilter=";
|
||||
private static final String METHOD_REPEAT_FILTER = "--repeat=";
|
||||
private static final String CLASS_NAME_FILTER = "--filter=";
|
||||
|
||||
private static TestSuite instance = null;
|
||||
|
||||
public static TestSuite getInstance() {
|
||||
if (instance == null) {
|
||||
instance = new TestSuite();
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* This has to be public so that the JUnit4
|
||||
*/
|
||||
public TestSuite() {}
|
||||
|
||||
/**
|
||||
* Displays a help message for the user when they use the --help flag at
|
||||
* runtime.
|
||||
*/
|
||||
protected static void displayHelp() {
|
||||
StringBuilder helpMessage = new StringBuilder("Test Parameters help: \n");
|
||||
helpMessage.append("\t" + QUICK_TEST_FLAG
|
||||
+ " will cause the quick test to be run. Ignores other flags except for "
|
||||
+ METHOD_REPEAT_FILTER + "\n");
|
||||
helpMessage.append("\t" + CLASS_NAME_FILTER
|
||||
+ " will use the supplied regex text to search for suite/test class names "
|
||||
+ "matching the regex and run them.\n");
|
||||
helpMessage.append("\t" + METHOD_NAME_FILTER
|
||||
+ " will use the supplied regex text to search for test methods (excluding methods "
|
||||
+ "with the @Ignore annotation) and run only those methods. Can be paired with "
|
||||
+ METHOD_REPEAT_FILTER + " to " + "repeat the selected tests multiple times.\n");
|
||||
helpMessage.append("\t" + METHOD_REPEAT_FILTER + " will repeat the tests selected with either "
|
||||
+ QUICK_TEST_FLAG + " or " + CLASS_NAME_FILTER
|
||||
+ " and run them the given number of times.\n");
|
||||
helpMessage
|
||||
.append("[NOTE] All regex uses the syntax defined by java.util.regex.Pattern. This documentation can be found at "
|
||||
+ "http://docs.oracle.com/javase/7/docs/api/java/util/regex/Pattern.html\n");
|
||||
helpMessage.append("\n");
|
||||
helpMessage.append("\n");
|
||||
|
||||
TestBench.out().println(helpMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lets the user know that they used the TestSuite improperly and gives
|
||||
* details about how to use it correctly in the future.
|
||||
*/
|
||||
protected static void displayInvalidUsage(String message, String... args) {
|
||||
StringBuilder invalidMessage = new StringBuilder("Invalid Usage: " + message + "\n");
|
||||
invalidMessage.append("Params received: ");
|
||||
for (String a : args) {
|
||||
invalidMessage.append(a + " ");
|
||||
}
|
||||
invalidMessage.append("\n");
|
||||
invalidMessage
|
||||
.append("For details on proper usage of the runtime flags please run again with the "
|
||||
+ HELP_FLAG + " flag.\n\n");
|
||||
|
||||
TestBench.out().println(invalidMessage);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints the loaded tests before they are run.
|
||||
*$
|
||||
* @param classes the classes that were loaded.
|
||||
*/
|
||||
protected static void printLoadedTests(final Class<?>... classes) {
|
||||
StringBuilder loadedTestsMessage = new StringBuilder("The following tests were loaded:\n");
|
||||
Package p = null;
|
||||
for (Class<?> c : classes) {
|
||||
if (c.getPackage().equals(p)) {
|
||||
p = c.getPackage();
|
||||
loadedTestsMessage.append(p.getName() + "\n");
|
||||
}
|
||||
loadedTestsMessage.append("\t" + c.getSimpleName() + "\n");
|
||||
}
|
||||
TestBench.out().println(loadedTestsMessage);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parses the arguments passed at runtime and runs the appropriate tests based
|
||||
* upon these arguments
|
||||
*$
|
||||
* @param args the args passed into the program at runtime
|
||||
* @return the restults of the tests that have run. If no tests were run then
|
||||
* null is returned.
|
||||
*/
|
||||
protected static Result parseArgsRunAndGetResult(final String[] args) {
|
||||
if (args.length == 0) { // If there are no args passed at runtime then just
|
||||
// run all of the tests.
|
||||
printLoadedTests(TestSuite.class);
|
||||
return JUnitCore.runClasses(TestSuite.class);
|
||||
}
|
||||
|
||||
// The method filter was passed
|
||||
boolean methodFilter = false;
|
||||
String methodRegex = "";
|
||||
// The class filter was passed
|
||||
boolean classFilter = false;
|
||||
String classRegex = "";
|
||||
boolean repeatFilter = false;
|
||||
int repeatCount = 1;
|
||||
|
||||
for (String s : args) {
|
||||
if (Pattern.matches(METHOD_NAME_FILTER + ".*", s)) {
|
||||
methodFilter = true;
|
||||
methodRegex = new String(s).replace(METHOD_NAME_FILTER, "");
|
||||
}
|
||||
if (Pattern.matches(METHOD_REPEAT_FILTER + ".*", s)) {
|
||||
repeatFilter = true;
|
||||
try {
|
||||
repeatCount = Integer.parseInt(new String(s).replace(METHOD_REPEAT_FILTER, ""));
|
||||
} catch (NumberFormatException e) {
|
||||
displayInvalidUsage("The argument passed to the repeat rule was not a valid integer.",
|
||||
args);
|
||||
}
|
||||
}
|
||||
if (Pattern.matches(CLASS_NAME_FILTER + ".*", s)) {
|
||||
classFilter = true;
|
||||
classRegex = new String(s).replace(CLASS_NAME_FILTER, "");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
ArrayList<String> argsParsed = new ArrayList<String>(Arrays.asList(args));
|
||||
if (argsParsed.contains(HELP_FLAG)) {
|
||||
// If the user inputs the help flag then return the help message and exit
|
||||
// without running any tests
|
||||
displayHelp();
|
||||
return null;
|
||||
}
|
||||
if (argsParsed.contains(QUICK_TEST_FLAG)) {
|
||||
printLoadedTests(QUICK_TEST);
|
||||
return JUnitCore.runClasses(QUICK_TEST);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores the data from multiple {@link Result}s in one class that can be
|
||||
* returned to display the results.
|
||||
*/
|
||||
class MultipleResult extends Result {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private final List<Failure> failures = new Vector<Failure>();
|
||||
private int runCount = 0;
|
||||
private int ignoreCount = 0;
|
||||
private long runTime = 0;
|
||||
|
||||
@Override
|
||||
public int getRunCount() {
|
||||
return runCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getFailureCount() {
|
||||
return failures.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getRunTime() {
|
||||
return runTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Failure> getFailures() {
|
||||
return failures;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIgnoreCount() {
|
||||
return ignoreCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given result's data to this result
|
||||
*$
|
||||
* @param r the result to add to this result
|
||||
*/
|
||||
void addResult(Result r) {
|
||||
failures.addAll(r.getFailures());
|
||||
runCount += r.getRunCount();
|
||||
ignoreCount += r.getIgnoreCount();
|
||||
runTime += r.getRunTime();
|
||||
}
|
||||
}
|
||||
|
||||
// If a specific method has been requested
|
||||
if (methodFilter) {
|
||||
List<ClassMethodPair> pairs = (new TestSuite()).getMethodMatching(methodRegex);
|
||||
if (pairs.size() == 0) {
|
||||
displayInvalidUsage("None of the arguments passed to the method name filter matched.", args);
|
||||
return null;
|
||||
}
|
||||
// Print out the list of tests before we run them
|
||||
TestBench.out().println("Running the following tests:");
|
||||
Class<?> classListing = null;
|
||||
for (ClassMethodPair p : pairs) {
|
||||
if (!p.methodClass.equals(classListing)) {
|
||||
// Only display the class name every time it changes
|
||||
classListing = p.methodClass;
|
||||
TestBench.out().println(classListing);
|
||||
}
|
||||
TestBench.out().println("\t" + p.methodName);
|
||||
}
|
||||
|
||||
|
||||
// The test results will be saved here
|
||||
MultipleResult results = new MultipleResult();
|
||||
// Runs tests multiple times if the repeat rule is used
|
||||
for (int i = 0; i < repeatCount; i++) {
|
||||
// Now run all of the tests
|
||||
for (ClassMethodPair p : pairs) {
|
||||
Result result = (new JUnitCore()).run(p.getMethodRunRequest());
|
||||
// Store the given results in one cohesive result
|
||||
results.addResult(result);
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
// If a specific class has been requested
|
||||
if (classFilter) {
|
||||
List<Class<?>> testClasses = (new TestSuite()).getSuiteOrTestMatchingRegex(classRegex);
|
||||
if (testClasses.size() == 0) {
|
||||
displayInvalidUsage("None of the arguments passed to the filter matched.", args);
|
||||
return null;
|
||||
}
|
||||
printLoadedTests(testClasses.toArray(new Class[0]));
|
||||
MultipleResult results = new MultipleResult();
|
||||
// Runs tests multiple times if the repeat rule is used
|
||||
for (int i = 0; i < repeatCount; i++) {
|
||||
Result result = (new JUnitCore()).run(testClasses.toArray(new Class[0]));
|
||||
// Store the given results in one cohesive result
|
||||
results.addResult(result);
|
||||
}
|
||||
return results;
|
||||
}
|
||||
displayInvalidUsage(
|
||||
"None of the parameters that you passed matched any of the accepted flags.", args);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected static void displayResults(Result result) {
|
||||
// Results are collected and displayed here
|
||||
TestBench.out().println("\n");
|
||||
if (!result.wasSuccessful()) {
|
||||
// Prints out a list of stack traces for the failed tests
|
||||
TestBench.out().println("Failure List: ");
|
||||
for (Failure f : result.getFailures()) {
|
||||
TestBench.out().println(f.getDescription());
|
||||
TestBench.out().println(f.getTrace());
|
||||
}
|
||||
TestBench.out().println();
|
||||
TestBench.out().println("FAILURES!!!");
|
||||
// Print the test statistics
|
||||
TestBench.out().println(
|
||||
"Tests run: " + result.getRunCount() + ", Failures: " + result.getFailureCount()
|
||||
+ ", Ignored: " + result.getIgnoreCount() + ", In " + result.getRunTime() + "ms");
|
||||
|
||||
// Prints out a list of test that failed
|
||||
TestBench.out().println("Failure List (short):");
|
||||
String failureClass = result.getFailures().get(0).getDescription().getClassName();
|
||||
TestBench.out().println(failureClass);
|
||||
for (Failure f : result.getFailures()) {
|
||||
if (!failureClass.equals(f.getDescription().getClassName())) {
|
||||
failureClass = f.getDescription().getClassName();
|
||||
TestBench.out().println(failureClass);
|
||||
}
|
||||
TestBench.err().println("\t" + f.getDescription());
|
||||
}
|
||||
} else {
|
||||
TestBench.out().println("SUCCESS!");
|
||||
TestBench.out().println(
|
||||
"Tests run: " + result.getRunCount() + ", Ignored: " + result.getIgnoreCount() + ", In "
|
||||
+ result.getRunTime() + "ms");
|
||||
}
|
||||
TestBench.out().println();
|
||||
}
|
||||
|
||||
/**
|
||||
* This is used by ant to get the Junit tests. This is required because the
|
||||
* tests try to load using a JUnit 3 framework. JUnit4 uses annotations to
|
||||
* load tests. This method allows JUnit3 to load JUnit4 tests.
|
||||
*/
|
||||
public static junit.framework.Test suite() {
|
||||
return new JUnit4TestAdapter(TestSuite.class);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The method called at runtime
|
||||
*$
|
||||
* @param args The test suites to run
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
TestBench.out().println("JUnit version " + Version.id());
|
||||
|
||||
// Tests are run here
|
||||
Result result = parseArgsRunAndGetResult(args);
|
||||
if (result != null) {
|
||||
// Results are collected and displayed here
|
||||
displayResults(result);
|
||||
|
||||
System.exit(result.wasSuccessful() ? 0 : 1);
|
||||
}
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* This is the starting point for the integration testing framework. This
|
||||
* package should contain classes that are not explicitly for testing the
|
||||
* library but instead provide the framework that the tests can extend from.
|
||||
* Every test should extend from
|
||||
* {@link edu.wpi.first.wpilibj.test.AbstractComsSetup} to ensure that Network
|
||||
* Communications is properly instantiated before the test is run.
|
||||
*$
|
||||
* The {@link edu.wpi.first.wpilibj.test.TestBench} should contain the port
|
||||
* numbers for all of the hardware and these values should not be explicitly
|
||||
* defined anywhere else in the testing framework.
|
||||
*/
|
||||
package edu.wpi.first.wpilibj.test;
|
||||
Reference in New Issue
Block a user