diff --git a/README.md b/README.md index 6faf121..de0e2f7 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,11 @@ You can also use command line to provide these options to cucumber-android. Here android.defaultConfig.testInstrumentationRunner "cucumber.cukeulator.test.CukeulatorAndroidJUnitRunner" ``` +### File-based tests + +If, for some reason, you need to load features from the filesystem, use an absolute path when specifying the features options to @CucumberOptions (e.g. `features = "/data/local/tmp"`). + +It is up to you to ensure your instrumentation has permission to read from the filesystem. You may need to add something like `` to your AndroidManifest.xml. ### Debugging Please read [the Android documentation on debugging](https://developer.android.com/tools/debugging/index.html). diff --git a/cucumber-android/src/main/java/cucumber/runtime/android/AndroidResourceLoader.java b/cucumber-android/src/main/java/cucumber/runtime/android/AndroidResourceLoader.java index fd1b590..8bcac3d 100644 --- a/cucumber-android/src/main/java/cucumber/runtime/android/AndroidResourceLoader.java +++ b/cucumber-android/src/main/java/cucumber/runtime/android/AndroidResourceLoader.java @@ -5,6 +5,7 @@ import cucumber.runtime.CucumberException; import cucumber.runtime.io.Resource; import cucumber.runtime.io.ResourceLoader; +import cucumber.runtime.io.MultiLoader; import java.io.IOException; import java.net.URI; @@ -26,17 +27,37 @@ final class AndroidResourceLoader implements ResourceLoader { */ private final Context context; + /** + * The {@link MultiLoader} to get the resources from. + */ + private final MultiLoader multiLoader; + /** * Creates a new instance for the given parameter. * * @param context the {@link Context} to get resources from */ AndroidResourceLoader(final Context context) { + this(context, new MultiLoader(context.getClassLoader())); + } + + /** + * Creates a new instance for the given parameter. + * + * @param context the {@link Context} to get resources from + * @param multiLoader the {@link Context} to get resources from + */ + AndroidResourceLoader(final Context context, final MultiLoader multiLoader) { this.context = context; + this.multiLoader = multiLoader; } @Override public Iterable resources(final URI path, final String suffix) { + if (path.getScheme().equals("file") && path.getRawSchemeSpecificPart().startsWith("/")) { + return multiLoader.resources(path, suffix); + } + try { final List resources = new ArrayList<>(); final AssetManager assetManager = context.getAssets(); diff --git a/cucumber-android/src/test/java/cucumber/runtime/android/AndroidResourceLoaderTest.java b/cucumber-android/src/test/java/cucumber/runtime/android/AndroidResourceLoaderTest.java index 1ca87bc..4f301ee 100644 --- a/cucumber-android/src/test/java/cucumber/runtime/android/AndroidResourceLoaderTest.java +++ b/cucumber-android/src/test/java/cucumber/runtime/android/AndroidResourceLoaderTest.java @@ -3,6 +3,7 @@ import android.content.Context; import android.content.res.AssetManager; import com.google.common.collect.Lists; +import cucumber.runtime.io.MultiLoader; import cucumber.runtime.io.Resource; import org.hamcrest.Description; import org.hamcrest.Matcher; @@ -15,6 +16,7 @@ import java.io.IOException; import java.net.URI; +import java.util.Iterator; import java.util.List; import static org.hamcrest.CoreMatchers.hasItem; @@ -30,7 +32,12 @@ public class AndroidResourceLoaderTest { private final Context context = mock(Context.class); private final AssetManager assetManager = mock(AssetManager.class, RETURNS_SMART_NULLS); - private final AndroidResourceLoader androidResourceLoader = new AndroidResourceLoader(context); + private final MultiLoader multiLoader = mock(MultiLoader.class, RETURNS_SMART_NULLS); + private final Resource fileResource1 = mock(Resource.class, RETURNS_SMART_NULLS); + private final Resource fileResource2 = mock(Resource.class, RETURNS_SMART_NULLS); + private Iterable fileResources = mock(Iterable.class, RETURNS_SMART_NULLS); + private Iterator fileResourcesIterator = mock(Iterator.class, RETURNS_SMART_NULLS); + private final AndroidResourceLoader androidResourceLoader = new AndroidResourceLoader(context, multiLoader); @Before public void beforeEachTest() { @@ -94,6 +101,81 @@ public void only_retrieves_those_resources_which_end_the_specified_suffix() thro assertThat(resources, hasItem(withPath(path + "/" + expected))); } + @Test + public void retrieves_file_resources_with_an_absolute_path() throws IOException { + // given + final String path = "file:/dir"; + final String dir = "dir"; + final String dirFile = "dir.feature"; + final String subDir = "subdir"; + final String subDirFile = "subdir.feature"; + final String suffix = "feature"; + + when(fileResource1.getPath()).thenReturn(URI.create(path + "/" + dirFile)); + when(fileResource2.getPath()).thenReturn(URI.create(path + "/" + subDir + "/" + subDirFile)); + when(fileResourcesIterator.hasNext()).thenReturn(Boolean.TRUE, Boolean.TRUE, Boolean.FALSE); + when(fileResourcesIterator.next()).thenReturn(fileResource1, fileResource2); + when(fileResources.iterator()).thenReturn(fileResourcesIterator); + when(multiLoader.resources(URI.create(path), suffix)).thenReturn(fileResources); + + // when + final List resources = Lists.newArrayList(androidResourceLoader.resources(URI.create(path), suffix)); + + // then + assertThat(resources.size(), is(2)); + assertThat(resources, hasItem(withPath(path + "/" + dirFile))); + assertThat(resources, hasItem(withPath(path + "/" + subDir + "/" + subDirFile))); + } + + @Test + public void only_retrieves_android_resources_with_a_relative_path() throws IOException { + // given + final String dir = "dir"; + String absPath = "file:/" + dir; + String relPath = "file:" + dir; + final String expected = "expected.feature"; + final String unexpected = "unexpected.feature"; + final String suffix = "feature"; + + when(assetManager.list(dir)).thenReturn(new String[]{expected}); + when(fileResource1.getPath()).thenReturn(URI.create(absPath + "/" + unexpected)); + when(fileResourcesIterator.hasNext()).thenReturn(Boolean.TRUE, Boolean.FALSE); + when(fileResourcesIterator.next()).thenReturn(fileResource1); + when(fileResources.iterator()).thenReturn(fileResourcesIterator); + when(multiLoader.resources(URI.create(absPath), suffix)).thenReturn(fileResources); + + // when + final List resources = Lists.newArrayList(androidResourceLoader.resources(URI.create(relPath), suffix)); + + // then + assertThat(resources.size(), is(1)); + assertThat(resources, hasItem(withPath(relPath + "/" + expected))); + } + + @Test + public void only_retrieves_file_resources_with_an_absolute_path() throws IOException { + // given + final String dir = "dir"; + String absPath = "file:/" + dir; + final String expected = "expected.feature"; + final String unexpected = "unexpected.feature"; + final String suffix = "feature"; + + when(fileResource1.getPath()).thenReturn(URI.create(absPath + "/" + expected)); + when(fileResourcesIterator.hasNext()).thenReturn(Boolean.TRUE, Boolean.FALSE); + when(fileResourcesIterator.next()).thenReturn(fileResource1); + when(fileResources.iterator()).thenReturn(fileResourcesIterator); + when(multiLoader.resources(URI.create(absPath), suffix)).thenReturn(fileResources); + when(assetManager.list(dir)).thenReturn(new String[]{unexpected}); + + // when + final List resources = Lists.newArrayList(androidResourceLoader.resources(URI.create(absPath), suffix)); + + // then + assertThat(resources.size(), is(1)); + assertThat(resources, hasItem(withPath(absPath + "/" + expected))); + } + private static Matcher withPath(final String path) { return new TypeSafeMatcher() { @Override