org.pantsbuild.tools.junit.impl.SpecParser Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of junit-runner Show documentation
Show all versions of junit-runner Show documentation
A command line tool for running junit tests that provides functionality above and beyond
that provided by org.junit.runner.JUnitCore.
// Copyright 2016 Pants project contributors (see CONTRIBUTORS.md).
// Licensed under the Apache License, Version 2.0 (see LICENSE).
package org.pantsbuild.tools.junit.impl;
import com.google.common.base.Optional;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.LinkedHashMap;
import com.google.common.base.Preconditions;
import com.google.common.collect.Iterables;
/**
* Takes strings passed to the command line representing packages or individual methods
* and returns a parsed Spec. Each Spec represents a single class, so individual methods
* are added into each spec
*/
class SpecParser {
private final Iterable testSpecStrings;
private final LinkedHashMap, Spec> specs = new LinkedHashMap<>();
/**
* Parses the list of incoming test specs from the command line.
*
* Expects a list of string specs which can be represented as one of:
*
* - package.className
* - package.className#methodName
*
* Note that each class or method will only be executed once, no matter how many times it is
* present in the list.
*
*
* It is illegal to pass a spec with just the className if there are also individual methods
* present in the list within the same class.
*
*/
// TODO(zundel): This could easily be extended to allow a regular expression in the spec
SpecParser(Iterable testSpecStrings) {
Preconditions.checkArgument(!Iterables.isEmpty(testSpecStrings));
this.testSpecStrings = testSpecStrings;
}
/**
* Parse the specs passed in to the constructor.
*
* @return List of parsed specs
* @throws SpecException when there is a problem parsing specs
*/
Collection parse() throws SpecException {
for (String specString : testSpecStrings) {
if (specString.indexOf('#') >= 0) {
addMethod(specString);
} else {
Optional spec = getOrCreateSpec(specString, specString);
if (spec.isPresent()) {
Spec s = spec.get();
if (specs.containsKey(s.getSpecClass()) && !s.getMethods().isEmpty()) {
throw new SpecException(specString,
"Request for entire class already requesting individual methods");
}
}
}
}
return specs.values();
}
/**
* Creates or returns an existing Spec that corresponds to the className parameter.
*
* @param className The class name already parsed out of specString
* @param specString A spec string described in {@link SpecParser}
* @return a present Spec instance on success, absent if this spec string should be ignored
* @throws SpecException if the method passed in is not an executable test method
*/
private Optional getOrCreateSpec(String className, String specString) throws SpecException {
try {
Class> clazz = getClass().getClassLoader().loadClass(className);
if (Util.isTestClass(clazz)) {
if (!specs.containsKey(clazz)) {
Spec newSpec = new Spec(clazz);
specs.put(clazz, newSpec);
}
return Optional.of(specs.get(clazz));
}
return Optional.absent();
} catch (ClassNotFoundException | NoClassDefFoundError e) {
throw new SpecException(specString,
String.format("Class %s not found in classpath.", className), e);
} catch (LinkageError e) {
// Any of a number of runtime linking errors can occur when trying to load a class,
// fail with the test spec so the class failing to link is known.
throw new SpecException(specString,
String.format("Error linking %s.", className), e);
// See the comment below for justification.
} catch (RuntimeException e) {
// The class may fail with some variant of RTE in its static initializers, trap these
// and dump the bad spec in question to help narrow down issue.
throw new SpecException(specString,
String.format("Error initializing %s.",className), e);
}
}
/**
* Handle a spec that looks like package.className#methodName
*/
private void addMethod(String specString) throws SpecException {
String[] results = specString.split("#");
if (results.length != 2) {
throw new SpecException(specString, "Expected only one # in spec");
}
String className = results[0];
String methodName = results[1];
Optional spec = getOrCreateSpec(className, specString);
if (spec.isPresent()) {
Spec s = spec.get();
for (Method clazzMethod : s.getSpecClass().getMethods()) {
if (clazzMethod.getName().equals(methodName)) {
Spec specWithMethod = s.withMethod(methodName);
specs.put(s.getSpecClass(), specWithMethod);
return;
}
}
// TODO(John Sirois): Introduce an Either type to make this function total.
throw new SpecException(specString,
String.format("Method %s not found in class %s", methodName, className));
}
}
}