Skip to content

Instantly share code, notes, and snippets.

@sethrylan
Last active December 17, 2015 06:28
Show Gist options
  • Save sethrylan/5565452 to your computer and use it in GitHub Desktop.
Save sethrylan/5565452 to your computer and use it in GitHub Desktop.
A PaxExam and Cucumber JUnit Test Runner
/**
* This class allows for running Cucumber features tests using PaxExam in-container testing.
*
* When added as a RunWith annotation to a test class
* @RunWith(PaxCucumber.class)
* this effectively means that
* @RunWith(PaxCucumber.class) and @RunWith(Cucumber.class)
* are used as test runners for that class. Cucumber and PaxExam are run
* in parallel, and any Cucumber tasks must manually verify that any PaxExam
* dependencies have been satisfied.
*
* @author sethrylan
*/
public class PaxCucumber extends BlockJUnit4ClassRunner {
private PaxExam paxExamRunner;
private Cucumber cucumberRunner;
private final ExecutorService executorService = Executors.newCachedThreadPool();
private static Logger logger = LoggerFactory.getLogger(PaxCucumber.class);
public PaxCucumber(Class<?> klass) throws InitializationError, Exception {
super(klass);
paxExamRunner = new PaxExam(klass);
cucumberRunner = new Cucumber(klass);
}
@Override
public void run(RunNotifier rn) {
PaxExamTask paxExamTask = new PaxExamTask(rn, paxExamRunner);
executorService.submit(paxExamTask);
CucumberTask cucumberTask = new CucumberTask(rn, cucumberRunner);
executorService.submit(cucumberTask);
executorService.shutdown() ;
try {
executorService.awaitTermination(1, TimeUnit.DAYS);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public class PaxExamTask implements Callable<RunNotifier> {
private RunNotifier rn;
private PaxExam paxExamRunner;
public PaxExamTask(RunNotifier rn, PaxExam paxExamRunner) {
this.rn = rn;
this.paxExamRunner = paxExamRunner;
}
@Override
public RunNotifier call() throws Exception {
AbsoluteSingleton.getInstance().setValue(true);
this.paxExamRunner.run(this.rn);
logger.debug("PaxExamTask finished");
return null;
}
}
public class CucumberTask implements Callable<RunNotifier> {
private RunNotifier rn;
private Cucumber cucumberRunner;
public CucumberTask(RunNotifier rn, Cucumber cucumberRunner) {
this.rn = rn;
this.cucumberRunner = cucumberRunner;
}
@Override
public RunNotifier call() throws Exception {
this.cucumberRunner.run(this.rn);
logger.debug("CucumberTask finished");
AbsoluteSingleton.getInstance().setValue(false);
return null;
}
}
}
public abstract class PaxExamBaseIntegrationTest {
private static final String PROP_FRAGMENTS = "iehr.osgi.integrationTest.fragments";
private static final String PROP_BUNDLES = "iehr.osgi.integrationTest.bundles";
private static final String PROP_TIMEOUT = "iehr.osgi.integrationTest.paxexam.systemTimeout";
private static final String PROP_CONFIG_DIR = "iehr.osgi.integrationTest.configDir";
private Logger logger = LoggerFactory.getLogger(PaxExamBaseIntegrationTest.class);
@Inject
protected BundleContext ctx;
@Configuration
public Option[] config() {
List<Option> opts = new ArrayList<Option>();
String fragments = System.getProperty(PROP_FRAGMENTS);
String bundles = System.getProperty(PROP_BUNDLES);
String timeout = System.getProperty(PROP_TIMEOUT);
String configDir = System.getProperty(PROP_CONFIG_DIR);
// Load the systemTimeout value, if specified.
opts.add(getTimeoutOption(System.getProperty(PROP_TIMEOUT)));
// Load the jUnit bundles
opts.add(junitBundles());
// Load all bundle dependencies that are specified in the system
// property.
opts.addAll(getITestBundles(bundles));
opts.addAll(getITestFragments(fragments));
// Set the fileinstall directory to load Configuration Admin data
opts.add(getFileInstallConfigOption(configDir));
return opts.toArray(new Option[opts.size()]);
}
private Option getTimeoutOption(String timeout) {
Option opt = null;
if ((timeout != null) && !"".equals(timeout))
opt = systemTimeout(Integer.parseInt(timeout));
return opt;
}
private Option getFileInstallConfigOption(String configDir) {
Option opt = null;
if ((configDir != null) && !"".equals(configDir))
opt = frameworkProperty("felix.fileinstall.dir").value(configDir);
return opt;
}
private Collection<Option> getITestBundles(String bundleList) {
Collection<Option> opts = new HashSet<Option>();
if ((bundleList != null) && !"".equals(bundleList)) {
// TODO: validate the string
for (String bundle : bundleList.split("::")) {
if (opts.contains(bundle))
logger.debug("Loading Bundle --> " + bundle);
opts.add(bundle("file:" + bundle));
}
}
return opts;
}
private Collection<Option> getITestFragments(String fragmentList) {
Collection<Option> opts = new HashSet<Option>();
if ((fragmentList != null) && !"".equals(fragmentList)) {
// TODO: validate the string
for (String fragment : fragmentList.split("::")) {
if (opts.contains(fragment))
logger.debug("Loading fragment --> " + fragment);
opts.add(bundle("file:" + fragment).noStart());
}
}
return opts;
}
public void logOSGIBundles() {
assertNotNull(ctx);
int maxLen = 0;
for (Bundle bundle : ctx.getBundles()) {
if (bundle.getSymbolicName() != null) {
String name = bundle.getSymbolicName() + " ("
+ bundle.getVersion() + ")";
if (maxLen < name.length())
maxLen = name.length();
}
}
StringBuffer dashes = new StringBuffer();
for (int i = 0; i < maxLen; i++)
dashes.append("-");
logger.info("INSTALLED BUNDLES");
logger.info("-----------------");
logger.info("Level | Bundle | Bundle State");
logger.info("--------" + dashes + "------------");
String format = "%1$5s | %2$-" + maxLen + "s | ";
for (Bundle bundle : ctx.getBundles()) {
BundleStartLevel bsl = bundle.adapt(BundleStartLevel.class);
String b = bundle.getSymbolicName() + " (" + bundle.getVersion()
+ ")";
String msg = String.format(format, bsl.getStartLevel(), b)
+ getBundleStateString(bundle);
logger.info(msg);
}
}
private String getBundleStateString(Bundle bundle) {
int state = bundle.getState();
if (state == Bundle.ACTIVE) {
return "Active";
} else if (state == Bundle.INSTALLED) {
return "Installed";
} else if (state == Bundle.RESOLVED) {
return "Resolved";
} else if (state == Bundle.STARTING) {
return "Starting";
} else if (state == Bundle.STOPPING) {
return "Stopping";
} else {
return "Unknown";
}
}
public void logOSGIServices(){
try {
ServiceReference<?>[] refs = ctx.getAllServiceReferences(null, null);
List<String> serviceNames = new ArrayList<String>();
if (refs != null) {
logger.info("OSGi Services (" + refs.length + ")");
logger.info("-------------------------------");
for (ServiceReference<?> r : refs) {
Object o = ctx.getService(r);
Class<?> c = o.getClass();
logger.info("Service: " + c.getName());
Type[] intfs = c.getGenericInterfaces();
if (intfs.length != 0) {
for (Type intf : intfs)
logger.info(" - interface: " + intfs.toString());
}
serviceNames.add(o.getClass().getName());
ctx.ungetService(r);
}
/*
logger.info("OSGi Services (" + refs.length + ")");
logger.info("-------------------------------");
for (String name : serviceNames) {
logger.info("Service: " + name);
}
*/
}
}
catch (InvalidSyntaxException e) {
logger.error("logOSGIServices() exception thrown: " + e.getCause());
}
}
}
/**
* An acceptance test which uses PaxExam in-container testing
*
* Cucumber.Options documentation at:
* http://cukes.info/api/cucumber/jvm/javadoc/cucumber/api/junit/Cucumber.Options.html
* http://emprovisetech.blogspot.com/2012/11/acceptance-testing-cucumber-jvm.html
*
*/
@RunWith(PaxCucumber.class)
@ExamReactorStrategy(PerClass.class)
@Options( features = {"src/test/features"},
glue = {"classpath:"},
monochrome = false,
dryRun = false,
strict = false,
tags = {"~@WIP"},
format = { "pretty",
"html:build/cucumber",
"junit:build/cucumber/junit.xml",
"json-pretty:build/cucumber-json-report.json"})
public class RunCucumberTests extends PaxExamBaseIntegrationTest {
private static Logger logger = LoggerFactory.getLogger(RunCucumberTests.class);
static volatile Boolean keepRunning = true;
@Before
public void before() {
logOSGIBundles();
}
@Test
public void runAcceptanceTests() throws Exception {
logger.debug("System-under-test created. Running acceptance tests.");
// wait until Cucumber finishes
while(AbsoluteSingleton.getInstance().getValue()) {
Thread.sleep(1000);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment