Skip to content

Commit

Permalink
All tests passing again. Ref #525.
Browse files Browse the repository at this point in the history
  • Loading branch information
aslakhellesoy committed Jun 25, 2013
1 parent 51da5a8 commit b3a800b
Show file tree
Hide file tree
Showing 14 changed files with 79 additions and 77 deletions.
2 changes: 1 addition & 1 deletion android-test/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
<plugin>
<groupId>com.jayway.maven.plugins.android.generation2</groupId>
<artifactId>android-maven-plugin</artifactId>
<version>3.5.3</version>
<version>3.6.0</version>
<extensions>true</extensions>
</plugin>
</plugins>
Expand Down
2 changes: 1 addition & 1 deletion android-test/project.properties
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
target=android-17
target=android-8
android.library=false
android.library.reference.1=../android
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import cucumber.api.java.en.Then;
import cucumber.api.java.en.When;

// I don't quite get why glue code has to be in a subclass....

This comment has been minimized.

Copy link
@mfellner

mfellner Jun 26, 2013

Contributor

The glue code should be inside an AndroidTestCase in order for the glue code to be able to access a Context or an Instrumentation (these are necessary when testing an Android app).

This comment has been minimized.

Copy link
@aslakhellesoy

aslakhellesoy Jun 26, 2013

Author Contributor

I see - is this some kind of dependency injection mechanism? I guess it will be more obvious to me when #547 is fixed.

public class CucumberActivitySteps extends ActivityInstrumentationTestCase2<CucumberActivity> {
private int steps;

Expand Down
8 changes: 8 additions & 0 deletions android/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,11 @@ On OS X: Note that for the path to work on the commandline and in IDE's started
The generated target artifacts are an [apklib](https://code.google.com/p/maven-android-plugin/wiki/ApkLib) file and a regular .jar.

Tests are in `../cucumber-test/`.

## Developers

When running the test app, logs can be read with:

```
adb logcat
```
2 changes: 1 addition & 1 deletion android/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
<plugin>
<groupId>com.jayway.maven.plugins.android.generation2</groupId>
<artifactId>android-maven-plugin</artifactId>
<version>3.5.3</version>
<version>3.6.0</version>
<extensions>true</extensions>
</plugin>
</plugins>
Expand Down
2 changes: 1 addition & 1 deletion android/project.properties
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
target=android-17
target=android-8
android.library=true
54 changes: 39 additions & 15 deletions android/src/cucumber/api/android/CucumberInstrumentation.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@
import cucumber.runtime.CucumberException;
import cucumber.runtime.Runtime;
import cucumber.runtime.RuntimeOptions;
import cucumber.runtime.android.*;
import cucumber.runtime.android.AndroidFormatter;
import cucumber.runtime.android.AndroidObjectFactory;
import cucumber.runtime.android.AndroidReflections;
import cucumber.runtime.android.AndroidResourceLoader;
import cucumber.runtime.io.Reflections;
import cucumber.runtime.io.ResourceLoader;
import cucumber.runtime.java.JavaBackend;
Expand Down Expand Up @@ -50,13 +53,40 @@ public class CucumberInstrumentation extends Instrumentation {
public void onCreate(Bundle arguments) {
super.onCreate(arguments);

if(arguments == null) {
throw new CucumberException("No arguments");
}
Log.d(TAG, "************** KEYS **************");
for (String key : arguments.keySet()) {
Log.d(TAG, key + " => " + arguments.get(key));
}
/*
MAVEN:
log => false
coverage => false
coverageFile => expression
debug => false
IDEA:
class => cucumber.android.test.CucumberActivitySteps
debug => false
*/



// Maven: [log, coverage, coverageFile, debug]
// IDEA: [log, coverage, coverageFile, debug]

Context context = getContext();
classLoader = context.getClassLoader();

String apkPath = context.getPackageCodePath();
ClassPathPackageInfoSource.setApkPaths(new String[]{apkPath});
ClassPathPackageInfoSource source = new ClassPathPackageInfoSource();

// For glue and features either use the provided arguments or try to find a RunWithCucumber annotated class.
// If nothing works, default values will be used instead.
if (arguments != null &&
(arguments.containsKey(ARGUMENT_TEST_CLASS) || arguments.containsKey(ARGUMENT_TEST_PACKAGE))) {
if (arguments.containsKey(ARGUMENT_TEST_CLASS) || arguments.containsKey(ARGUMENT_TEST_PACKAGE)) {

String testClass = arguments.getString(ARGUMENT_TEST_CLASS);
testClass = testClass != null ? testClass : "null";
Expand All @@ -82,11 +112,10 @@ public void onCreate(Bundle arguments) {
Log.w(TAG, e.toString());
}
} else {
throw new CucumberException("bad args");
// ClassPathPackageInfoSource source = AndroidClasspathMethodScanner.classPathPackageInfoSource(context);
// for (Class<?> clazz : source.getPackageInfo(context.getPackageName()).getTopLevelClassesRecursive()) {
// if (readRunWithCucumberAnnotation(clazz)) break;
// }
// throw new CucumberException("bad args");
for (Class<?> clazz : source.getPackageInfo(context.getPackageName()).getTopLevelClassesRecursive()) {
if (readRunWithCucumberAnnotation(clazz)) break;
}
}

Properties properties = new Properties();
Expand All @@ -97,15 +126,10 @@ public void onCreate(Bundle arguments) {
runtimeOptions = new RuntimeOptions(properties);

resourceLoader = new AndroidResourceLoader(context);
// resourceLoader = new ClasspathResourceLoader(classLoader);
List<Backend> backends = new ArrayList<Backend>();
// backends.add(new AndroidBackend(this));

String apkPath = context.getPackageCodePath();
ClassPathPackageInfoSource.setApkPaths(new String[]{apkPath});
ClassPathPackageInfoSource source = new ClassPathPackageInfoSource();

Reflections androidReflections = new AndroidReflections(source);

List<Backend> backends = new ArrayList<Backend>();
backends.add(new JavaBackend(new AndroidObjectFactory(this), androidReflections));
runtime = new Runtime(resourceLoader, classLoader, backends, runtimeOptions);

Expand Down
2 changes: 2 additions & 0 deletions android/src/cucumber/api/android/RunWithCucumber.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

// TODO (aslak): In stead of a stripped down copy of cucumber-junit's @Cucumber.Options annotation
// it would be better to pull that annotation up to core and deprecate the JUnit one.
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface RunWithCucumber {
Expand Down
24 changes: 12 additions & 12 deletions android/src/cucumber/runtime/android/AndroidObjectFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,29 +15,29 @@
import java.util.Set;

public class AndroidObjectFactory implements ObjectFactory {
private final Instrumentation mInstrumentation;
private final Set<Class<?>> mClasses = new HashSet<Class<?>>();
private final Map<Class<?>, Object> mInstances = new HashMap<Class<?>, Object>();
private final Instrumentation instrumentation;
private final Set<Class<?>> classes = new HashSet<Class<?>>();
private final Map<Class<?>, Object> instances = new HashMap<Class<?>, Object>();

public AndroidObjectFactory(Instrumentation instrumentation) {
mInstrumentation = instrumentation;
this.instrumentation = instrumentation;
}

public void start() {
// No-op
}

public void stop() {
mInstances.clear();
instances.clear();
}

public void addClass(Class<?> clazz) {
mClasses.add(clazz);
classes.add(clazz);
}

public <T> T getInstance(Class<T> type) {
if (mInstances.containsKey(type)) {
return type.cast(mInstances.get(type));
if (instances.containsKey(type)) {
return type.cast(instances.get(type));
} else {
return cacheNewInstance(type);
}
Expand All @@ -49,18 +49,18 @@ private <T> T cacheNewInstance(Class<T> type) {
T instance = constructor.newInstance();

if (instance instanceof ActivityInstrumentationTestCase2) {
((ActivityInstrumentationTestCase2) instance).injectInstrumentation(mInstrumentation);
((ActivityInstrumentationTestCase2) instance).injectInstrumentation(instrumentation);
// This Intent prevents the ActivityInstrumentationTestCase2 to stall on
// Intent.startActivitySync (when calling getActivity) if the activity is already running.
Intent intent = new Intent();
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
((ActivityInstrumentationTestCase2) instance).setActivityIntent(intent);
} else if (instance instanceof InstrumentationTestCase) {
((InstrumentationTestCase) instance).injectInstrumentation(mInstrumentation);
((InstrumentationTestCase) instance).injectInstrumentation(instrumentation);
} else if (instance instanceof AndroidTestCase) {
((AndroidTestCase) instance).setContext(mInstrumentation.getTargetContext());
((AndroidTestCase) instance).setContext(instrumentation.getTargetContext());
}
mInstances.put(type, instance);
instances.put(type, instance);
return instance;
} catch (NoSuchMethodException e) {
throw new CucumberException(String.format("%s doesn't have an empty constructor. If you need DI, put cucumber-picocontainer on the classpath", type), e);
Expand Down
34 changes: 0 additions & 34 deletions android/src/ext/android/test/ClassPathPackageInfoSource.java
Original file line number Diff line number Diff line change
Expand Up @@ -164,36 +164,6 @@ private void findClassesInDirectory(File classDir,
}
}

/**
* Finds all classes and sub packages that are below the packageName and
* add them to the respective sets. Searches the package in a single jar file.
*/
private void findClassesInJar(File jarFile, String pathPrefix,
Set<String> classNames, Set<String> subpackageNames)
throws IOException {
Set<String> entryNames = getJarEntries(jarFile);
// check if the Jar contains the package.
if (!entryNames.contains(pathPrefix)) {
return;
}
int prefixLength = pathPrefix.length();
for (String entryName : entryNames) {
if (entryName.startsWith(pathPrefix)) {
if (entryName.endsWith(CLASS_EXTENSION)) {
// check if the class is in the package itself or in one of its
// subpackages.
int index = entryName.indexOf('/', prefixLength);
if (index >= 0) {
String p = entryName.substring(0, index).replace('/', '.');
subpackageNames.add(p);
} else if (isToplevelClass(entryName)) {
classNames.add(getClassName(entryName).replace('/', '.'));
}
}
}
}
}

/**
* Finds all classes and sub packages that are below the packageName and
* add them to the respective sets. Searches the package in a single apk file.
Expand Down Expand Up @@ -300,8 +270,4 @@ private static String[] getClassPath() {
String separator = System.getProperty("path.separator", ":");
return classPath.split(Pattern.quote(separator));
}

public void setClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public Collection<Class<? extends Annotation>> getAnnotations(String packageName
@Override
public <T> Collection<Class<? extends T>> getDescendants(Class<T> parentType, String packageName) {
Collection<Class<? extends T>> result = new HashSet<Class<? extends T>>();
String packagePath = packageName.replace('.', '/').replace(File.separatorChar, '/');
String packagePath = "classpath:" + packageName.replace('.', '/').replace(File.separatorChar, '/');
for (Resource classResource : resourceLoader.resources(packagePath, ".class")) {
String className = classResource.getClassName();
Class<?> clazz = loadClass(className, classLoader);
Expand Down
7 changes: 2 additions & 5 deletions java/src/main/java/cucumber/runtime/java/JavaBackend.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,7 @@
import cucumber.api.java.After;
import cucumber.api.java.Before;
import cucumber.runtime.*;
import cucumber.runtime.io.ClasspathResourceLoader;
import cucumber.runtime.io.Reflections;
import cucumber.runtime.io.ResourceLoader;
import cucumber.runtime.io.ResourceLoaderReflections;
import cucumber.runtime.io.*;
import cucumber.runtime.snippets.SnippetGenerator;
import gherkin.formatter.model.Step;

Expand Down Expand Up @@ -36,7 +33,7 @@ public JavaBackend(ResourceLoader resourceLoader) {

public JavaBackend(ObjectFactory objectFactory) {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
ResourceLoader resourceLoader = new ClasspathResourceLoader(classLoader);
ResourceLoader resourceLoader = new MultiLoader(classLoader);
reflections = new ResourceLoaderReflections(resourceLoader, classLoader);
methodScanner = new MethodScanner(reflections);
this.objectFactory = objectFactory;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
import cucumber.api.java.Before;
import cucumber.runtime.CucumberException;
import cucumber.runtime.Glue;
import cucumber.runtime.io.ClasspathResourceLoader;
import cucumber.runtime.io.MultiLoader;
import cucumber.runtime.io.ResourceLoader;
import cucumber.runtime.io.ResourceLoaderReflections;
import org.junit.Test;
import org.mockito.Mockito;
Expand All @@ -18,7 +19,7 @@ public class MethodScannerTest {
@Test
public void loadGlue_registers_the_methods_declaring_class_in_the_object_factory() throws NoSuchMethodException {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
ClasspathResourceLoader resourceLoader = new ClasspathResourceLoader(classLoader);
ResourceLoader resourceLoader = new MultiLoader(classLoader);
MethodScanner methodScanner = new MethodScanner(new ResourceLoaderReflections(resourceLoader, classLoader));

ObjectFactory factory = Mockito.mock(ObjectFactory.class);
Expand Down
11 changes: 7 additions & 4 deletions scala/src/main/scala/cucumber/runtime/scala/ScalaBackend.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,16 @@ import _root_.gherkin.formatter.model.Step
import _root_.java.lang.reflect.Modifier
import _root_.cucumber.runtime.snippets.SnippetGenerator
import _root_.cucumber.api.scala.ScalaDsl
import _root_.cucumber.runtime.io.Reflections
import _root_.cucumber.runtime.io.ResourceLoaderReflections
import _root_.cucumber.runtime.io.ResourceLoader
import _root_.cucumber.runtime.io.ClasspathResourceLoader
import _root_.cucumber.runtime.io.MultiLoader
import _root_.cucumber.runtime.Backend
import _root_.cucumber.runtime.UnreportedStepExecutor
import _root_.cucumber.runtime.Glue
import collection.JavaConversions._

class ScalaBackend(ignore:ResourceLoader) extends Backend {
class ScalaBackend(resourceLoader:ResourceLoader) extends Backend {
private var snippetGenerator = new SnippetGenerator(new ScalaSnippetGenerator())
private var instances:Seq[ScalaDsl] = Nil

Expand All @@ -33,9 +35,10 @@ class ScalaBackend(ignore:ResourceLoader) extends Backend {
}

def loadGlue(glue: Glue, gluePaths: JList[String]) {
val cl = new ClasspathResourceLoader(Thread.currentThread().getContextClassLoader)
val cl = Thread.currentThread().getContextClassLoader
val reflections = new ResourceLoaderReflections(resourceLoader, cl)
val packages = gluePaths map { cucumber.runtime.io.MultiLoader.packageName(_) }
val dslClasses = packages flatMap { cl.getDescendants(classOf[ScalaDsl], _) } filter { cls =>
val dslClasses = packages flatMap { reflections.getDescendants(classOf[ScalaDsl], _) } filter { cls =>
try {
cls.getDeclaredConstructor()
true
Expand Down

0 comments on commit b3a800b

Please sign in to comment.