package org.robolectric.annotation; import android.app.Application; import org.jetbrains.annotations.NotNull; import java.lang.annotation.Annotation; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.util.Arrays; import java.util.HashSet; import java.util.Properties; import java.util.Set; /** * Configuration settings that can be used on a per-class or per-test basis. */ @Documented @Inherited @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE, ElementType.METHOD}) public @interface Config { String NONE = "--none"; String DEFAULT = "--default"; String DEFAULT_RES_FOLDER = "res"; String DEFAULT_ASSET_FOLDER = "assets"; String DEFAULT_BUILD_FOLDER = "build"; /** * The Android SDK level to emulate. If not specified, Robolectric defaults to API 16. * This value will also be set as Build.VERSION.SDK_INT. * * @return The Android SDK level to emulate. */ int[] sdk() default {}; /** * The Android manifest file to load; Robolectric will look relative to the current directory. * Resources and assets will be loaded relative to the manifest. * * If not specified, Robolectric defaults to {@code AndroidManifest.xml}. * * If your project has no manifest or resources, use {@link Config#NONE}. * * @return The Android manifest file to load. */ String manifest() default DEFAULT; /** * Reference to the BuildConfig class created by the Gradle build system. * * @return Reference to BuildConfig class. */ Class> constants() default Void.class; /** * The {@link android.app.Application} class to use in the test, this takes precedence over any application * specified in the AndroidManifest.xml. * * @return The {@link android.app.Application} class to use in the test. */ Class extends Application> application() default Application.class; /** * Java package name where the "R.class" file is located. This only needs to be specified if you define * an {@code applicationId} associated with {@code productFlavors} or specify {@code applicationIdSuffix} * in your build.gradle. * *
If not specified, Robolectric defaults to the {@code applicationId}.
* * @return The java package name for R.class. */ String packageName() default ""; /** * The ABI split to use when locating resources and AndroidManifest.xml * *You do not typically have to set this, unless you are utilizing the ABI split feature
* * @return The ABI split to test with */ String abiSplit() default ""; /** * Qualifiers for the resource resolution, such as "fr-normal-port-hdpi". * * @return Qualifiers used for resource resolution. */ String qualifiers() default ""; /** * The directory from which to load resources. This should be relative to the directory containing AndroidManifest.xml. * *If not specified, Robolectric defaults to {@code res}.
* * @return Android resource directory. */ String resourceDir() default DEFAULT_RES_FOLDER; /** * The directory from which to load assets. This should be relative to the directory containing AndroidManifest.xml. * *If not specified, Robolectric defaults to {@code assets}.
* * @return Android asset directory. */ String assetDir() default DEFAULT_ASSET_FOLDER; /** * The directory where application files are created during the application build process. * *If not specified, Robolectric defaults to {@code build}.
* * @return Android build directory. */ String buildDir() default DEFAULT_BUILD_FOLDER; /** * A list of shadow classes to enable, in addition to those that are already present. * * @return A list of additional shadow classes to enable. */ Class>[] shadows() default {}; /** * A list of instrumented packages, in addition to those that are already instrumented. * * @return A list of additional instrumented packages. */ String[] instrumentedPackages() default {}; /** * A list of folders containing Android Libraries on which this project depends. * * @return A list of Android Libraries. */ String[] libraries() default {}; class Implementation implements Config { private final int[] sdk; private final String manifest; private final String qualifiers; private final String resourceDir; private final String assetDir; private final String buildDir; private final String packageName; private final String abiSplit; private final Class> constants; private final Class>[] shadows; private final String[] instrumentedPackages; private final Class extends Application> application; private final String[] libraries; public static Config fromProperties(Properties properties) { if (properties == null || properties.size() == 0) return null; return new Implementation( parseIntArrayProperty(properties.getProperty("sdk", "")), properties.getProperty("manifest", DEFAULT), properties.getProperty("qualifiers", ""), properties.getProperty("packageName", ""), properties.getProperty("abiSplit", ""), properties.getProperty("resourceDir", Config.DEFAULT_RES_FOLDER), properties.getProperty("assetDir", Config.DEFAULT_ASSET_FOLDER), properties.getProperty("buildDir", Config.DEFAULT_BUILD_FOLDER), parseClasses(properties.getProperty("shadows", "")), parseStringArrayProperty(properties.getProperty("instrumentedPackages", "")), parseApplication(properties.getProperty("application", "android.app.Application")), parseStringArrayProperty(properties.getProperty("libraries", "")), parseClass(properties.getProperty("constants", "")) ); } private static Class> parseClass(String className) { if (className.isEmpty()) return null; try { return Implementation.class.getClassLoader().loadClass(className); } catch (ClassNotFoundException e) { throw new RuntimeException("Could not load class: " + className); } } private static Class>[] parseClasses(String input) { if (input.isEmpty()) return new Class[0]; final String[] classNames = input.split("[, ]+"); final Class[] classes = new Class[classNames.length]; for (int i = 0; i < classNames.length; i++) { classes[i] = parseClass(classNames[i]); } return classes; } @SuppressWarnings("unchecked") private static