From f9ee1dae4bbd13f970d7f21a478dcf73d67c7500 Mon Sep 17 00:00:00 2001 From: Darren Warner Date: Tue, 12 Nov 2019 16:51:25 -0800 Subject: [PATCH 1/4] Add support for loading features from the filesystem using absolute (file:///) URIs --- .../cucumber/runtime/android/AndroidResourceLoader.java | 8 ++++++++ 1 file changed, 8 insertions(+) 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..90f2ac9 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; @@ -37,6 +38,13 @@ final class AndroidResourceLoader implements ResourceLoader { @Override public Iterable resources(final URI path, final String suffix) { + if (path.getScheme().equals("file") && path.getRawSchemeSpecificPart().startsWith("//")) { + MultiLoader multiLoader; + multiLoader = new MultiLoader(Thread.currentThread().getContextClassLoader()); + + return multiLoader.resources(path, suffix); + } + try { final List resources = new ArrayList<>(); final AssetManager assetManager = context.getAssets(); From e31f73411bf42354c23e34a55bea0e99b462d3c3 Mon Sep 17 00:00:00 2001 From: Darren Warner Date: Wed, 13 Nov 2019 14:22:14 -0800 Subject: [PATCH 2/4] File URIs do not require a hostname --- .../java/cucumber/runtime/android/AndroidResourceLoader.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 90f2ac9..4952c74 100644 --- a/cucumber-android/src/main/java/cucumber/runtime/android/AndroidResourceLoader.java +++ b/cucumber-android/src/main/java/cucumber/runtime/android/AndroidResourceLoader.java @@ -38,7 +38,7 @@ final class AndroidResourceLoader implements ResourceLoader { @Override public Iterable resources(final URI path, final String suffix) { - if (path.getScheme().equals("file") && path.getRawSchemeSpecificPart().startsWith("//")) { + if (path.getScheme().equals("file") && path.getRawSchemeSpecificPart().startsWith("/")) { MultiLoader multiLoader; multiLoader = new MultiLoader(Thread.currentThread().getContextClassLoader()); From fd0a5066576dd4ad62ae5716cbe3ee8ae51b8f1e Mon Sep 17 00:00:00 2001 From: Darren Warner Date: Wed, 13 Nov 2019 14:54:50 -0800 Subject: [PATCH 3/4] Add unit tests to provide coverage for MultiLoader vs. AndroidResourceLoader execution paths --- .../android/AndroidResourceLoader.java | 19 ++++- .../android/AndroidResourceLoaderTest.java | 84 ++++++++++++++++++- 2 files changed, 99 insertions(+), 4 deletions(-) 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 4952c74..8bcac3d 100644 --- a/cucumber-android/src/main/java/cucumber/runtime/android/AndroidResourceLoader.java +++ b/cucumber-android/src/main/java/cucumber/runtime/android/AndroidResourceLoader.java @@ -27,21 +27,34 @@ 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("/")) { - MultiLoader multiLoader; - multiLoader = new MultiLoader(Thread.currentThread().getContextClassLoader()); - return multiLoader.resources(path, suffix); } 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 From a4878799dab8e379e4c9f19effdb64ff13961c80 Mon Sep 17 00:00:00 2001 From: Darren Warner Date: Wed, 13 Nov 2019 14:55:04 -0800 Subject: [PATCH 4/4] Update the README to include a note about file-based tests --- README.md | 5 +++++ 1 file changed, 5 insertions(+) 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).