Using Robolectric offline?

1,015 views
Skip to first unread message

Andrew Richardson

unread,
Dec 12, 2013, 1:55:15 PM12/12/13
to robol...@googlegroups.com
We're exploring the possibility of using Robolectric for a project at work, and one obstacle we have is the fact that Robolectric wants to download Maven artifacts at runtime (such as "org.robolectric.android-all", "org.json.json", etc).

To me, this seems like a very non-standard use of Maven. Couldn't those be compile-time dependencies in Robolectric's pom, so that we have the ability to assemble a Robolectric jar that runs offline? Obviously we could try to set up a test server with public access to the Internet, but we try to keep them pretty sandboxed as a rule. Just trying to understand the reason that these files have to be downloaded at runtime.

Even barring a change to core Robolectric functionality, can anyone provide suggestions for how we could build a version that runs offline? ie maybe by overriding the SdkConfig or RobolectricTestRunner objects to only look in the local .m2 folder for these dependencies and not search remote Maven repos...I've been experimenting but haven't come up with anything that actually works. Appreciate any help.

Erich Douglass

unread,
Dec 13, 2013, 11:01:11 AM12/13/13
to robol...@googlegroups.com
Hi Andrew,

Robolectric downloads a prebuilt jar of the Android platform depending on what SDK your application targets.  Since Robolectric doesn't know what version of Android you are targeting until your tests actually run, it does this at run time.

The jars are also quite large - each one is around ~35Mb.  That would be a hefty download if we required you to download each prebuilt SDK level that Robolectric ran against.  Since it does use the standard Maven dependency resolution process, you can always download and mavenize the jars by hand.  This should let you run in "offline" mode.




--
You received this message because you are subscribed to the Google Groups "Robolectric" group.
To unsubscribe from this group and stop receiving emails from it, send an email to robolectric...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.

Jonathan Gerrish

unread,
Dec 13, 2013, 12:15:02 PM12/13/13
to robol...@googlegroups.com

Andrew,

We ran into the same issues, infact, out cloud based continuous build system doesn't allow us to make random http requests outside of the testing environment.

You can work around this by overriding the RobolectricTestRunner, some code snippets below.

Hope this helps.

Jonathan.

public class MyRobolectricTestRunner extends RobolectricTestRunner {

  /** The default file system path directory containing the "real" Android JAR files. */
  public static final File REAL_ANDROID_JAR_DIR =
      new File(.....);

  public MyRobolectricTestRunner(Class<?> testClass) throws InitializationError {
    super(testClass);
  }

  /**
   * Returns as a {@link File} the directory containing the "real" Android JARs:
   *
   * <ul>
   * <li>android-luni-4.1.2_r1_rc-real.jar</li>
   * <li>android-base-4.1.2_r1_rc-real.jar</li>
   * <li>android-res-4.1.2_r1_rc-real.jar</li>
   * </ul>
   *
   * <p>
   *
   * Subclasses can override this method to specify the path containing the
   * above three files.
   */
  protected File getRealAndroidJarDir() {
    return REAL_ANDROID_JAR_DIR;
  }

  // Robolectric uses Maven to fetch the below jars from the Maven Central repository at
  // runtime (ugh). We override that functionality here and supply those files from the
  // runfiles directory.
  @Override
  protected ClassLoader createRobolectricClassLoader(Setup setup, SdkConfig sdkConfig) {
    File dir = getRealAndroidJarDir();
    File luniFile = new File(dir, "android-luni-4.1.2_r1_rc-real.jar");
    File baseFile = new File(dir, "android-base-4.1.2_r1_rc-real.jar");
    File policyFile = new File(dir, "android-policy-4.1.2_r1_rc-real.jar");
    URL[] urls = new URL[] {
        fileToUrl(validateFile(luniFile)),
        fileToUrl(validateFile(baseFile)),
        fileToUrl(validateFile(policyFile)),
    };
    return new AsmInstrumentingClassLoader(setup, urls);
  }

  @Override
  public SdkEnvironment createSdkEnvironment(SdkConfig sdkConfig) {
    Setup setup = createSetup();
    ClassLoader robolectricClassLoader = createRobolectricClassLoader(setup, sdkConfig);
    File resFile = new File(getRealAndroidJarDir(), "android-res-4.1.2_r1_rc-real.jar");
    URL resUrl = fileToUrl(validateFile(resFile));
    return new LocalSdkEnvironment(resUrl, sdkConfig, robolectricClassLoader);
  }

  /**
   * Validates {@code file} as a File that exists and is a file, and is readable.
   *
   * @param file the File to test
   *
   * @return the provided file, if all validation passes
   *
   * @throws IllegalArgumentException if validation fails
   */
  private static File validateFile(File file) throws IllegalArgumentException {
    if (!file.exists()) {
      throw new IllegalArgumentException("File does not exist: " + file);
    }
    if (!file.isFile()) {
      throw new IllegalArgumentException("Path is not a file: " + file);
    }
    if (!file.canRead()) {
      throw new IllegalArgumentException("Unable to read file: " + file);
    }
    return file;
  }

  /** Returns the given file as a {@link URL}. */
  private static URL fileToUrl(File file) {
    try {
      return file.toURI().toURL();
    } catch (MalformedURLException e) {
      throw new IllegalArgumentException(
          String.format("File \"%s\" cannot be represented as a URL: %s", file, e));
    }
  }

  private static class LocalSdkEnvironment extends SdkEnvironment {

    private final URL resourceUrl;

    public LocalSdkEnvironment(
        URL resourceUrl, SdkConfig sdkConfig, ClassLoader robolectricClassLoader) {
      super(sdkConfig, robolectricClassLoader);
      this.resourceUrl = resourceUrl;
    }

    // Robolectric uses Maven to fetch a jar named android-res-4.1.2_r1_rc-real.jar from
    // the Maven Central repository at runtime (ugh). We override that functionality here
    // and supply that file from the runfiles directory.
    @Override
    public PackageResourceLoader createSystemResourceLoader(MavenCentral mavenCentral,
        RobolectricTestRunner robolectricTestRunner) {
      Fs systemResFs = Fs.fromJar(resourceUrl);
      ResourceExtractor resourceIndex = new ResourceExtractor(this.getRobolectricClassLoader());
      ResourcePath resourcePath = new ResourcePath(
          resourceIndex.getProcessedRFile(), resourceIndex.getPackageName(),
          systemResFs.join("res"), systemResFs.join("assets"));
      return new PackageResourceLoader(resourcePath, resourceIndex);
    }
  }
}

We're exploring the possibility of using Robolectric for a project at work, and one obstacle we have is the fact that Robolectric wants to download Maven artifacts at runtime (such as "org.robolectric.android-all", "org.json.json", etc).

To me, this seems like a very non-standard use of Maven. Couldn't those be compile-time dependencies in Robolectric's pom, so that we have the ability to assemble a Robolectric jar that runs offline? Obviously we could try to set up a test server with public access to the Internet, but we try to keep them pretty sandboxed as a rule. Just trying to understand the reason that these files have to be downloaded at runtime.

Even barring a change to core Robolectric functionality, can anyone provide suggestions for how we could build a version that runs offline? ie maybe by overriding the SdkConfig or RobolectricTestRunner objects to only look in the local .m2 folder for these dependencies and not search remote Maven repos...I've been experimenting but haven't come up with anything that actually works. Appreciate any help.

--

Andrew Richardson

unread,
Jul 8, 2014, 3:20:06 PM7/8/14
to robol...@googlegroups.com
Hey - I was off on another project for a few months, but I'm back on some automated testing now :) the code you gave provided a great starting point, and I was able to put together a solution that should work for us on our build machines.

I submitted a pull request to see if there's any interest in rolling this back to the main project:
https://github.com/robolectric/robolectric/pull/1189
It may be doomed to remain an unsupported use case, but I wanted to at least open the discussion.

Jonathan Gerrish

unread,
Jul 8, 2014, 3:38:20 PM7/8/14
to robol...@googlegroups.com
You should check out the latest commit at head:- https://github.com/robolectric/robolectric/commit/37ad5edfe1d5b3493337c61904dbce19e6aadc67

....the caching of these jars could support what you need? Of course, you need to be online for the first run.

I'd be in favor of further refactoring, renaming the MavenCentral interface to something like JarResolver, move RobolectricTestRunner.configureMaven() onto MavenCentralImpl (api breaking change) and move the static initializer of MavenCentral to an overrideable factory method JarResolver createJarResolver() which we would override to load the jars from the file system.

Thoughts?


--
You received this message because you are subscribed to the Google Groups "Robolectric" group.
To unsubscribe from this group and stop receiving emails from it, send an email to robolectric...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Andrew Richardson

unread,
Jul 8, 2014, 4:56:53 PM7/8/14
to robol...@googlegroups.com
I rebased my pull request onto master to pick up the change you mentioned. I split my work into two commits (one to refactor MavenCentral somewhat like you suggested, and one to add my OfflineResolver into the mix). Do you think this is a step in the right direction?

Andrew Richardson
Software Engineer, Cisco Systems


--
You received this message because you are subscribed to a topic in the Google Groups "Robolectric" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/robolectric/a8Pr_ChcWJs/unsubscribe.
To unsubscribe from this group and all its topics, send an email to robolectric...@googlegroups.com.

Andrew Richardson

unread,
Jul 15, 2014, 1:39:19 PM7/15/14
to robol...@googlegroups.com
Just to update this thread for future searches - my pull request was merged, so this functionality should be officially available in Robolectric 2.4. Thanks guys for helping to get this in!



On Tuesday, July 8, 2014 4:56:53 PM UTC-4, Andrew Richardson wrote:
I rebased my pull request onto master to pick up the change you mentioned. I split my work into two commits (one to refactor MavenCentral somewhat like you suggested, and one to add my OfflineResolver into the mix). Do you think this is a step in the right direction?

Andrew Richardson
Software Engineer, Cisco Systems


On Tue, Jul 8, 2014 at 3:38 PM, Jonathan Gerrish <jona...@indiekid.org> wrote:
You should check out the latest commit at head:- https://github.com/robolectric/robolectric/commit/37ad5edfe1d5b3493337c61904dbce19e6aadc67

....the caching of these jars could support what you need? Of course, you need to be online for the first run.

I'd be in favor of further refactoring, renaming the MavenCentral interface to something like JarResolver, move RobolectricTestRunner.configureMaven() onto MavenCentralImpl (api breaking change) and move the static initializer of MavenCentral to an overrideable factory method JarResolver createJarResolver() which we would override to load the jars from the file system.

Thoughts?
On 8 July 2014 12:20, Andrew Richardson <awrich...@gmail.com> wrote:
Hey - I was off on another project for a few months, but I'm back on some automated testing now :) the code you gave provided a great starting point, and I was able to put together a solution that should work for us on our build machines.

I submitted a pull request to see if there's any interest in rolling this back to the main project:
https://github.com/robolectric/robolectric/pull/1189
It may be doomed to remain an unsupported use case, but I wanted to at least open the discussion.



On Thursday, December 12, 2013 1:55:15 PM UTC-5, Andrew Richardson wrote:
We're exploring the possibility of using Robolectric for a project at work, and one obstacle we have is the fact that Robolectric wants to download Maven artifacts at runtime (such as "org.robolectric.android-all", "org.json.json", etc).

To me, this seems like a very non-standard use of Maven. Couldn't those be compile-time dependencies in Robolectric's pom, so that we have the ability to assemble a Robolectric jar that runs offline? Obviously we could try to set up a test server with public access to the Internet, but we try to keep them pretty sandboxed as a rule. Just trying to understand the reason that these files have to be downloaded at runtime.

Even barring a change to core Robolectric functionality, can anyone provide suggestions for how we could build a version that runs offline? ie maybe by overriding the SdkConfig or RobolectricTestRunner objects to only look in the local .m2 folder for these dependencies and not search remote Maven repos...I've been experimenting but haven't come up with anything that actually works. Appreciate any help.

--
You received this message because you are subscribed to the Google Groups "Robolectric" group.
To unsubscribe from this group and stop receiving emails from it, send an email to robolectric+unsubscribe@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

--
You received this message because you are subscribed to a topic in the Google Groups "Robolectric" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/robolectric/a8Pr_ChcWJs/unsubscribe.
To unsubscribe from this group and all its topics, send an email to robolectric+unsubscribe@googlegroups.com.
Reply all
Reply to author
Forward
0 new messages