aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRex Hoffman <rexhoffman@google.com>2024-04-03 13:47:24 +0000
committerGerrit Code Review <noreply-gerritcodereview@google.com>2024-04-03 13:47:24 +0000
commitc1e9dcc09586aac4d2adb38e90d396a7387092fd (patch)
treed5db24dce82a9a30cb3f57e1407af3a4ded60a89
parent8a88d54f8fc094d3d6b6758193712e0673291b9b (diff)
parente1d1549a07a4521c8cb1ba48b15fe28948f4c07d (diff)
downloadrobolectric-c1e9dcc09586aac4d2adb38e90d396a7387092fd.tar.gz
Merge changes I6ae79b35,If1ded0da into main
* changes: Support for Robolectric's validator Merge remote-tracking branch 'aosp/upstream-google' into merge
-rw-r--r--.github/workflows/copybara_build_and_test.yml1
-rw-r--r--.github/workflows/gradle_tasks_validation.yml20
-rw-r--r--.github/workflows/tests.yml1
-rw-r--r--.github/workflows/validate_commit_message.yml12
-rw-r--r--ARCHITECTURE.md213
-rw-r--r--Android.bp21
-rw-r--r--README.md3
-rw-r--r--annotations/build.gradle6
-rw-r--r--annotations/src/main/java/org/robolectric/annotation/InDevelopment.java18
-rw-r--r--annotations/src/main/java/org/robolectric/versioning/AndroidVersionInitTools.java (renamed from shadows/versioning/src/main/java/org/robolectric/versioning/AndroidVersionInitTools.java)0
-rw-r--r--annotations/src/main/java/org/robolectric/versioning/AndroidVersions.java (renamed from shadows/versioning/src/main/java/org/robolectric/versioning/AndroidVersions.java)497
-rw-r--r--annotations/src/test/java/org/robolectric/versioning/AndroidVersionsEdgeCaseTest.java (renamed from shadows/versioning/src/test/java/org/robolectric/versioning/AndroidVersionsEdgeCaseTest.java)0
-rw-r--r--annotations/src/test/java/org/robolectric/versioning/AndroidVersionsTest.java169
-rw-r--r--build.gradle49
-rw-r--r--buildSrc/src/main/groovy/AndroidSdk.groovy18
-rw-r--r--buildSrc/src/main/groovy/ProvideBuildClasspathTask.groovy8
-rw-r--r--buildSrc/src/main/groovy/ShadowsPlugin.groovy51
-rw-r--r--buildSrc/src/main/groovy/org/robolectric/gradle/AarDepsPlugin.java10
-rw-r--r--buildSrc/src/main/groovy/org/robolectric/gradle/AndroidProjectConfigPlugin.groovy14
-rw-r--r--buildSrc/src/main/groovy/org/robolectric/gradle/RoboJavaModulePlugin.groovy16
-rw-r--r--buildSrc/src/main/groovy/org/robolectric/gradle/SpotlessPlugin.groovy27
-rw-r--r--gradle/libs.versions.toml27
-rw-r--r--gradle/wrapper/gradle-wrapper.jarbin43462 -> 43453 bytes
-rw-r--r--gradle/wrapper/gradle-wrapper.properties2
-rw-r--r--integration_tests/agp/src/main/AndroidManifest.xml3
-rw-r--r--integration_tests/agp/testsupport/src/main/AndroidManifest.xml3
-rw-r--r--integration_tests/compat-target28/src/main/AndroidManifest.xml1
-rw-r--r--integration_tests/ctesque/src/sharedTest/java/android/view/KeyCharacterMapTest.java3
-rw-r--r--integration_tests/ctesque/src/sharedTest/java/android/view/KeyEventTest.java61
-rw-r--r--integration_tests/dependency-on-stubs/src/test/java/org/robolectric/ShadowLogResolutionTest.java22
-rw-r--r--integration_tests/jacoco-offline/build.gradle87
-rw-r--r--integration_tests/kotlin/build.gradle10
-rw-r--r--integration_tests/memoryleaks/src/main/AndroidManifest.xml7
-rw-r--r--integration_tests/nativegraphics/src/main/AndroidManifest.xml6
-rw-r--r--integration_tests/roborazzi/build.gradle11
-rw-r--r--integration_tests/room/build.gradle1
-rw-r--r--integration_tests/room/src/main/AndroidManifest.xml9
-rw-r--r--integration_tests/sparsearray/build.gradle11
-rw-r--r--integration_tests/sparsearray/src/main/AndroidManifest.xml2
-rw-r--r--integration_tests/versioning/build.gradle (renamed from shadows/versioning/build.gradle)1
-rw-r--r--integration_tests/versioning/src/test/java/org/robolectric/versioning/AndroidVersionsTest.java (renamed from shadows/versioning/src/test/java/org/robolectric/versioning/AndroidVersionsTest.java)69
-rw-r--r--integration_tests/versioning/src/test/resources/AndroidManifest.xml (renamed from shadows/versioning/src/test/resources/AndroidManifest.xml)0
-rw-r--r--preinstrumented/build.gradle43
-rw-r--r--processor/Android.bp6
-rw-r--r--processor/build.gradle12
-rw-r--r--processor/src/main/java/org/robolectric/annotation/processing/RobolectricProcessor.java54
-rw-r--r--processor/src/main/java/org/robolectric/annotation/processing/validator/SdkStore.java269
-rw-r--r--processor/src/test/java/org/robolectric/annotation/processing/validator/InDevelopmentValidatorTest.java71
-rw-r--r--processor/src/test/java/org/robolectric/annotation/processing/validator/SingleClassSubject.java51
-rw-r--r--processor/src/test/resources/org/robolectric/annotation/processing/shadows/ShadowImplementsInDevelopment.java16
-rw-r--r--processor/src/test/resources/org/robolectric/annotation/processing/shadows/ShadowImplementsInDevelopmentMissing.java14
-rw-r--r--resources/src/main/java/org/robolectric/manifest/AndroidManifest.java4
-rw-r--r--robolectric/src/main/java/org/robolectric/RobolectricTestRunner.java36
-rw-r--r--robolectric/src/main/java/org/robolectric/plugins/DefaultSdkProvider.java2
-rw-r--r--robolectric/src/test/java/org/robolectric/RobolectricTestRunnerTest.java16
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowBatteryManagerTest.java24
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowConnectivityManagerTest.java11
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowContextHubClientTest.java13
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowSurfaceViewTest.java44
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowTelephonyManagerTest.java9
-rw-r--r--sandbox/src/main/java/org/robolectric/internal/bytecode/ShadowWrangler.java74
-rw-r--r--shadows/framework/Android.bp8
-rw-r--r--shadows/framework/build.gradle3
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowArscAssetManager14.java3
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowBatteryManager.java16
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowConnectivityManager.java9
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowContextHubClient.java9
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowInstrumentation.java7
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowKeyCharacterMap.java58
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowKeyEvent.java53
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowLog.java9
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowMotionEvent.java40
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowPaint.java2
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowSpeechRecognizer.java2
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowSurfaceView.java20
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowTelephonyManager.java11
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowTrace.java2
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowViewConfiguration.java62
-rw-r--r--shadows/playservices/build.gradle6
-rw-r--r--shadows/playservices/src/main/java/org/robolectric/shadows/gms/ShadowGooglePlayServicesUtil.java9
-rw-r--r--shadows/playservices/src/main/java/org/robolectric/shadows/gms/common/ShadowGoogleApiAvailability.java4
-rw-r--r--shadows/playservices/src/test/java/org/robolectric/shadows/gms/ShadowGooglePlayServicesUtilTest.java7
-rw-r--r--shadows/playservices/src/test/java/org/robolectric/shadows/gms/common/ShadowGoogleApiAvailabilityTest.java17
-rw-r--r--testapp/src/main/AndroidManifest.xml4
-rw-r--r--testapp/src/main/res/font/downloadable.xml2
-rw-r--r--utils/src/main/java/org/robolectric/util/TempDirectory.java25
-rw-r--r--utils/src/test/java/org/robolectric/util/TempDirectoryTest.kt1
87 files changed, 1860 insertions, 788 deletions
diff --git a/.github/workflows/copybara_build_and_test.yml b/.github/workflows/copybara_build_and_test.yml
index 55a995360..f780ecf94 100644
--- a/.github/workflows/copybara_build_and_test.yml
+++ b/.github/workflows/copybara_build_and_test.yml
@@ -40,6 +40,5 @@ jobs:
-Drobolectric.alwaysIncludeVariantMarkersInTestName=true \
-Drobolectric.enabledSdks=34 \
-Dorg.gradle.workers.max=2 \
- -Drobolectric.usePreinstrumentedJars=false \
-x :integration_tests:nativegraphics:test \
)
diff --git a/.github/workflows/gradle_tasks_validation.yml b/.github/workflows/gradle_tasks_validation.yml
index f8d065d2b..a3661d768 100644
--- a/.github/workflows/gradle_tasks_validation.yml
+++ b/.github/workflows/gradle_tasks_validation.yml
@@ -58,9 +58,6 @@ jobs:
steps:
- uses: actions/checkout@v4
- with:
- fetch-depth: 0
- submodules: recursive
- name: Set up JDK
uses: actions/setup-java@v4
@@ -75,3 +72,20 @@ jobs:
- name: Run :preinstrumented:instrumentAll with SDK 33
run: PREINSTRUMENTED_SDK_VERSIONS=33 ./gradlew :preinstrumented:instrumentAll
+
+ run_publishToMavenLocal:
+ runs-on: ubuntu-22.04
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Set up JDK 17
+ uses: actions/setup-java@v4
+ with:
+ distribution: 'adopt'
+ java-version: 17
+
+ - uses: gradle/actions/setup-gradle@v3
+
+ - name: Publish to Maven local
+ run: ./gradlew publishToMavenLocal --no-watch-fs
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 66ca84bb9..00e9e46b2 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -68,7 +68,6 @@ jobs:
--no-watch-fs \
-Drobolectric.enabledSdks=${{ matrix.api-versions }} \
-Drobolectric.alwaysIncludeVariantMarkersInTestName=true \
- -Drobolectric.usePreinstrumentedJars=false \
-Dorg.gradle.workers.max=2 \
-x :integration_tests:nativegraphics:test \
-x :integration_tests:roborazzi:test
diff --git a/.github/workflows/validate_commit_message.yml b/.github/workflows/validate_commit_message.yml
index 57124e8b2..f90be1f69 100644
--- a/.github/workflows/validate_commit_message.yml
+++ b/.github/workflows/validate_commit_message.yml
@@ -25,7 +25,7 @@ jobs:
# Check that the commit title isn't excessively long.
commit_title="$(git log -1 --pretty=format:'%s')"
if [ "${#commit_title}" -gt 120 ]; then
- echo "Error: The title of commit is too long"
+ echo "Error: the title of the commit is too long (max: 120 characters)"
exit 1
fi
@@ -41,6 +41,14 @@ jobs:
# Check that the commit has a body
commit_body="$(git log -1 --pretty=format:'%b' | grep -v 'PiperOrigin-RevId')"
if [ -z "$commit_body" ]; then
- echo "Error: The commit message should have a descriptive body"
+ echo "Error: the commit should have a descriptive body"
exit 1
fi
+
+ while read -r line; do
+ if [ "${#line}" -gt 120 ] && [[ ! "$line" =~ (https?://|www\.) ]]; then
+ echo "Error: the following line of the commit body is too long (max: 120 characters):"
+ echo "> $line"
+ exit 1
+ fi
+ done <<< $commit_body
diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md
new file mode 100644
index 000000000..1654ebbeb
--- /dev/null
+++ b/ARCHITECTURE.md
@@ -0,0 +1,213 @@
+# Robolectric architecture
+
+Robolectric is a unit testing framework that allows Android code to be tested on
+the JVM without the need for an emulator or device. This allows tests to run
+very quickly in a more hermetic environment. Robolectric has a complex
+architecture and makes use of many advanced features of the JVM such as bytecode
+instrumentation and custom ClassLoaders. This document provides a high level
+overview of the architecture of Robolectric.
+
+# Android framework Jars and instrumentation
+
+At the heart of Robolectric are the Android framework Jars and the bytecode
+instrumentation. The Android framework Jars are a collection of Jar files that
+are built directly from Android platform sources. There is a single Jar file for
+each version of Android. These Jar files can be built by checking out an AOSP
+repo and building the
+[robolectric-host-android\_all](https://cs.android.com/android/platform/superproject/main/+/main:external/robolectric/Android.bp;l=99)
+target. Unlike the android.jar (stubs jar) files managed by Android Studio,
+which only contain public method signatures, the Robolectric android-all Jars
+contain the implementation of the Android Java framework. This gives Robolectric
+the ability to use as much real Android code as possible. A new android-all jar
+is uploaded to MavenCentral for each Android release. You can see the current
+android-all jars
+[here](https://repo1.maven.org/maven2/org/robolectric/android-all/).
+
+However, the pristine android-all jars are not the ones used during tests.
+Instead, Robolectric modifies the pristine android-all jars using bytecode
+instrumentation (see
+[ClassInstrumentor](https://github.com/robolectric/robolectric/blob/master/sandbox/src/main/java/org/robolectric/internal/bytecode/ClassInstrumentor.java)).
+It performs several modifications:
+
+1. All Android methods, including constructors and static initializers, are
+ modified to support `shadowing`. This allows any method call to the Android
+ framework to be intercepted by Robolectric and delegated to a shadow method.
+ At a high level, this is done by iterating over each Android method and
+ converting it into two methods: the original method (but renamed), and the
+ `invokedynamic delegator` which can optionally invoke shadow methods if they
+ are available.
+
+1. Android constructors are specially modified to create shadow objects, if a
+ shadow class is bound to the Android class being instantiated.
+
+1. Because the Android version of Java core classes (libcore) contain subtle
+ differences to the JDKs, certain problematic method calls have to be
+ intercepted and rewritten. See
+ [AndroidInterceptors](https://github.com/robolectric/robolectric/blob/master/sandbox/src/main/java/org/robolectric/interceptors/AndroidInterceptors.java).
+
+1. Native methods undergo special instrumentation. Currently native methods are
+ converted to no-op non-native methods that are shadowable by default.
+ However, there is now a native variant of each method also created. There is
+ more details about native code in a section below.
+
+1. The `final` keyword is stripped from classes and methods.
+
+1. Some bespoke pieces of instrumentation, such as supporting
+ [SparseArray.set](https://github.com/robolectric/robolectric/blob/master/sandbox/src/main/java/org/robolectric/internal/bytecode/ClassInstrumentor.java#L201).
+
+This instrumentation is typically performed when a new release of Robolectric is
+made. These pre-instrumented Android-all jars are published on MavenCentral. See
+the
+[android-all-instrumented](https://repo1.maven.org/maven2/org/robolectric/android-all-instrumented/)
+path. They are lazily downloaded and during tests runtime using
+[MavenArtifactFetcher](https://github.com/robolectric/robolectric/blob/master/plugins/maven-dependency-resolver/src/main/java/org/robolectric/internal/dependency/MavenArtifactFetcher.java).
+
+Although Robolectric supports shadowing for Android framework classes, it is
+also possible for users to perform Robolectric instrumentation for any package
+(with the exception of built in Java packages). This enables shadowing of
+arbitrary third-party code.
+
+# Shadows
+
+By default when an Android method is invoked during a Robolectric test, the real
+Android framework code is invoked. This is because a lot of Android framework
+classes are pure Java code (e.g the
+[Intent](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/content/Intent.java)
+class or the
+[org.json](https://cs.android.com/android/platform/superproject/main/+/main:libcore/json/src/main/java/org/json/)
+package) and that code can run on the JVM without any modifications needed.
+
+However, there are cases where Robolectric needs to intercept and replace
+Android method calls. This most commonly occurs when Android system service or
+native methods are invoked. To do this, Robolectric uses a system called Shadow
+classes.
+
+Shadow classes are Java classes that contain the replacement code of Android
+methods when they are invoked. Each shadow class is bound to specific Android
+classes and methods through annotations. There are currently hundreds of shadow
+classes that can be found
+[here](https://github.com/robolectric/robolectric/tree/master/shadows/framework/src/main/java/org/robolectric/shadows).
+
+Shadow classes may optionally contain public apis APIs that can customize the
+behavior of the methods they are shadowing.
+
+Robolectric allows tests to specify custom shadows as well to provide user
+defined implementation for Android classes.
+
+## Shadow Packages and the Robolectric Annotation Processor
+
+There are two categories of shadows: Robolectric’s built-in shadows that are
+aggregated using the [Robolectric Annotation Processor
+(RAP)](https://github.com/robolectric/robolectric/blob/master/processor/src/main/java/org/robolectric/annotation/processing/RobolectricProcessor.java),
+and custom shadows that are commonly specified using `@Config(shadows = …)`. RAP
+is configured to process all of the shadow files that exist in Robolectric’s
+code. The main shadow package is [framework
+shadows](https://github.com/robolectric/robolectric/tree/master/shadows/framework),
+which contain shadows for the Android framework. There are other shadow packages
+in Robolectric's code, such as [httpclient
+shadows](https://github.com/robolectric/robolectric/tree/master/shadows/httpclient),
+but all of them outside of framework shadows are deprecated. When Robolectric is
+built, each shadow package is processed by RAP and a
+[ShadowProvider](https://github.com/robolectric/robolectric/blob/master/shadowapi/src/main/java/org/robolectric/internal/ShadowProvider.java)
+file is generated. For example, to see the ShadowProvider for the framework
+shadows, you can run:
+
+```sh
+./gradlew :shadows:framework:assemble
+cat ./shadows/framework/build/generated/src/apt/main/org/robolectric/Shadows.java
+```
+
+In this file you will see the class `public class Shadows implements
+ShadowProvider`.
+
+During runtime, Robolectric will use ServiceLoader to detect all shadow packages
+that implement ShadowProvider and the shadow classes contained in them.
+
+# Sandbox and ClassLoader
+
+Before a Robolectric test is executed, a
+[Sandbox](https://github.com/robolectric/robolectric/blob/master/sandbox/src/main/java/org/robolectric/internal/bytecode/Sandbox.java)
+must be initialized. A Sandbox consists of some high-level structures that are
+necessary to run a Robolectric test. It primarily contains a
+[SandboxClassLoader](https://github.com/robolectric/robolectric/blob/master/sandbox/src/main/java/org/robolectric/internal/bytecode/SandboxClassLoader.java),
+which is a custom ClassLoader that is bound to a specific instrumented
+Android-all jar. Sandboxes also contain the ExecutorService that serves as the
+main thread (UI thread) as well as high-level instrumentation configuration. The
+SandboxClassLoader is installed as the default ClassLoader for the test method.
+When any Android class is requested, SandboxClassLoader will attempt to load the
+Android class from the instrumented Android-all Jar first. The primary goal of
+SandboxClassLoader is to ensure that classes from the android.jar stubs jar are
+not inadvertently loaded. When classes from the android.jar stubs jar are
+loaded, attempting to invoke any method on them will result in a
+`RuntimeException(“Stub!”)` error. Typically the Android stubs jar is on the
+class path during a Robolectric test, but it is important not to load classes
+from the stubs jar.
+
+# Invokedynamic Delegators and ShadowWrangler
+
+This section provides more detail for `invokedynamic delegators` that were
+referenced in the instrumentation section. For an overview of the
+`invokedynamic` JVM instructions, you can search for articles or watch [YouTube
+videos such as this](https://www.youtube.com/watch?v=KhiECfzyVt0).
+
+To reiterate, for any Android method, Robolectric’s instrumentation adds an
+`invokedynamic delegator` that is responsible for determining at runtime to
+either invoke the real Android framework code or a shadow method. The first time
+an Android method is invoked in a Sandbox, it will result in a call to one of
+the bootstrap methods in
+[InvokeDynamicSupport](https://github.com/robolectric/robolectric/blob/master/sandbox/src/main/java/org/robolectric/internal/bytecode/InvokeDynamicSupport.java).
+This will subsequently invoke the
+[ShadowWrangler.findShadowMethodHandle](https://github.com/robolectric/robolectric/blob/master/sandbox/src/main/java/org/robolectric/internal/bytecode/ShadowWrangler.java#L197)
+to determine if a shadow method exists for the method that is being invoked. If
+a shadow method is available a MethodHandle to it will be returned. Otherwise a
+MethodHandle for the original framework code will be returned.
+
+# Test lifecycle
+
+There is a lot of work done by Robolectric before and after a test is run.
+Besides the Sandbox and ClassLoader initialization mentioned above, there is
+also extensive Android environment initialization that occurs before each test.
+The high-level class for this is
+[AndroidTestEnvironment](https://github.com/robolectric/robolectric/blob/master/robolectric/src/main/java/org/robolectric/android/internal/AndroidTestEnvironment.java).
+This involves:
+
+* Initializing up the Looper mode (i.e. the scheduler)
+* Initializing system and app resources
+* Initializing the application context and system context
+* Loading the Android manifest for the test
+* Creating the Application object used for the test
+* Initializing the [display configuration](https://robolectric.org/device-configuration/)
+* Setting up the ActivityThread
+* Creating app directories
+
+It is possible for users to extend the test environment setup using
+[TestEnvironmentLifecyclePlugin](https://github.com/robolectric/robolectric/blob/master/pluginapi/src/main/java/org/robolectric/pluginapi/TestEnvironmentLifecyclePlugin.java).
+
+Similarly, after each test, many Android classes are reset during
+[RobolectricTestRunner.finallyAfterTest](https://github.com/robolectric/robolectric/blob/master/robolectric/src/main/java/org/robolectric/RobolectricTestRunner.java#L301).
+This will iterate over all shadows and invoke their static `@Resetter` methods.
+
+# Plugin System
+
+Many parts of Robolectric can be customized using a plugin system based on
+Java’s
+[ServiceLoader](https://docs.oracle.com/javase/8/docs/api/java/util/ServiceLoader.html).
+This extensibility is useful when running Robolectric in more constrained
+environments. For example, by default, most of the Robolectric classes are
+designed to work in a Gradle/Android Studio environment. However, there are
+companies (such as Google) that use alternate build systems (such as Bazel), and
+it can be helpful to have the ability to customize the behavior of some core
+modules.
+
+The
+[pluginapi](https://github.com/robolectric/robolectric/tree/master/pluginapi/src)
+subproject contains many extension points of Robolectric. However, virtually any
+class that is loaded by Robolectric’s
+[Injector](https://github.com/robolectric/robolectric/blob/master/utils/src/main/java/org/robolectric/util/inject/Injector.java)
+has the ability to use
+[PluginFinder](https://github.com/robolectric/robolectric/blob/master/utils/src/main/java/org/robolectric/util/inject/PluginFinder.java),
+which means it can be extended at runtime.
+
+Typically ServiceLoaders plugins can be easily written using the
+[AutoService](https://github.com/google/auto/tree/main/service) project.
+
diff --git a/Android.bp b/Android.bp
index 0f1cfd421..4e5cb23b2 100644
--- a/Android.bp
+++ b/Android.bp
@@ -61,6 +61,17 @@ robolectric_build_props {
}
java_genrule {
+ name: "robolectric_props_jar",
+ host_supported: true,
+ tools: ["soong_zip"],
+ srcs: [":robolectric_build_props"],
+ out: ["robolectric_props.jar"],
+ cmd: "cp $(location :robolectric_build_props) . && $(location soong_zip) " +
+ "-o $(location robolectric_props.jar) " +
+ "-f ./build.prop ",
+}
+
+java_genrule {
name: "robolectric_framework_res",
host_supported: true,
tools: ["zip2zip"],
@@ -95,12 +106,15 @@ java_device_for_host {
],
}
-java_library_host {
+java_library {
name: "robolectric-host-android_all",
+ host_supported: true,
+ device_supported: false,
static_libs: [
"robolectric_android-all-device-deps",
"robolectric_tzdata",
"robolectric_framework_res",
+ "robolectric_props_jar",
],
// WARNING: DO NOT ADD NEW DEPENDENCIES ON THIS MODULE OR ITS DIST JAR
// This dist jar is an internal implementation detail. For external Gradle builds (outside
@@ -118,11 +132,6 @@ java_library_host {
],
dest: "android-all-robolectric.jar",
},
-
- java_resources: [
- // Copy the build.prop
- ":robolectric_build_props",
- ],
visibility: [
":__subpackages__",
"//external/robolectric-shadows:__subpackages__",
diff --git a/README.md b/README.md
index 0fe4d4471..faa46f7a5 100644
--- a/README.md
+++ b/README.md
@@ -47,6 +47,9 @@ testImplementation "org.robolectric:robolectric:4.11.1"
Robolectric is built using Gradle. Both IntelliJ and Android Studio can import the top-level `build.gradle` file and will automatically generate their project files from it.
+To get a high-level overview of Robolectric's architecture, check out
+[ARCHITECTURE.md](ARCHITECTURE.md).
+
### Prerequisites
See [Building Robolectric](http://robolectric.org/building-robolectric/) for more details about setting up a build environment for Robolectric.
diff --git a/annotations/build.gradle b/annotations/build.gradle
index d8bd113c5..c26efdb25 100644
--- a/annotations/build.gradle
+++ b/annotations/build.gradle
@@ -6,5 +6,11 @@ apply plugin: DeployedRoboJavaModulePlugin
dependencies {
compileOnly libs.findbugs.jsr305
+ compileOnly libs.javax.annotation.api
compileOnly AndroidSdk.MAX_SDK.coordinates
+ testImplementation libs.truth
+ testImplementation libs.junit4
+ testImplementation libs.javax.annotation.api
+ testCompileOnly AndroidSdk.MAX_SDK.coordinates // compile against latest Android SDK
+ testRuntimeOnly AndroidSdk.MAX_SDK.coordinates // run against whatever this JDK supports
}
diff --git a/annotations/src/main/java/org/robolectric/annotation/InDevelopment.java b/annotations/src/main/java/org/robolectric/annotation/InDevelopment.java
new file mode 100644
index 000000000..beacbe92a
--- /dev/null
+++ b/annotations/src/main/java/org/robolectric/annotation/InDevelopment.java
@@ -0,0 +1,18 @@
+package org.robolectric.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * InDevelopment applies to @Implementation methods and @Implements classes that are affected by
+ * changes in unreleased versions of Android. <br>
+ * Only unreleased versions of android, as defined in the AndroidVersions honor this annotation
+ * during validation.
+ */
+@Documented
+@Retention(RetentionPolicy.SOURCE)
+@Target({ElementType.METHOD, ElementType.TYPE})
+public @interface InDevelopment {}
diff --git a/shadows/versioning/src/main/java/org/robolectric/versioning/AndroidVersionInitTools.java b/annotations/src/main/java/org/robolectric/versioning/AndroidVersionInitTools.java
index 316365b39..316365b39 100644
--- a/shadows/versioning/src/main/java/org/robolectric/versioning/AndroidVersionInitTools.java
+++ b/annotations/src/main/java/org/robolectric/versioning/AndroidVersionInitTools.java
diff --git a/shadows/versioning/src/main/java/org/robolectric/versioning/AndroidVersions.java b/annotations/src/main/java/org/robolectric/versioning/AndroidVersions.java
index e5431eb97..71cc48042 100644
--- a/shadows/versioning/src/main/java/org/robolectric/versioning/AndroidVersions.java
+++ b/annotations/src/main/java/org/robolectric/versioning/AndroidVersions.java
@@ -19,7 +19,7 @@ package org.robolectric.versioning;
import static java.util.Arrays.asList;
import java.io.IOException;
-import java.lang.reflect.Field;
+import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
import java.util.AbstractMap;
@@ -29,12 +29,11 @@ import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
+import java.util.Objects;
import java.util.Properties;
import java.util.jar.JarFile;
import java.util.zip.ZipEntry;
import javax.annotation.Nullable;
-import org.robolectric.util.Logger;
-import org.robolectric.util.ReflectionHelpers;
/**
* Android versioning is complicated.<br>
@@ -63,17 +62,13 @@ public final class AndroidVersions {
* true if this release has already occurred, false otherwise. If unreleased, the getSdkInt may
* still be that of the prior release.
*/
- public int getSdkInt() {
- return ReflectionHelpers.getStaticField(this.getClass(), "SDK_INT");
- }
+ public abstract int getSdkInt();
/**
* single character short code for the release, multiple characters for minor releases (only
* minor version numbers increment - usually within the same year).
*/
- public String getShortCode() {
- return ReflectionHelpers.getStaticField(this.getClass(), "SHORT_CODE");
- }
+ public abstract String getShortCode();
/**
* true if this release has already occurred, false otherwise. If unreleased, the getSdkInt will
@@ -81,26 +76,22 @@ public final class AndroidVersions {
* including most modern build tools; bazle, soong all are full build systems - and as such
* organizations using them have no concerns.
*/
- public boolean isReleased() {
- return ReflectionHelpers.getStaticField(this.getClass(), "RELEASED");
- }
+ public abstract boolean isReleased();
/** major.minor version number as String. */
- public String getVersion() {
- return ReflectionHelpers.getStaticField(this.getClass(), "VERSION");
- }
+ public abstract String getVersion();
/**
* Implements comparable.
*
* @param other the object to be compared.
* @return 1 if this is greater than other, 0 if equal, -1 if less
- * @throws RuntimeException if other is not an instance of AndroidRelease.
+ * @throws IllegalStateException if other is not an instance of AndroidRelease.
*/
@Override
public int compareTo(AndroidRelease other) {
if (other == null) {
- throw new RuntimeException(
+ throw new IllegalStateException(
"Only "
+ AndroidVersions.class.getName()
+ " should define Releases, illegal class "
@@ -123,21 +114,80 @@ public final class AndroidVersions {
}
}
+ /** A released version of Android */
+ public abstract static class AndroidReleased extends AndroidRelease {
+ @Override
+ public boolean isReleased() {
+ return true;
+ }
+ }
+
+ /** An in-development version of Android */
+ public abstract static class AndroidUnreleased extends AndroidRelease {
+ @Override
+ public boolean isReleased() {
+ return false;
+ }
+ }
+
+ /**
+ * Version: -1 <br>
+ * ShortCode: "" <br>
+ * SDK API Level: "" <br>
+ * release: false <br>
+ */
+ public static final class Unbound extends AndroidUnreleased {
+
+ public static final int SDK_INT = -1;
+
+ public static final String SHORT_CODE = "_";
+
+ public static final String VERSION = "_";
+
+ @Override
+ public int getSdkInt() {
+ return SDK_INT;
+ }
+
+ @Override
+ public String getShortCode() {
+ return SHORT_CODE;
+ }
+
+ @Override
+ public String getVersion() {
+ return VERSION;
+ }
+ }
+
/**
* Version: 4.1 <br>
* ShortCode: J <br>
* SDK API Level: 16 <br>
* release: true <br>
*/
- public static final class J extends AndroidRelease {
+ public static final class J extends AndroidReleased {
public static final int SDK_INT = 16;
- public static final boolean RELEASED = true;
-
public static final String SHORT_CODE = "J";
public static final String VERSION = "4.1";
+
+ @Override
+ public int getSdkInt() {
+ return SDK_INT;
+ }
+
+ @Override
+ public String getShortCode() {
+ return SHORT_CODE;
+ }
+
+ @Override
+ public String getVersion() {
+ return VERSION;
+ }
}
/**
@@ -146,15 +196,28 @@ public final class AndroidVersions {
* SDK API Level: 17 <br>
* release: true <br>
*/
- public static final class JMR1 extends AndroidRelease {
+ public static final class JMR1 extends AndroidReleased {
public static final int SDK_INT = 17;
- public static final boolean RELEASED = true;
-
public static final String SHORT_CODE = "JMR1";
public static final String VERSION = "4.2";
+
+ @Override
+ public int getSdkInt() {
+ return SDK_INT;
+ }
+
+ @Override
+ public String getShortCode() {
+ return SHORT_CODE;
+ }
+
+ @Override
+ public String getVersion() {
+ return VERSION;
+ }
}
/**
@@ -163,15 +226,28 @@ public final class AndroidVersions {
* SDK API Level: 18 <br>
* release: true <br>
*/
- public static final class JMR2 extends AndroidRelease {
+ public static final class JMR2 extends AndroidReleased {
public static final int SDK_INT = 18;
- public static final boolean RELEASED = true;
-
public static final String SHORT_CODE = "JMR2";
public static final String VERSION = "4.3";
+
+ @Override
+ public int getSdkInt() {
+ return SDK_INT;
+ }
+
+ @Override
+ public String getShortCode() {
+ return SHORT_CODE;
+ }
+
+ @Override
+ public String getVersion() {
+ return VERSION;
+ }
}
/**
@@ -180,15 +256,28 @@ public final class AndroidVersions {
* SDK API Level: 19 <br>
* release: true <br>
*/
- public static final class K extends AndroidRelease {
+ public static final class K extends AndroidReleased {
public static final int SDK_INT = 19;
- public static final boolean RELEASED = true;
-
public static final String SHORT_CODE = "K";
public static final String VERSION = "4.4";
+
+ @Override
+ public int getSdkInt() {
+ return SDK_INT;
+ }
+
+ @Override
+ public String getShortCode() {
+ return SHORT_CODE;
+ }
+
+ @Override
+ public String getVersion() {
+ return VERSION;
+ }
}
// Skipping K Watch release, which was 20.
@@ -199,15 +288,28 @@ public final class AndroidVersions {
* SDK API Level: 21 <br>
* release: true <br>
*/
- public static final class L extends AndroidRelease {
+ public static final class L extends AndroidReleased {
public static final int SDK_INT = 21;
- public static final boolean RELEASED = true;
-
public static final String SHORT_CODE = "L";
public static final String VERSION = "5.0";
+
+ @Override
+ public int getSdkInt() {
+ return SDK_INT;
+ }
+
+ @Override
+ public String getShortCode() {
+ return SHORT_CODE;
+ }
+
+ @Override
+ public String getVersion() {
+ return VERSION;
+ }
}
/**
@@ -216,15 +318,28 @@ public final class AndroidVersions {
* SDK API Level: 22 <br>
* release: true <br>
*/
- public static final class LMR1 extends AndroidRelease {
+ public static final class LMR1 extends AndroidReleased {
public static final int SDK_INT = 22;
- public static final boolean RELEASED = true;
-
public static final String SHORT_CODE = "LMR1";
public static final String VERSION = "5.1";
+
+ @Override
+ public int getSdkInt() {
+ return SDK_INT;
+ }
+
+ @Override
+ public String getShortCode() {
+ return SHORT_CODE;
+ }
+
+ @Override
+ public String getVersion() {
+ return VERSION;
+ }
}
/**
@@ -233,15 +348,28 @@ public final class AndroidVersions {
* SDK API Level: 23 <br>
* release: true <br>
*/
- public static final class M extends AndroidRelease {
+ public static final class M extends AndroidReleased {
public static final int SDK_INT = 23;
- public static final boolean RELEASED = true;
-
public static final String SHORT_CODE = "M";
public static final String VERSION = "6.0";
+
+ @Override
+ public int getSdkInt() {
+ return SDK_INT;
+ }
+
+ @Override
+ public String getShortCode() {
+ return SHORT_CODE;
+ }
+
+ @Override
+ public String getVersion() {
+ return VERSION;
+ }
}
/**
@@ -250,15 +378,28 @@ public final class AndroidVersions {
* SDK API Level: 24 <br>
* release: true <br>
*/
- public static final class N extends AndroidRelease {
+ public static final class N extends AndroidReleased {
public static final int SDK_INT = 24;
- public static final boolean RELEASED = true;
-
public static final String SHORT_CODE = "N";
public static final String VERSION = "7.0";
+
+ @Override
+ public int getSdkInt() {
+ return SDK_INT;
+ }
+
+ @Override
+ public String getShortCode() {
+ return SHORT_CODE;
+ }
+
+ @Override
+ public String getVersion() {
+ return VERSION;
+ }
}
/**
@@ -267,15 +408,28 @@ public final class AndroidVersions {
* SDK Framework: 25 <br>
* release: true <br>
*/
- public static final class NMR1 extends AndroidRelease {
+ public static final class NMR1 extends AndroidReleased {
public static final int SDK_INT = 25;
- public static final boolean RELEASED = true;
-
public static final String SHORT_CODE = "NMR1";
- private static final String VERSION = "7.1";
+ public static final String VERSION = "7.1";
+
+ @Override
+ public int getSdkInt() {
+ return SDK_INT;
+ }
+
+ @Override
+ public String getShortCode() {
+ return SHORT_CODE;
+ }
+
+ @Override
+ public String getVersion() {
+ return VERSION;
+ }
}
/**
@@ -284,15 +438,28 @@ public final class AndroidVersions {
* SDK API Level: 26 <br>
* release: true <br>
*/
- public static final class O extends AndroidRelease {
+ public static final class O extends AndroidReleased {
public static final int SDK_INT = 26;
- public static final boolean RELEASED = true;
-
public static final String SHORT_CODE = "O";
public static final String VERSION = "8.0";
+
+ @Override
+ public int getSdkInt() {
+ return SDK_INT;
+ }
+
+ @Override
+ public String getShortCode() {
+ return SHORT_CODE;
+ }
+
+ @Override
+ public String getVersion() {
+ return VERSION;
+ }
}
/**
@@ -301,15 +468,28 @@ public final class AndroidVersions {
* SDK API Level: 27 <br>
* release: true <br>
*/
- public static final class OMR1 extends AndroidRelease {
+ public static final class OMR1 extends AndroidReleased {
public static final int SDK_INT = 27;
- public static final boolean RELEASED = true;
-
public static final String SHORT_CODE = "OMR1";
public static final String VERSION = "8.1";
+
+ @Override
+ public int getSdkInt() {
+ return SDK_INT;
+ }
+
+ @Override
+ public String getShortCode() {
+ return SHORT_CODE;
+ }
+
+ @Override
+ public String getVersion() {
+ return VERSION;
+ }
}
/**
@@ -318,15 +498,28 @@ public final class AndroidVersions {
* SDK API Level: 28 <br>
* release: true <br>
*/
- public static final class P extends AndroidRelease {
+ public static final class P extends AndroidReleased {
public static final int SDK_INT = 28;
- public static final boolean RELEASED = true;
-
public static final String SHORT_CODE = "P";
public static final String VERSION = "9.0";
+
+ @Override
+ public int getSdkInt() {
+ return SDK_INT;
+ }
+
+ @Override
+ public String getShortCode() {
+ return SHORT_CODE;
+ }
+
+ @Override
+ public String getVersion() {
+ return VERSION;
+ }
}
/**
@@ -335,15 +528,28 @@ public final class AndroidVersions {
* SDK API Level: 29 <br>
* release: true <br>
*/
- public static final class Q extends AndroidRelease {
+ public static final class Q extends AndroidReleased {
public static final int SDK_INT = 29;
- public static final boolean RELEASED = true;
-
public static final String SHORT_CODE = "Q";
public static final String VERSION = "10.0";
+
+ @Override
+ public int getSdkInt() {
+ return SDK_INT;
+ }
+
+ @Override
+ public String getShortCode() {
+ return SHORT_CODE;
+ }
+
+ @Override
+ public String getVersion() {
+ return VERSION;
+ }
}
/**
@@ -352,15 +558,28 @@ public final class AndroidVersions {
* SDK API Level: 30 <br>
* release: true <br>
*/
- public static final class R extends AndroidRelease {
+ public static final class R extends AndroidReleased {
public static final int SDK_INT = 30;
- public static final boolean RELEASED = true;
-
public static final String SHORT_CODE = "R";
public static final String VERSION = "11.0";
+
+ @Override
+ public int getSdkInt() {
+ return SDK_INT;
+ }
+
+ @Override
+ public String getShortCode() {
+ return SHORT_CODE;
+ }
+
+ @Override
+ public String getVersion() {
+ return VERSION;
+ }
}
/**
@@ -369,15 +588,28 @@ public final class AndroidVersions {
* SDK API Level: 31 <br>
* release: true <br>
*/
- public static final class S extends AndroidRelease {
+ public static final class S extends AndroidReleased {
public static final int SDK_INT = 31;
- public static final boolean RELEASED = true;
-
public static final String SHORT_CODE = "S";
public static final String VERSION = "12.0";
+
+ @Override
+ public int getSdkInt() {
+ return SDK_INT;
+ }
+
+ @Override
+ public String getShortCode() {
+ return SHORT_CODE;
+ }
+
+ @Override
+ public String getVersion() {
+ return VERSION;
+ }
}
/**
@@ -387,15 +619,28 @@ public final class AndroidVersions {
* release: true <br>
*/
@SuppressWarnings("UPPER_SNAKE_CASE")
- public static final class Sv2 extends AndroidRelease {
+ public static final class Sv2 extends AndroidReleased {
public static final int SDK_INT = 32;
- public static final boolean RELEASED = true;
-
public static final String SHORT_CODE = "Sv2";
public static final String VERSION = "12.1";
+
+ @Override
+ public int getSdkInt() {
+ return SDK_INT;
+ }
+
+ @Override
+ public String getShortCode() {
+ return SHORT_CODE;
+ }
+
+ @Override
+ public String getVersion() {
+ return VERSION;
+ }
}
/**
@@ -404,15 +649,28 @@ public final class AndroidVersions {
* SDK API Level: 33 <br>
* release: true <br>
*/
- public static final class T extends AndroidRelease {
+ public static final class T extends AndroidReleased {
public static final int SDK_INT = 33;
- public static final boolean RELEASED = true;
-
public static final String SHORT_CODE = "T";
public static final String VERSION = "13.0";
+
+ @Override
+ public int getSdkInt() {
+ return SDK_INT;
+ }
+
+ @Override
+ public String getShortCode() {
+ return SHORT_CODE;
+ }
+
+ @Override
+ public String getVersion() {
+ return VERSION;
+ }
}
/**
@@ -421,15 +679,28 @@ public final class AndroidVersions {
* SDK API Level: 34 <br>
* release: false <br>
*/
- public static final class U extends AndroidRelease {
+ public static final class U extends AndroidReleased {
public static final int SDK_INT = 34;
- public static final boolean RELEASED = true;
-
public static final String SHORT_CODE = "U";
public static final String VERSION = "14.0";
+
+ @Override
+ public int getSdkInt() {
+ return SDK_INT;
+ }
+
+ @Override
+ public String getShortCode() {
+ return SHORT_CODE;
+ }
+
+ @Override
+ public String getVersion() {
+ return VERSION;
+ }
}
/**
@@ -438,15 +709,28 @@ public final class AndroidVersions {
* SDK API Level: 34+ <br>
* release: false <br>
*/
- public static final class V extends AndroidRelease {
+ public static final class V extends AndroidUnreleased {
public static final int SDK_INT = 35;
- public static final boolean RELEASED = false;
-
public static final String SHORT_CODE = "V";
public static final String VERSION = "15";
+
+ @Override
+ public int getSdkInt() {
+ return SDK_INT;
+ }
+
+ @Override
+ public String getShortCode() {
+ return SHORT_CODE;
+ }
+
+ @Override
+ public String getVersion() {
+ return VERSION;
+ }
}
/** The current release this process is running on. */
@@ -574,7 +858,7 @@ public final class AndroidVersions {
}
}
if (errors.length() > 0) {
- throw new RuntimeException(
+ throw new IllegalStateException(
errors
.append("Please check the AndroidReleases defined ")
.append("in ")
@@ -587,18 +871,13 @@ public final class AndroidVersions {
public AndroidRelease computeCurrentSdk(
int reportedVersion, String releaseName, String codename, List<String> activeCodeNames) {
- Logger.info("Reported Version: " + reportedVersion);
- Logger.info("Release Name: " + releaseName);
- Logger.info("Code Name: " + codename);
- Logger.info("Active Code Names: " + String.join(",", activeCodeNames));
-
AndroidRelease current = null;
// Special case "REL", which means the build is not a pre-release build.
- if ("REL".equals(codename)) {
+ if (Objects.equals(codename, "REL")) {
// the first letter of the code name equal to the release number.
current = sdkIntToAllReleases.get(reportedVersion);
if (current != null && !current.isReleased()) {
- throw new RuntimeException(
+ throw new IllegalStateException(
"The current sdk "
+ current.getShortCode()
+ " has been released. Please update the contents of "
@@ -657,7 +936,7 @@ public final class AndroidVersions {
.append("contents of current sdk jar to the released version.\n");
}
if (detectedProblems.length() > 0) {
- throw new RuntimeException(detectedProblems.toString());
+ throw new IllegalStateException(detectedProblems.toString());
}
}
}
@@ -670,7 +949,7 @@ public final class AndroidVersions {
* the shortCode, sdkInt, and release information.
*
* <p>All errors are stored and can be reported at once by asking the SdkInformation to throw a
- * runtime exception after it has been populated.
+ * IllegalStateException after it has been populated.
*/
static SdkInformation gatherStaticSdkInformationFromThisClass() {
List<AndroidRelease> allReleases = new ArrayList<>();
@@ -678,7 +957,8 @@ public final class AndroidVersions {
for (Class<?> clazz : AndroidVersions.class.getClasses()) {
if (AndroidRelease.class.isAssignableFrom(clazz)
&& !clazz.isInterface()
- && !Modifier.isAbstract(clazz.getModifiers())) {
+ && !Modifier.isAbstract(clazz.getModifiers())
+ && clazz != Unbound.class) {
try {
AndroidRelease rel = (AndroidRelease) clazz.getDeclaredConstructor().newInstance();
allReleases.add(rel);
@@ -691,7 +971,7 @@ public final class AndroidVersions {
| IllegalArgumentException
| IllegalAccessException
| InvocationTargetException ex) {
- throw new RuntimeException(
+ throw new IllegalStateException(
"Classes "
+ clazz.getName()
+ "should be accessible via "
@@ -731,50 +1011,19 @@ public final class AndroidVersions {
return information.computeCurrentSdk(sdk, release, codename, asList(activeCodeNames));
}
- /**
- * If we are working in android source, this code detects the list of active code names if any.
- */
- private static List<String> getActiveCodeNamesIfAny(Class<?> targetClass) {
- try {
- Field activeCodeFields = targetClass.getDeclaredField("ACTIVE_CODENAMES");
- String[] activeCodeNames = (String[]) activeCodeFields.get(null);
- if (activeCodeNames == null) {
- return new ArrayList<>();
- }
- return asList(activeCodeNames);
- } catch (NoSuchFieldException | IllegalAccessException | IllegalArgumentException ex) {
- return new ArrayList<>();
- }
- }
-
private static final SdkInformation information;
static {
AndroidRelease currentRelease = null;
information = gatherStaticSdkInformationFromThisClass();
try {
- Class<?> buildClass =
- Class.forName("android.os.Build", false, Thread.currentThread().getContextClassLoader());
- System.out.println("build class " + buildClass);
- Class<?> versionClass = null;
- for (Class<?> c : buildClass.getClasses()) {
- if (c.getSimpleName().equals("VERSION")) {
- versionClass = c;
- System.out.println("Version class " + versionClass);
- break;
- }
- }
- if (versionClass != null) {
- // 33, 34, etc....
- int sdkInt = (int) ReflectionHelpers.getStaticField(versionClass, "SDK_INT");
- // Either unset, or 13, 14, etc....
- String release = ReflectionHelpers.getStaticField(versionClass, "RELEASE");
- // Either REL if release is set, or Tiramasu, UpsideDownCake, etc
- String codename = ReflectionHelpers.getStaticField(versionClass, "CODENAME");
- List<String> activeCodeNames = getActiveCodeNamesIfAny(versionClass);
- currentRelease = information.computeCurrentSdk(sdkInt, release, codename, activeCodeNames);
+ InputStream is = AndroidVersions.class.getClassLoader().getResourceAsStream("build.prop");
+ if (is != null) {
+ Properties buildProps = new Properties();
+ buildProps.load(is);
+ currentRelease = computeCurrentSdkFromBuildProps(buildProps);
}
- } catch (ClassNotFoundException | IllegalArgumentException | UnsatisfiedLinkError e) {
+ } catch (IOException ioe) {
// No op, this class should be usable outside of a Robolectric sandbox.
}
CURRENT = currentRelease;
diff --git a/shadows/versioning/src/test/java/org/robolectric/versioning/AndroidVersionsEdgeCaseTest.java b/annotations/src/test/java/org/robolectric/versioning/AndroidVersionsEdgeCaseTest.java
index 95b2c4266..95b2c4266 100644
--- a/shadows/versioning/src/test/java/org/robolectric/versioning/AndroidVersionsEdgeCaseTest.java
+++ b/annotations/src/test/java/org/robolectric/versioning/AndroidVersionsEdgeCaseTest.java
diff --git a/annotations/src/test/java/org/robolectric/versioning/AndroidVersionsTest.java b/annotations/src/test/java/org/robolectric/versioning/AndroidVersionsTest.java
new file mode 100644
index 000000000..670e695a3
--- /dev/null
+++ b/annotations/src/test/java/org/robolectric/versioning/AndroidVersionsTest.java
@@ -0,0 +1,169 @@
+package org.robolectric.versioning;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Check versions information aligns with runtime information. Primarily, selected SDK with
+ * internally detected version number.
+ */
+@RunWith(JUnit4.class)
+public final class AndroidVersionsTest {
+
+ @Test
+ public void testStandardInitializationT() {
+ assertThat(AndroidVersions.T.SDK_INT).isEqualTo(33);
+ assertThat(AndroidVersions.T.SHORT_CODE).isEqualTo("T");
+ assertThat(AndroidVersions.T.VERSION).isEqualTo("13.0");
+ assertThat(new AndroidVersions.T().getSdkInt()).isEqualTo(33);
+ assertThat(new AndroidVersions.T().getShortCode()).isEqualTo("T");
+ assertThat(new AndroidVersions.T().getVersion()).isEqualTo("13.0");
+ assertThat(new AndroidVersions.T().isReleased()).isEqualTo(true);
+ }
+
+ @Test
+ public void testStandardInitializationSv2() {
+ assertThat(AndroidVersions.Sv2.SDK_INT).isEqualTo(32);
+ assertThat(AndroidVersions.Sv2.SHORT_CODE).isEqualTo("Sv2");
+ assertThat(AndroidVersions.Sv2.VERSION).isEqualTo("12.1");
+ assertThat(new AndroidVersions.Sv2().getSdkInt()).isEqualTo(32);
+ assertThat(new AndroidVersions.Sv2().getShortCode()).isEqualTo("Sv2");
+ assertThat(new AndroidVersions.Sv2().getVersion()).isEqualTo("12.1");
+ assertThat(new AndroidVersions.Sv2().isReleased()).isEqualTo(true);
+ }
+
+ @Test
+ public void testStandardInitializationS() {
+ assertThat(AndroidVersions.S.SDK_INT).isEqualTo(31);
+ assertThat(AndroidVersions.S.SHORT_CODE).isEqualTo("S");
+ assertThat(AndroidVersions.S.VERSION).isEqualTo("12.0");
+ assertThat(new AndroidVersions.S().getSdkInt()).isEqualTo(31);
+ assertThat(new AndroidVersions.S().getShortCode()).isEqualTo("S");
+ assertThat(new AndroidVersions.S().getVersion()).isEqualTo("12.0");
+ assertThat(new AndroidVersions.S().isReleased()).isEqualTo(true);
+ }
+
+ @Test
+ public void testStandardInitializationR() {
+ assertThat(AndroidVersions.R.SDK_INT).isEqualTo(30);
+ assertThat(AndroidVersions.R.SHORT_CODE).isEqualTo("R");
+ assertThat(AndroidVersions.R.VERSION).isEqualTo("11.0");
+ assertThat(new AndroidVersions.R().getSdkInt()).isEqualTo(30);
+ assertThat(new AndroidVersions.R().getShortCode()).isEqualTo("R");
+ assertThat(new AndroidVersions.R().getVersion()).isEqualTo("11.0");
+ assertThat(new AndroidVersions.R().isReleased()).isEqualTo(true);
+ }
+
+ @Test
+ public void testStandardInitializationQ() {
+ assertThat(AndroidVersions.Q.SDK_INT).isEqualTo(29);
+ assertThat(AndroidVersions.Q.SHORT_CODE).isEqualTo("Q");
+ assertThat(AndroidVersions.Q.VERSION).isEqualTo("10.0");
+ assertThat(new AndroidVersions.Q().getSdkInt()).isEqualTo(29);
+ assertThat(new AndroidVersions.Q().getShortCode()).isEqualTo("Q");
+ assertThat(new AndroidVersions.Q().getVersion()).isEqualTo("10.0");
+ assertThat(new AndroidVersions.Q().isReleased()).isEqualTo(true);
+ }
+
+ @Test
+ public void testStandardInitializationP() {
+ assertThat(AndroidVersions.P.SDK_INT).isEqualTo(28);
+ assertThat(AndroidVersions.P.SHORT_CODE).isEqualTo("P");
+ assertThat(AndroidVersions.P.VERSION).isEqualTo("9.0");
+ assertThat(new AndroidVersions.P().getSdkInt()).isEqualTo(28);
+ assertThat(new AndroidVersions.P().getShortCode()).isEqualTo("P");
+ assertThat(new AndroidVersions.P().getVersion()).isEqualTo("9.0");
+ assertThat(new AndroidVersions.P().isReleased()).isEqualTo(true);
+ }
+
+ @Test
+ public void testStandardInitializationOMR1() {
+ assertThat(AndroidVersions.OMR1.SDK_INT).isEqualTo(27);
+ assertThat(AndroidVersions.OMR1.SHORT_CODE).isEqualTo("OMR1");
+ assertThat(AndroidVersions.OMR1.VERSION).isEqualTo("8.1");
+ assertThat(new AndroidVersions.OMR1().getSdkInt()).isEqualTo(27);
+ assertThat(new AndroidVersions.OMR1().getShortCode()).isEqualTo("OMR1");
+ assertThat(new AndroidVersions.OMR1().getVersion()).isEqualTo("8.1");
+ assertThat(new AndroidVersions.OMR1().isReleased()).isEqualTo(true);
+ }
+
+ @Test
+ public void testStandardInitializationO() {
+ assertThat(AndroidVersions.O.SDK_INT).isEqualTo(26);
+ assertThat(AndroidVersions.O.SHORT_CODE).isEqualTo("O");
+ assertThat(AndroidVersions.O.VERSION).isEqualTo("8.0");
+ assertThat(new AndroidVersions.O().getSdkInt()).isEqualTo(26);
+ assertThat(new AndroidVersions.O().getShortCode()).isEqualTo("O");
+ assertThat(new AndroidVersions.O().getVersion()).isEqualTo("8.0");
+ assertThat(new AndroidVersions.O().isReleased()).isEqualTo(true);
+ }
+
+ @Test
+ public void testStandardInitializationNMR1() {
+ assertThat(AndroidVersions.NMR1.SDK_INT).isEqualTo(25);
+ assertThat(AndroidVersions.NMR1.SHORT_CODE).isEqualTo("NMR1");
+ assertThat(AndroidVersions.NMR1.VERSION).isEqualTo("7.1");
+ assertThat(new AndroidVersions.NMR1().getSdkInt()).isEqualTo(25);
+ assertThat(new AndroidVersions.NMR1().getShortCode()).isEqualTo("NMR1");
+ assertThat(new AndroidVersions.NMR1().getVersion()).isEqualTo("7.1");
+ assertThat(new AndroidVersions.NMR1().isReleased()).isEqualTo(true);
+ }
+
+ @Test
+ public void testStandardInitializationN() {
+ assertThat(AndroidVersions.N.SDK_INT).isEqualTo(24);
+ assertThat(AndroidVersions.N.SHORT_CODE).isEqualTo("N");
+ assertThat(AndroidVersions.N.VERSION).isEqualTo("7.0");
+ assertThat(new AndroidVersions.N().getSdkInt()).isEqualTo(24);
+ assertThat(new AndroidVersions.N().getShortCode()).isEqualTo("N");
+ assertThat(new AndroidVersions.N().getVersion()).isEqualTo("7.0");
+ assertThat(new AndroidVersions.N().isReleased()).isEqualTo(true);
+ }
+
+ @Test
+ public void testStandardInitializationM() {
+ assertThat(AndroidVersions.M.SDK_INT).isEqualTo(23);
+ assertThat(AndroidVersions.M.SHORT_CODE).isEqualTo("M");
+ assertThat(AndroidVersions.M.VERSION).isEqualTo("6.0");
+ assertThat(new AndroidVersions.M().getSdkInt()).isEqualTo(23);
+ assertThat(new AndroidVersions.M().getShortCode()).isEqualTo("M");
+ assertThat(new AndroidVersions.M().getVersion()).isEqualTo("6.0");
+ assertThat(new AndroidVersions.M().isReleased()).isEqualTo(true);
+ }
+
+ @Test
+ public void testStandardInitializationLMR1() {
+ assertThat(AndroidVersions.LMR1.SDK_INT).isEqualTo(22);
+ assertThat(AndroidVersions.LMR1.SHORT_CODE).isEqualTo("LMR1");
+ assertThat(AndroidVersions.LMR1.VERSION).isEqualTo("5.1");
+ assertThat(new AndroidVersions.LMR1().getSdkInt()).isEqualTo(22);
+ assertThat(new AndroidVersions.LMR1().getShortCode()).isEqualTo("LMR1");
+ assertThat(new AndroidVersions.LMR1().getVersion()).isEqualTo("5.1");
+ assertThat(new AndroidVersions.LMR1().isReleased()).isEqualTo(true);
+ }
+
+ @Test
+ public void testStandardInitializationL() {
+ assertThat(AndroidVersions.L.SDK_INT).isEqualTo(21);
+ assertThat(AndroidVersions.L.SHORT_CODE).isEqualTo("L");
+ assertThat(AndroidVersions.L.VERSION).isEqualTo("5.0");
+ assertThat(new AndroidVersions.L().getSdkInt()).isEqualTo(21);
+ assertThat(new AndroidVersions.L().getShortCode()).isEqualTo("L");
+ assertThat(new AndroidVersions.L().getVersion()).isEqualTo("5.0");
+ assertThat(new AndroidVersions.L().isReleased()).isEqualTo(true);
+ }
+
+ @Test
+ public void testStandardInitializationK() {
+ assertThat(AndroidVersions.K.SDK_INT).isEqualTo(19);
+ assertThat(AndroidVersions.K.SHORT_CODE).isEqualTo("K");
+ assertThat(AndroidVersions.K.VERSION).isEqualTo("4.4");
+ assertThat(new AndroidVersions.K().getSdkInt()).isEqualTo(19);
+ assertThat(new AndroidVersions.K().getShortCode()).isEqualTo("K");
+ assertThat(new AndroidVersions.K().getVersion()).isEqualTo("4.4");
+ assertThat(new AndroidVersions.K().isReleased()).isEqualTo(true);
+ }
+}
diff --git a/build.gradle b/build.gradle
index 29ad8a91c..0f79ab057 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,5 +1,5 @@
import org.gradle.plugins.ide.idea.model.IdeaModel
-
+import org.robolectric.gradle.SpotlessPlugin
buildscript {
apply from: 'dependencies.gradle'
@@ -33,21 +33,8 @@ allprojects {
}
apply plugin: 'idea'
-apply plugin: "com.diffplug.spotless"
-
-// Apply Spotless for buildSrc and build scripts
-spotless {
- java {
- target("buildSrc/**/*.java")
- googleJavaFormat("1.17.0")
- }
- groovy {
- target("buildSrc/**/*.groovy")
- }
- groovyGradle {
- target('*.gradle', "**/*.gradle")
- }
-}
+// apply SpotlessPlugin
+apply plugin: SpotlessPlugin
project.ext.configAnnotationProcessing = []
project.afterEvaluate {
@@ -58,8 +45,9 @@ project.afterEvaluate {
// prevent compiler from complaining about duplicate classes...
def excludeFromCompile = compilerConfiguration.appendNode 'excludeFromCompile'
configAnnotationProcessing.each { Project subProject ->
+ def buildDirectory = subProject.layout.buildDirectory.get().asFile
excludeFromCompile.appendNode('directory',
- [url: "file://${subProject.buildDir}/classes/java/main/generated", includeSubdirectories: "true"])
+ [url: "file://$buildDirectory/classes/java/main/generated", includeSubdirectories: "true"])
}
// replace existing annotationProcessing tag with a new one...
@@ -73,13 +61,16 @@ project.afterEvaluate {
processor(name: "org.robolectric.annotation.processing.RobolectricProcessor")
processorPath(useClasspath: "false") {
- def processorRuntimeCfg = project.project(":processor").configurations['runtime']
- processorRuntimeCfg.allArtifacts.each { artifact ->
- entry(name: artifact.file)
- }
- processorRuntimeCfg.files.each { file ->
- entry(name: file)
- }
+ project.project(":processor")
+ .configurations.named("runtime")
+ .configure { runtimeConfiguration ->
+ runtimeConfiguration.allArtifacts.each { artifact ->
+ entry(name: artifact.file)
+ }
+ runtimeConfiguration.files.each { file ->
+ entry(name: file)
+ }
+ }
}
}
}
@@ -91,7 +82,9 @@ project.afterEvaluate {
apply plugin: 'nebula-aggregate-javadocs'
rootProject.gradle.projectsEvaluated {
- rootProject.tasks['aggregateJavadocs'].failOnError = false
+ rootProject.tasks.named("aggregateJavadocs").configure {
+ it.failOnError = false
+ }
}
gradle.projectsEvaluated {
@@ -122,10 +115,10 @@ gradle.projectsEvaluated {
gradle.projectsEvaluated {
tasks.register('aggregateJsondocs', Copy) {
project.subprojects.findAll { it.plugins.hasPlugin(ShadowsPlugin) }.each { subproject ->
- dependsOn subproject.tasks["compileJava"]
- from "${subproject.buildDir}/docs/json"
+ dependsOn subproject.tasks.named("compileJava")
+ from subproject.layout.buildDirectory.dir("docs/json")
}
- into "$buildDir/docs/json"
+ into layout.buildDirectory.dir("docs/json")
}
}
diff --git a/buildSrc/src/main/groovy/AndroidSdk.groovy b/buildSrc/src/main/groovy/AndroidSdk.groovy
index 59bbb5541..5a6ed794b 100644
--- a/buildSrc/src/main/groovy/AndroidSdk.groovy
+++ b/buildSrc/src/main/groovy/AndroidSdk.groovy
@@ -1,5 +1,5 @@
class AndroidSdk implements Comparable<AndroidSdk> {
- static final PREINSTRUMENTED_VERSION = 5
+ static final PREINSTRUMENTED_VERSION = 6
static final KITKAT = new AndroidSdk(19, "4.4_r1", "r2")
static final LOLLIPOP = new AndroidSdk(21, "5.0.2_r3", "r0")
@@ -9,13 +9,13 @@ class AndroidSdk implements Comparable<AndroidSdk> {
static final N_MR1 = new AndroidSdk(25, "7.1.0_r7", "r1")
static final O = new AndroidSdk(26, "8.0.0_r4", "r1")
static final O_MR1 = new AndroidSdk(27, "8.1.0", "4611349")
- static final P = new AndroidSdk(28, "9", "4913185-2");
- static final Q = new AndroidSdk(29, "10", "5803371");
- static final R = new AndroidSdk(30, "11", "6757853");
- static final S = new AndroidSdk(31, "12", "7732740");
- static final S_V2 = new AndroidSdk(32, "12.1", "8229987");
- static final TIRAMISU = new AndroidSdk(33, "13", "9030017");
- static final U = new AndroidSdk(34, "14", "10818077");
+ static final P = new AndroidSdk(28, "9", "4913185-2")
+ static final Q = new AndroidSdk(29, "10", "5803371")
+ static final R = new AndroidSdk(30, "11", "6757853")
+ static final S = new AndroidSdk(31, "12", "7732740")
+ static final S_V2 = new AndroidSdk(32, "12.1", "8229987")
+ static final TIRAMISU = new AndroidSdk(33, "13", "9030017")
+ static final U = new AndroidSdk(34, "14", "10818077")
static final List<AndroidSdk> ALL_SDKS = [
KITKAT,
@@ -26,7 +26,7 @@ class AndroidSdk implements Comparable<AndroidSdk> {
static final MAX_SDK = Collections.max(ALL_SDKS)
public final int apiLevel
- private final String androidVersion
+ public final String androidVersion
private final String frameworkSdkBuildVersion
AndroidSdk(int apiLevel, String androidVersion, String frameworkSdkBuildVersion) {
diff --git a/buildSrc/src/main/groovy/ProvideBuildClasspathTask.groovy b/buildSrc/src/main/groovy/ProvideBuildClasspathTask.groovy
index 8a6d0f279..014b1b28c 100644
--- a/buildSrc/src/main/groovy/ProvideBuildClasspathTask.groovy
+++ b/buildSrc/src/main/groovy/ProvideBuildClasspathTask.groovy
@@ -6,18 +6,18 @@ class ProvideBuildClasspathTask extends DefaultTask {
@OutputFile File outFile
@TaskAction
- public void writeProperties() throws Exception {
+ void writeProperties() throws Exception {
final Properties props = new Properties()
- String preinstrumentedKey = "robolectric.usePreinstrumentedJars";
+ String preinstrumentedKey = "robolectric.usePreinstrumentedJars"
boolean usePreinstrumentedJars =
Boolean.parseBoolean(
- System.getProperty(preinstrumentedKey, "true"));
+ System.getProperty(preinstrumentedKey, "true"))
AndroidSdk.ALL_SDKS.each { androidSdk ->
String coordinates =
usePreinstrumentedJars ?
- androidSdk.preinstrumentedCoordinates : androidSdk.coordinates;
+ androidSdk.preinstrumentedCoordinates : androidSdk.coordinates
def config =
project.configurations.create("sdk${androidSdk.apiLevel}")
project.dependencies.add(
diff --git a/buildSrc/src/main/groovy/ShadowsPlugin.groovy b/buildSrc/src/main/groovy/ShadowsPlugin.groovy
index 27aa14fa4..a0563ee85 100644
--- a/buildSrc/src/main/groovy/ShadowsPlugin.groovy
+++ b/buildSrc/src/main/groovy/ShadowsPlugin.groovy
@@ -16,37 +16,40 @@ class ShadowsPlugin implements Plugin<Project> {
annotationProcessor project.project(":processor")
}
- def compileJavaTask = project.tasks["compileJava"]
-
// write generated Java into its own dir... see https://github.com/gradle/gradle/issues/4956
- def generatedSrcRelPath = 'build/generated/src/apt/main'
- def generatedSrcDir = project.file(generatedSrcRelPath)
-
- project.sourceSets.main.java { srcDir generatedSrcRelPath }
- project.mkdir(generatedSrcDir)
- compileJavaTask.options.annotationProcessorGeneratedSourcesDirectory = generatedSrcDir
- compileJavaTask.outputs.dir(generatedSrcDir)
-
- compileJavaTask.doFirst {
- options.compilerArgs.add("-Aorg.robolectric.annotation.processing.jsonDocsEnabled=true")
- options.compilerArgs.add("-Aorg.robolectric.annotation.processing.jsonDocsDir=${project.buildDir}/docs/json")
- options.compilerArgs.add("-Aorg.robolectric.annotation.processing.shadowPackage=${project.shadows.packageName}")
- options.compilerArgs.add("-Aorg.robolectric.annotation.processing.sdkCheckMode=${project.shadows.sdkCheckMode}")
- options.compilerArgs.add("-Aorg.robolectric.annotation.processing.sdks=${project.rootProject.buildDir}/sdks.txt")
+ def generatedSrcDir = project.file("build/generated/src/apt/main")
+
+ project.tasks.named("compileJava").configure { task ->
+ task.options.annotationProcessorGeneratedSourcesDirectory = generatedSrcDir
+
+ task.doFirst {
+ options.compilerArgs.add("-Aorg.robolectric.annotation.processing.jsonDocsEnabled=true")
+ options.compilerArgs.add("-Aorg.robolectric.annotation.processing.jsonDocsDir=${project.layout.buildDirectory.get().asFile}/docs/json")
+ options.compilerArgs.add("-Aorg.robolectric.annotation.processing.shadowPackage=${project.shadows.packageName}")
+ options.compilerArgs.add("-Aorg.robolectric.annotation.processing.sdkCheckMode=${project.shadows.sdkCheckMode}")
+ options.compilerArgs.add("-Aorg.robolectric.annotation.processing.sdks=${project.rootProject.layout.buildDirectory.get().asFile}/sdks.txt")
+ }
}
// include generated sources in javadoc jar
- project.tasks['javadoc'].source(generatedSrcDir)
+ project.tasks.named("javadoc").configure { task ->
+ task.source(generatedSrcDir)
+ }
// verify that we have the apt-generated files in our javadoc and sources jars
- project.tasks['javadocJar'].doLast { task ->
- def shadowPackageNameDir = project.shadows.packageName.replaceAll(/\./, '/')
- checkForFile(task.archivePath, "${shadowPackageNameDir}/Shadows.html")
+ project.tasks.named("javadocJar").configure { task ->
+ task.doLast {
+ def shadowPackageNameDir = project.shadows.packageName.replaceAll(/\./, '/')
+ checkForFile(task.archivePath, "${shadowPackageNameDir}/Shadows.html")
+ }
}
- project.tasks['sourcesJar'].doLast { task ->
- def shadowPackageNameDir = project.shadows.packageName.replaceAll(/\./, '/')
- checkForFile(task.archivePath, "${shadowPackageNameDir}/Shadows.java")
+ project.tasks.named("sourcesJar").configure { task ->
+ task.from(generatedSrcDir)
+ task.doLast {
+ def shadowPackageNameDir = project.shadows.packageName.replaceAll(/\./, '/')
+ checkForFile(task.archivePath, "${shadowPackageNameDir}/Shadows.java")
+ }
}
project.rootProject.configAnnotationProcessing += project
@@ -58,7 +61,7 @@ class ShadowsPlugin implements Plugin<Project> {
*
* See https://discuss.gradle.org/t/gradle-not-compiles-with-solder-tooling-jar/7583/20
*/
- project.tasks.withType(JavaCompile) { options.fork = true }
+ project.tasks.withType(JavaCompile).configureEach { options.fork = true }
}
static class ShadowsPluginExtension {
diff --git a/buildSrc/src/main/groovy/org/robolectric/gradle/AarDepsPlugin.java b/buildSrc/src/main/groovy/org/robolectric/gradle/AarDepsPlugin.java
index 7b61f7540..9322d6cd2 100644
--- a/buildSrc/src/main/groovy/org/robolectric/gradle/AarDepsPlugin.java
+++ b/buildSrc/src/main/groovy/org/robolectric/gradle/AarDepsPlugin.java
@@ -57,9 +57,9 @@ public class AarDepsPlugin implements Plugin<Project> {
// incremental compile breaks (run `gradlew -i classes` twice to see impact):
t ->
t.doFirst(
- new Action<Task>() {
+ new Action<>() {
@Override
- public void execute(Task task) {
+ public void execute(@NotNull Task task) {
List<File> aarFiles = AarDepsPlugin.this.findAarFiles(t.getClasspath());
if (!aarFiles.isEmpty()) {
throw new IllegalStateException(
@@ -91,8 +91,9 @@ public class AarDepsPlugin implements Plugin<Project> {
super.transform(
new TransformOutputs() {
// This is the one that ExtractAarTransform calls.
+ @NotNull
@Override
- public File dir(Object o) {
+ public File dir(@NotNull Object o) {
// ExtractAarTransform needs a place to extract the AAR. We don't really need to
// register this as an output, but it'd be tricky to avoid it.
File dir = outputs.dir(o);
@@ -105,8 +106,9 @@ public class AarDepsPlugin implements Plugin<Project> {
return outputs.dir(o);
}
+ @NotNull
@Override
- public File file(Object o) {
+ public File file(@NotNull Object o) {
throw new IllegalStateException("shouldn't be called");
}
});
diff --git a/buildSrc/src/main/groovy/org/robolectric/gradle/AndroidProjectConfigPlugin.groovy b/buildSrc/src/main/groovy/org/robolectric/gradle/AndroidProjectConfigPlugin.groovy
index 40442bfdc..18249f5b1 100644
--- a/buildSrc/src/main/groovy/org/robolectric/gradle/AndroidProjectConfigPlugin.groovy
+++ b/buildSrc/src/main/groovy/org/robolectric/gradle/AndroidProjectConfigPlugin.groovy
@@ -4,9 +4,9 @@ import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.tasks.testing.Test
-public class AndroidProjectConfigPlugin implements Plugin<Project> {
+class AndroidProjectConfigPlugin implements Plugin<Project> {
@Override
- public void apply(Project project) {
+ void apply(Project project) {
project.android.testOptions.unitTests.all {
// TODO: DRY up code with RoboJavaModulePlugin...
testLogging {
@@ -26,8 +26,8 @@ public class AndroidProjectConfigPlugin implements Plugin<Project> {
}
def forwardedSystemProperties = System.properties
- .findAll { k,v -> k.startsWith("robolectric.") }
- .collect { k,v -> "-D$k=$v" }
+ .findAll { k, v -> k.startsWith("robolectric.") }
+ .collect { k, v -> "-D$k=$v" }
jvmArgs = forwardedSystemProperties
jvmArgs += [
'--add-opens=java.base/java.lang=ALL-UNNAMED',
@@ -51,8 +51,8 @@ public class AndroidProjectConfigPlugin implements Plugin<Project> {
}
project.task('provideBuildClasspath', type: ProvideBuildClasspathTask) {
- File outDir = new File(project.buildDir, "generated/robolectric")
- outFile = new File(outDir, 'robolectric-deps.properties')
+ File outDir = project.layout.buildDirectory.dir("generated/robolectric").get().asFile
+ outFile = new File(outDir, "robolectric-deps.properties")
project.android.sourceSets['test'].resources.srcDir(outDir)
}
@@ -66,7 +66,7 @@ public class AndroidProjectConfigPlugin implements Plugin<Project> {
}
// Only run tests in the debug variant. This is to avoid running tests twice when `./gradlew test` is run at the top-level.
- project.tasks.withType(Test) {
+ project.tasks.withType(Test).configureEach {
onlyIf { variantName.toLowerCase().contains('debug') }
}
}
diff --git a/buildSrc/src/main/groovy/org/robolectric/gradle/RoboJavaModulePlugin.groovy b/buildSrc/src/main/groovy/org/robolectric/gradle/RoboJavaModulePlugin.groovy
index d48f89e1c..38f704159 100644
--- a/buildSrc/src/main/groovy/org/robolectric/gradle/RoboJavaModulePlugin.groovy
+++ b/buildSrc/src/main/groovy/org/robolectric/gradle/RoboJavaModulePlugin.groovy
@@ -11,11 +11,11 @@ class RoboJavaModulePlugin implements Plugin<Project> {
def skipErrorprone = System.getenv('SKIP_ERRORPRONE') == "true"
if (!skipErrorprone) {
- apply plugin: "net.ltgt.errorprone"
- project.dependencies {
- errorprone(libs.error.prone.core)
- errorproneJavac(libs.error.prone.javac)
- }
+ apply plugin: "net.ltgt.errorprone"
+ project.dependencies {
+ errorprone(libs.error.prone.core)
+ errorproneJavac(libs.error.prone.javac)
+ }
}
apply plugin: AarDepsPlugin
@@ -23,7 +23,7 @@ class RoboJavaModulePlugin implements Plugin<Project> {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
- tasks.withType(JavaCompile) { task ->
+ tasks.withType(JavaCompile).configureEach { task ->
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
@@ -68,8 +68,8 @@ class RoboJavaModulePlugin implements Plugin<Project> {
}
def forwardedSystemProperties = System.properties
- .findAll { k,v -> k.startsWith("robolectric.") }
- .collect { k,v -> "-D$k=$v" }
+ .findAll { k, v -> k.startsWith("robolectric.") }
+ .collect { k, v -> "-D$k=$v" }
jvmArgs = forwardedSystemProperties
jvmArgs += [
'--add-opens=java.base/java.lang=ALL-UNNAMED',
diff --git a/buildSrc/src/main/groovy/org/robolectric/gradle/SpotlessPlugin.groovy b/buildSrc/src/main/groovy/org/robolectric/gradle/SpotlessPlugin.groovy
new file mode 100644
index 000000000..8622fd2a0
--- /dev/null
+++ b/buildSrc/src/main/groovy/org/robolectric/gradle/SpotlessPlugin.groovy
@@ -0,0 +1,27 @@
+package org.robolectric.gradle
+
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+
+class SpotlessPlugin implements Plugin<Project> {
+ void apply(Project project) {
+ project.getPlugins().apply('com.diffplug.spotless')
+
+ project.spotless {
+ kotlin {
+ // Add configurations for Kotlin files
+ target '**/*.kt'
+ ktfmt('0.42').googleStyle()
+ }
+ groovy {
+ // Add configurations for Groovy files
+ target("**/*.groovy")
+ }
+ groovyGradle {
+ // Add configurations for Groovy Gradle files
+ target('*.gradle', "**/*.gradle")
+ }
+
+ }
+ }
+} \ No newline at end of file
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index c91c590fa..52a90b6aa 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -2,7 +2,7 @@
robolectric-nativeruntime-dist-compat = "1.0.9"
# https://developer.android.com/studio/releases
-android-gradle = "8.2.2"
+android-gradle = "8.3.1"
# https://github.com/google/conscrypt/tags
conscrypt = "2.5.2"
@@ -27,23 +27,23 @@ error-prone-javac = "9+181-r4173-1"
error-prone-gradle = "3.1.0"
# https://kotlinlang.org/docs/releases.html#release-details
-kotlin = "1.9.22"
+kotlin = "1.9.23"
# https://github.com/Kotlin/kotlinx.coroutines/releases/
-kotlinx-coroutines = '1.7.3'
+kotlinx-coroutines = '1.8.0'
# https://github.com/diffplug/spotless/blob/main/plugin-gradle/CHANGES.md
spotless-gradle = "6.25.0"
# https://detekt.dev/changelog
-detekt-gradle = "1.23.5"
+detekt-gradle = "1.23.6"
# https://hc.apache.org/news.html
apache-http-core = "4.0.1"
apache-http-client = "4.0.3"
# https://asm.ow2.io/versions.html
-asm = "9.6"
+asm = "9.7"
# https://github.com/google/auto/releases
auto-common = "1.2.2"
@@ -80,7 +80,7 @@ jetbrains-annotations = "24.1.0"
junit4 = "4.13.2"
# https://github.com/google/libphonenumber/releases
-libphonenumber = "8.13.30"
+libphonenumber = "8.13.33"
# https://github.com/mockito/mockito/releases
mockito = "4.11.0"
@@ -89,7 +89,7 @@ mockito = "4.11.0"
mockk = "1.13.7"
# https://github.com/takahirom/roborazzi/releases
-roborazzi = "1.9.0"
+roborazzi = "1.11.0"
# https://square.github.io/okhttp/changelogs/changelog/
okhttp = "4.12.0"
@@ -121,7 +121,7 @@ androidx-test-services = "1.4.2"
# for shadows/playservices/build.gradle
androidx-fragment-for-shadows = "1.2.0"
-play-services-base-for-shadows = "8.4.0"
+play-services-for-shadows = "17.0.0"
# https://developers.google.com/android/guides/releases
play-services-basement = "18.0.1"
@@ -236,14 +236,15 @@ androidx-test-ext-junit = { module = "androidx.test.ext:junit", version.ref = "a
androidx-test-ext-truth = { module = "androidx.test.ext:truth", version.ref = "androidx-test-ext-truth" }
androidx-fragment-for-shadows = { module = "androidx.fragment:fragment", version.ref = "androidx-fragment-for-shadows" }
-play-services-base-for-shadows = { module = "com.google.android.gms:play-services-base", version.ref = "play-services-base-for-shadows" }
-play-services-basement-for-shadows = { module = "com.google.android.gms:play-services-basement", version.ref = "play-services-base-for-shadows" }
+play-services-auth-for-shadows = { module = "com.google.android.gms:play-services-auth", version.ref = "play-services-for-shadows" }
+play-services-base-for-shadows = { module = "com.google.android.gms:play-services-base", version.ref = "play-services-for-shadows" }
+play-services-basement-for-shadows = { module = "com.google.android.gms:play-services-basement", version.ref = "play-services-for-shadows" }
play-services-basement = { module = "com.google.android.gms:play-services-basement", version.ref = "play-services-basement" }
[bundles]
-play-services-base-for-shadows = ["androidx-fragment-for-shadows", "play-services-base-for-shadows", "play-services-basement-for-shadows"]
-powermock = ["powermock-module-junit4", "powermock-module-junit4-rule", "powermock-api-mockito2", "powermock-classloading-xstream"]
-sqlite4java-native = ["sqlite4java-osx", "sqlite4java-linux-amd64", "sqlite4java-win32-x64", "sqlite4java-linux-i386", "sqlite4java-win32-x86"]
+play-services-for-shadows = [ "androidx-fragment-for-shadows", "play-services-auth-for-shadows", "play-services-base-for-shadows", "play-services-basement-for-shadows" ]
+powermock = [ "powermock-module-junit4", "powermock-module-junit4-rule", "powermock-api-mockito2", "powermock-classloading-xstream" ]
+sqlite4java-native = [ "sqlite4java-osx", "sqlite4java-linux-amd64", "sqlite4java-win32-x64", "sqlite4java-linux-i386", "sqlite4java-win32-x86" ]
[plugins]
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
index d64cd4917..e6441136f 100644
--- a/gradle/wrapper/gradle-wrapper.jar
+++ b/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index a80b22ce5..b82aa23a4 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
diff --git a/integration_tests/agp/src/main/AndroidManifest.xml b/integration_tests/agp/src/main/AndroidManifest.xml
index 4e2dd6d7b..e37688119 100644
--- a/integration_tests/agp/src/main/AndroidManifest.xml
+++ b/integration_tests/agp/src/main/AndroidManifest.xml
@@ -1,5 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="org.robolectric.integrationtests.agp">
+<manifest>
<application />
</manifest>
diff --git a/integration_tests/agp/testsupport/src/main/AndroidManifest.xml b/integration_tests/agp/testsupport/src/main/AndroidManifest.xml
index 1478552b6..ec0ff6152 100644
--- a/integration_tests/agp/testsupport/src/main/AndroidManifest.xml
+++ b/integration_tests/agp/testsupport/src/main/AndroidManifest.xml
@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="org.robolectric.integrationtests.agp.testsupport">
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application>
<activity android:name="TestActivity"/>
</application>
diff --git a/integration_tests/compat-target28/src/main/AndroidManifest.xml b/integration_tests/compat-target28/src/main/AndroidManifest.xml
index 9419a324e..b4251821a 100644
--- a/integration_tests/compat-target28/src/main/AndroidManifest.xml
+++ b/integration_tests/compat-target28/src/main/AndroidManifest.xml
@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:tools="http://schemas.android.com/tools"
- package="org.robolectric.integrationtests.compattarget28"
xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:appComponentFactory="org.robolectric.integrationtests.compattarget28.TestAppComponentFactory"
diff --git a/integration_tests/ctesque/src/sharedTest/java/android/view/KeyCharacterMapTest.java b/integration_tests/ctesque/src/sharedTest/java/android/view/KeyCharacterMapTest.java
index aadad99c4..20e84a22c 100644
--- a/integration_tests/ctesque/src/sharedTest/java/android/view/KeyCharacterMapTest.java
+++ b/integration_tests/ctesque/src/sharedTest/java/android/view/KeyCharacterMapTest.java
@@ -86,6 +86,9 @@ public final class KeyCharacterMapTest {
// Just assert that we got something back, there are many ways to return correct KeyEvents for
// this sequence.
assertThat(keyCharacterMap.getEvents("Test".toCharArray())).isNotEmpty();
+
+ char c = 'a';
+ keyCharacterMap.getEvents(new char[] {c});
}
@Test
diff --git a/integration_tests/ctesque/src/sharedTest/java/android/view/KeyEventTest.java b/integration_tests/ctesque/src/sharedTest/java/android/view/KeyEventTest.java
new file mode 100644
index 000000000..6772cb7e1
--- /dev/null
+++ b/integration_tests/ctesque/src/sharedTest/java/android/view/KeyEventTest.java
@@ -0,0 +1,61 @@
+package android.view;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
+
+import android.os.Build.VERSION;
+import android.os.Build.VERSION_CODES;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.internal.DoNotInstrument;
+
+/**
+ * Test {@link KeyEventTest}.
+ *
+ * <p>Inspired from Android cts/tests/tests/view/src/android/view/cts/KeyEventTest.java
+ */
+@DoNotInstrument
+@RunWith(AndroidJUnit4.class)
+public final class KeyEventTest {
+
+ @Test
+ public void testKeyCodeFromString() {
+ assertEquals(KeyEvent.KEYCODE_A, KeyEvent.keyCodeFromString("KEYCODE_A"));
+ assertEquals(
+ KeyEvent.KEYCODE_A, KeyEvent.keyCodeFromString(Integer.toString(KeyEvent.KEYCODE_A)));
+ assertEquals(KeyEvent.KEYCODE_UNKNOWN, KeyEvent.keyCodeFromString("keycode_a"));
+ assertEquals(KeyEvent.KEYCODE_UNKNOWN, KeyEvent.keyCodeFromString("a"));
+ assertEquals(0, KeyEvent.keyCodeFromString("0"));
+ assertEquals(1, KeyEvent.keyCodeFromString("1"));
+ assertEquals(KeyEvent.KEYCODE_HOME, KeyEvent.keyCodeFromString("3"));
+ assertEquals(
+ KeyEvent.KEYCODE_POWER,
+ KeyEvent.keyCodeFromString(Integer.toString(KeyEvent.KEYCODE_POWER)));
+ assertEquals(
+ KeyEvent.KEYCODE_MENU, KeyEvent.keyCodeFromString(Integer.toString(KeyEvent.KEYCODE_MENU)));
+ assertEquals(KeyEvent.KEYCODE_UNKNOWN, KeyEvent.keyCodeFromString("back"));
+
+ assertEquals(
+ KeyEvent.KEYCODE_UNKNOWN, KeyEvent.keyCodeFromString("KEYCODE_NOT_A_REAL_KEYCODE"));
+ assertEquals(KeyEvent.KEYCODE_UNKNOWN, KeyEvent.keyCodeFromString("NOT_A_REAL_KEYCODE"));
+ assertEquals(KeyEvent.KEYCODE_UNKNOWN, KeyEvent.keyCodeFromString("KEYCODE"));
+ assertEquals(KeyEvent.KEYCODE_UNKNOWN, KeyEvent.keyCodeFromString("KEYCODE_"));
+ assertEquals(KeyEvent.KEYCODE_UNKNOWN, KeyEvent.keyCodeFromString(""));
+ assertEquals(
+ KeyEvent.getMaxKeyCode(),
+ KeyEvent.keyCodeFromString(Integer.toString(KeyEvent.getMaxKeyCode())));
+ }
+
+ /**
+ * Verify the "starting in {@link android.os.Build.VERSION_CODES#Q} the prefix "KEYCODE_" is
+ * optional." statement in {@link KeyEvent#keyCodeFromString(String)} reference docs.
+ */
+ @Test
+ public void testKeyCodeFromString_prefixOptionalFromQ() {
+ assumeTrue(VERSION.SDK_INT >= VERSION_CODES.Q);
+ assertEquals(KeyEvent.KEYCODE_A, KeyEvent.keyCodeFromString("A"));
+
+ assertEquals(KeyEvent.KEYCODE_POWER, KeyEvent.keyCodeFromString("POWER"));
+ }
+}
diff --git a/integration_tests/dependency-on-stubs/src/test/java/org/robolectric/ShadowLogResolutionTest.java b/integration_tests/dependency-on-stubs/src/test/java/org/robolectric/ShadowLogResolutionTest.java
new file mode 100644
index 000000000..0324e81c1
--- /dev/null
+++ b/integration_tests/dependency-on-stubs/src/test/java/org/robolectric/ShadowLogResolutionTest.java
@@ -0,0 +1,22 @@
+package org.robolectric;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.robolectric.shadows.ShadowLog;
+
+/**
+ * This test attempts to reference ShadowLow from outside of the context of a Robolectric
+ * environment. ShadowLog contained reflector interfaces that referenced `@hide` classes (e.g.
+ * TerribleFailure), which cannot be referenced outside of a Robolectric ClassLoader.
+ *
+ * @see <a href="https://github.com/robolectric/robolectric/issues/8957">Issue 8957 </a> for more
+ * details.
+ */
+@RunWith(JUnit4.class)
+public class ShadowLogResolutionTest {
+ @Test
+ public void reference_shadowLog_outsideRobolectric() {
+ ShadowLog.stream = System.out;
+ }
+}
diff --git a/integration_tests/jacoco-offline/build.gradle b/integration_tests/jacoco-offline/build.gradle
index ea668f70a..423a62395 100644
--- a/integration_tests/jacoco-offline/build.gradle
+++ b/integration_tests/jacoco-offline/build.gradle
@@ -27,67 +27,68 @@ def unitTestTaskName = "test"
def compileSourceTaskName = "classes"
-def javaDirPath = "${buildDir.path}/classes/java/main"
+def javaDir = layout.buildDirectory.dir("classes/java/main").get().asFile
-def kotlinDirPath = "${buildDir.path}/classes/kotlin/main"
+def kotlinDir = layout.buildDirectory.dir("classes/kotlin/main").get().asFile
-def jacocoInstrumentedClassesOutputDirPath = "${buildDir.path}/$jacocoVersion/classes/java/classes-instrumented"
+def jacocoInstrumentedClassesOutputDir = layout.buildDirectory.dir("$jacocoVersion/classes/java/classes-instrumented").get().asFile
// make sure it's evaluated after the AGP evaluation.
afterEvaluate {
- tasks[compileSourceTaskName].doLast {
- println "[JaCoCo]:Generating JaCoCo instrumented classes for the build."
+ tasks.named(compileSourceTaskName).configure { task ->
+ task.doLast {
+ println "[JaCoCo]:Generating JaCoCo instrumented classes for the build."
- def jacocoInstrumentOutputDirPathFile = new File(jacocoInstrumentedClassesOutputDirPath)
- if (jacocoInstrumentOutputDirPathFile.exists()) {
- println "[JaCoCo]:Classes had been instrumented."
- return
- }
+ if (jacocoInstrumentedClassesOutputDir.exists()) {
+ println "[JaCoCo]:Classes had been instrumented."
+ return
+ }
- ant.taskdef(name: 'instrument',
- classname: 'org.jacoco.ant.InstrumentTask',
- classpath: configurations.jacocoAnt.asPath)
-
- def classesDirPathFile = new File(javaDirPath)
- if (classesDirPathFile.exists()) {
- ant.instrument(destdir: jacocoInstrumentedClassesOutputDirPath) {
- fileset(
- dir: javaDirPath,
- excludes: []
- )
+ ant.taskdef(name: 'instrument',
+ classname: 'org.jacoco.ant.InstrumentTask',
+ classpath: configurations.jacocoAnt.asPath)
+
+ if (javaDir.exists()) {
+ ant.instrument(destdir: jacocoInstrumentedClassesOutputDir.path) {
+ fileset(
+ dir: javaDir.path,
+ excludes: []
+ )
+ }
+ } else {
+ println "Classes directory with path: $javaDir does not existed."
}
- } else {
- println "Classes directory with path: " + classesDirPathFile + " was not existed."
- }
- def classesDirPathFileKotlin = new File(kotlinDirPath)
- if (classesDirPathFileKotlin.exists()) {
- ant.instrument(destdir: jacocoInstrumentedClassesOutputDirPath) {
- fileset(
- dir: kotlinDirPath,
- excludes: []
- )
+ if (kotlinDir.exists()) {
+ ant.instrument(destdir: jacocoInstrumentedClassesOutputDir.path) {
+ fileset(
+ dir: kotlinDir.path,
+ excludes: []
+ )
+ }
+ } else {
+ println "Classes directory with path: $kotlinDir does not existed."
}
- } else {
- println "Classes directory with path: " + classesDirPathFileKotlin + " was not existed."
}
}
- def executionDataFilePath = "${buildDir.path}/jacoco/${unitTestTaskName}.exec"
+ def executionDataFilePath = layout.buildDirectory.dir("jacoco").get().file("${unitTestTaskName}.exec").getAsFile().path
// put JaCoCo instrumented classes and JaCoCoRuntime to the beginning of the JVM classpath.
- tasks["${unitTestTaskName}"].doFirst {
- jacoco {
- // disable JaCoCo on-the-fly from Gradle JaCoCo plugin.
- enabled = false
- }
+ tasks.named(unitTestTaskName).configure { task ->
+ task.doFirst {
+ jacoco {
+ // disable JaCoCo on-the-fly from Gradle JaCoCo plugin.
+ enabled = false
+ }
- println "[JaCoCo]:Modifying classpath of tests JVM."
+ println "[JaCoCo]:Modifying classpath of tests JVM."
- systemProperty 'jacoco-agent.destfile', executionDataFilePath
+ systemProperty 'jacoco-agent.destfile', executionDataFilePath
- classpath = files(jacocoInstrumentedClassesOutputDirPath) + classpath + configurations.jacocoRuntime
+ classpath = files(jacocoInstrumentedClassesOutputDir.path) + classpath + configurations.jacocoRuntime
- println "Final test JVM classpath is ${classpath.getAsPath()}"
+ println "Final test JVM classpath is ${classpath.getAsPath()}"
+ }
}
}
diff --git a/integration_tests/kotlin/build.gradle b/integration_tests/kotlin/build.gradle
index 550f08a45..c95d64f3f 100644
--- a/integration_tests/kotlin/build.gradle
+++ b/integration_tests/kotlin/build.gradle
@@ -1,17 +1,7 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.robolectric.gradle.RoboJavaModulePlugin
-
apply plugin: RoboJavaModulePlugin
apply plugin: 'kotlin'
-apply plugin: "com.diffplug.spotless"
-
-spotless {
- kotlin {
- target '**/*.kt'
- ktfmt('0.42').googleStyle()
- }
-}
-
compileKotlin {
compilerOptions.jvmTarget = JvmTarget.JVM_1_8
}
diff --git a/integration_tests/memoryleaks/src/main/AndroidManifest.xml b/integration_tests/memoryleaks/src/main/AndroidManifest.xml
index 5cafa7793..9515bdd60 100644
--- a/integration_tests/memoryleaks/src/main/AndroidManifest.xml
+++ b/integration_tests/memoryleaks/src/main/AndroidManifest.xml
@@ -2,9 +2,6 @@
<!--
Manifest for androidx memoryleaks test module
-->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="org.robolectric.integrationtests.memoryleaks">
-
- <application>
- </application>
+<manifest>
+ <application />
</manifest>
diff --git a/integration_tests/nativegraphics/src/main/AndroidManifest.xml b/integration_tests/nativegraphics/src/main/AndroidManifest.xml
index 279252449..7a3694f72 100644
--- a/integration_tests/nativegraphics/src/main/AndroidManifest.xml
+++ b/integration_tests/nativegraphics/src/main/AndroidManifest.xml
@@ -1,9 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Manifest for native graphics tests -->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="org.robolectric.integrationtests.nativegraphics">
- <uses-sdk
- android:minSdkVersion="26"
- android:targetSdkVersion="34" />
+<manifest>
<application />
</manifest>
diff --git a/integration_tests/roborazzi/build.gradle b/integration_tests/roborazzi/build.gradle
index 7c7000c5b..034a69bba 100644
--- a/integration_tests/roborazzi/build.gradle
+++ b/integration_tests/roborazzi/build.gradle
@@ -1,18 +1,8 @@
import org.robolectric.gradle.AndroidProjectConfigPlugin
-
apply plugin: 'com.android.library'
apply plugin: AndroidProjectConfigPlugin
apply plugin: 'kotlin-android'
-apply plugin: "com.diffplug.spotless"
apply plugin: "io.github.takahirom.roborazzi"
-
-spotless {
- kotlin {
- target '**/*.kt'
- ktfmt('0.42').googleStyle()
- }
-}
-
android {
compileSdk 34
namespace 'org.robolectric.integration.roborazzi'
@@ -54,7 +44,6 @@ android {
}
}
}
-
dependencies {
api project(":robolectric")
testImplementation libs.androidx.test.core
diff --git a/integration_tests/room/build.gradle b/integration_tests/room/build.gradle
index 4a12143be..52831db58 100644
--- a/integration_tests/room/build.gradle
+++ b/integration_tests/room/build.gradle
@@ -30,7 +30,6 @@ dependencies {
testImplementation project(":robolectric")
testImplementation libs.junit4
testImplementation libs.guava.testlib
- testImplementation libs.guava.testlib
testImplementation libs.truth
implementation libs.androidx.room.runtime
annotationProcessor libs.androidx.room.compiler
diff --git a/integration_tests/room/src/main/AndroidManifest.xml b/integration_tests/room/src/main/AndroidManifest.xml
index 3707a8dcb..283a0587e 100644
--- a/integration_tests/room/src/main/AndroidManifest.xml
+++ b/integration_tests/room/src/main/AndroidManifest.xml
@@ -1,10 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- Manifest for androidx memoryleaks test module
+ Manifest for androidx room test module
-->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="org.robolectric.integrationtests.room">
-
- <application>
- </application>
+<manifest>
+ <application />
</manifest>
diff --git a/integration_tests/sparsearray/build.gradle b/integration_tests/sparsearray/build.gradle
index d408f0de7..6353b6b91 100644
--- a/integration_tests/sparsearray/build.gradle
+++ b/integration_tests/sparsearray/build.gradle
@@ -1,18 +1,8 @@
import org.robolectric.gradle.AndroidProjectConfigPlugin
-
apply plugin: 'com.android.library'
apply plugin: AndroidProjectConfigPlugin
apply plugin: 'kotlin-android'
-apply plugin: "com.diffplug.spotless"
apply plugin: "io.gitlab.arturbosch.detekt"
-
-spotless {
- kotlin {
- target '**/*.kt'
- ktfmt('0.42').googleStyle()
- }
-}
-
android {
compileSdk 34
namespace 'org.robolectric.sparsearray'
@@ -37,7 +27,6 @@ android {
}
}
}
-
dependencies {
compileOnly AndroidSdk.MAX_SDK.coordinates
implementation project(path: ':shadowapi', configuration: 'default')
diff --git a/integration_tests/sparsearray/src/main/AndroidManifest.xml b/integration_tests/sparsearray/src/main/AndroidManifest.xml
index 6ce6065f5..955c5548d 100644
--- a/integration_tests/sparsearray/src/main/AndroidManifest.xml
+++ b/integration_tests/sparsearray/src/main/AndroidManifest.xml
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
-<manifest package="org.robolectric.sparsearray">
+<manifest>
<application />
</manifest>
diff --git a/shadows/versioning/build.gradle b/integration_tests/versioning/build.gradle
index 68a8fb769..8ae425b51 100644
--- a/shadows/versioning/build.gradle
+++ b/integration_tests/versioning/build.gradle
@@ -9,7 +9,6 @@ configurations {
}
dependencies {
- api project(":shadowapi")
compileOnly AndroidSdk.MAX_SDK.coordinates // compile against latest Android SDK (AndroidSdk.s.coordinates) { force = true }
testImplementation project(":robolectric")
testImplementation libs.truth
diff --git a/shadows/versioning/src/test/java/org/robolectric/versioning/AndroidVersionsTest.java b/integration_tests/versioning/src/test/java/org/robolectric/versioning/AndroidVersionsTest.java
index 4666c6a0e..09b394a1a 100644
--- a/shadows/versioning/src/test/java/org/robolectric/versioning/AndroidVersionsTest.java
+++ b/integration_tests/versioning/src/test/java/org/robolectric/versioning/AndroidVersionsTest.java
@@ -2,12 +2,11 @@ package org.robolectric.versioning;
import static com.google.common.truth.Truth.assertThat;
-import android.os.Build.VERSION;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
-import org.robolectric.versioning.AndroidVersions.T;
+import org.robolectric.shadows.ShadowBuild;
/**
* Check versions information aligns with runtime information. Primarily, selected SDK with
@@ -17,11 +16,17 @@ import org.robolectric.versioning.AndroidVersions.T;
public final class AndroidVersionsTest {
@Test
- @Config(sdk = T.SDK_INT)
+ @Config(sdk = 33)
+ public void ignoreShadowBuildValues() {
+ ShadowBuild.setVersionCodename("_*&^%%&");
+ assertThat(AndroidVersions.T.SDK_INT).isEqualTo(33);
+ assertThat(AndroidVersions.CURRENT.getShortCode()).isEqualTo("T");
+ }
+
+ @Test
+ @Config(sdk = 33)
public void testStandardInitializationT() {
- assertThat(VERSION.SDK_INT).isEqualTo(33);
- assertThat(VERSION.RELEASE).isEqualTo("13");
- assertThat(VERSION.CODENAME).isEqualTo("REL");
+ assertThat(AndroidVersions.T.SDK_INT).isEqualTo(33);
assertThat(AndroidVersions.T.SHORT_CODE).isEqualTo("T");
assertThat(new AndroidVersions.T().getVersion()).isEqualTo("13.0");
assertThat(AndroidVersions.CURRENT.getShortCode()).isEqualTo("T");
@@ -30,9 +35,7 @@ public final class AndroidVersionsTest {
@Test
@Config(sdk = 32)
public void testStandardInitializationSv2() {
- assertThat(VERSION.SDK_INT).isEqualTo(32);
- assertThat(VERSION.RELEASE).isEqualTo("12");
- assertThat(VERSION.CODENAME).isEqualTo("REL");
+ assertThat(AndroidVersions.Sv2.SDK_INT).isEqualTo(32);
assertThat(AndroidVersions.Sv2.SHORT_CODE).isEqualTo("Sv2");
assertThat(new AndroidVersions.Sv2().getVersion()).isEqualTo("12.1");
assertThat(AndroidVersions.CURRENT.getShortCode()).isEqualTo("Sv2");
@@ -41,9 +44,7 @@ public final class AndroidVersionsTest {
@Test
@Config(sdk = 31)
public void testStandardInitializationS() {
- assertThat(VERSION.SDK_INT).isEqualTo(31);
- assertThat(VERSION.RELEASE).isEqualTo("12");
- assertThat(VERSION.CODENAME).isEqualTo("REL");
+ assertThat(AndroidVersions.S.SDK_INT).isEqualTo(31);
assertThat(AndroidVersions.S.SHORT_CODE).isEqualTo("S");
assertThat(new AndroidVersions.S().getVersion()).isEqualTo("12.0");
assertThat(AndroidVersions.CURRENT.getShortCode()).isEqualTo("S");
@@ -52,9 +53,7 @@ public final class AndroidVersionsTest {
@Test
@Config(sdk = 30)
public void testStandardInitializationR() {
- assertThat(VERSION.SDK_INT).isEqualTo(30);
- assertThat(VERSION.RELEASE).isEqualTo("11");
- assertThat(VERSION.CODENAME).isEqualTo("REL");
+ assertThat(AndroidVersions.R.SDK_INT).isEqualTo(30);
assertThat(AndroidVersions.R.SHORT_CODE).isEqualTo("R");
assertThat(new AndroidVersions.R().getVersion()).isEqualTo("11.0");
assertThat(AndroidVersions.CURRENT.getShortCode()).isEqualTo("R");
@@ -63,9 +62,7 @@ public final class AndroidVersionsTest {
@Test
@Config(sdk = 29)
public void testStandardInitializationQ() {
- assertThat(VERSION.SDK_INT).isEqualTo(29);
- assertThat(VERSION.RELEASE).isEqualTo("10");
- assertThat(VERSION.CODENAME).isEqualTo("REL");
+ assertThat(AndroidVersions.Q.SDK_INT).isEqualTo(29);
assertThat(AndroidVersions.Q.SHORT_CODE).isEqualTo("Q");
assertThat(new AndroidVersions.Q().getVersion()).isEqualTo("10.0");
assertThat(AndroidVersions.CURRENT.getShortCode()).isEqualTo("Q");
@@ -74,9 +71,7 @@ public final class AndroidVersionsTest {
@Test
@Config(sdk = 28)
public void testStandardInitializationP() {
- assertThat(VERSION.SDK_INT).isEqualTo(28);
- assertThat(VERSION.RELEASE).isEqualTo("9");
- assertThat(VERSION.CODENAME).isEqualTo("REL");
+ assertThat(AndroidVersions.P.SDK_INT).isEqualTo(28);
assertThat(AndroidVersions.P.SHORT_CODE).isEqualTo("P");
assertThat(new AndroidVersions.P().getVersion()).isEqualTo("9.0");
assertThat(AndroidVersions.CURRENT.getShortCode()).isEqualTo("P");
@@ -85,9 +80,7 @@ public final class AndroidVersionsTest {
@Test
@Config(sdk = 27)
public void testStandardInitializationOMR1() {
- assertThat(VERSION.SDK_INT).isEqualTo(27);
- assertThat(VERSION.RELEASE).isEqualTo("8.1.0");
- assertThat(VERSION.CODENAME).isEqualTo("REL");
+ assertThat(AndroidVersions.OMR1.SDK_INT).isEqualTo(27);
assertThat(AndroidVersions.OMR1.SHORT_CODE).isEqualTo("OMR1");
assertThat(new AndroidVersions.OMR1().getVersion()).isEqualTo("8.1");
assertThat(AndroidVersions.CURRENT.getShortCode()).isEqualTo("OMR1");
@@ -96,9 +89,7 @@ public final class AndroidVersionsTest {
@Test
@Config(sdk = 26)
public void testStandardInitializationO() {
- assertThat(VERSION.SDK_INT).isEqualTo(26);
- assertThat(VERSION.RELEASE).isEqualTo("8.0.0");
- assertThat(VERSION.CODENAME).isEqualTo("REL");
+ assertThat(AndroidVersions.O.SDK_INT).isEqualTo(26);
assertThat(AndroidVersions.O.SHORT_CODE).isEqualTo("O");
assertThat(new AndroidVersions.O().getVersion()).isEqualTo("8.0");
assertThat(AndroidVersions.CURRENT.getShortCode()).isEqualTo("O");
@@ -107,9 +98,7 @@ public final class AndroidVersionsTest {
@Test
@Config(sdk = 25)
public void testStandardInitializationNMR1() {
- assertThat(VERSION.SDK_INT).isEqualTo(25);
- assertThat(VERSION.RELEASE).isEqualTo("7.1");
- assertThat(VERSION.CODENAME).isEqualTo("REL");
+ assertThat(AndroidVersions.NMR1.SDK_INT).isEqualTo(25);
assertThat(AndroidVersions.NMR1.SHORT_CODE).isEqualTo("NMR1");
assertThat(new AndroidVersions.NMR1().getVersion()).isEqualTo("7.1");
assertThat(AndroidVersions.CURRENT.getShortCode()).isEqualTo("NMR1");
@@ -118,9 +107,7 @@ public final class AndroidVersionsTest {
@Test
@Config(sdk = 24)
public void testStandardInitializationN() {
- assertThat(VERSION.SDK_INT).isEqualTo(24);
- assertThat(VERSION.RELEASE).isEqualTo("7.0");
- assertThat(VERSION.CODENAME).isEqualTo("REL");
+ assertThat(AndroidVersions.N.SDK_INT).isEqualTo(24);
assertThat(AndroidVersions.N.SHORT_CODE).isEqualTo("N");
assertThat(new AndroidVersions.N().getVersion()).isEqualTo("7.0");
assertThat(AndroidVersions.CURRENT.getShortCode()).isEqualTo("N");
@@ -129,9 +116,7 @@ public final class AndroidVersionsTest {
@Test
@Config(sdk = 23)
public void testStandardInitializationM() {
- assertThat(VERSION.SDK_INT).isEqualTo(23);
- assertThat(VERSION.RELEASE).isEqualTo("6.0.1");
- assertThat(VERSION.CODENAME).isEqualTo("REL");
+ assertThat(AndroidVersions.M.SDK_INT).isEqualTo(23);
assertThat(AndroidVersions.M.SHORT_CODE).isEqualTo("M");
assertThat(new AndroidVersions.M().getVersion()).isEqualTo("6.0");
assertThat(AndroidVersions.CURRENT.getShortCode()).isEqualTo("M");
@@ -140,9 +125,7 @@ public final class AndroidVersionsTest {
@Test
@Config(sdk = 22)
public void testStandardInitializationLMR1() {
- assertThat(VERSION.SDK_INT).isEqualTo(22);
- assertThat(VERSION.RELEASE).isEqualTo("5.1.1");
- assertThat(VERSION.CODENAME).isEqualTo("REL");
+ assertThat(AndroidVersions.LMR1.SDK_INT).isEqualTo(22);
assertThat(AndroidVersions.LMR1.SHORT_CODE).isEqualTo("LMR1");
assertThat(new AndroidVersions.LMR1().getVersion()).isEqualTo("5.1");
assertThat(AndroidVersions.CURRENT.getShortCode()).isEqualTo("LMR1");
@@ -151,9 +134,7 @@ public final class AndroidVersionsTest {
@Test
@Config(sdk = 21)
public void testStandardInitializationL() {
- assertThat(VERSION.SDK_INT).isEqualTo(21);
- assertThat(VERSION.RELEASE).isEqualTo("5.0.2");
- assertThat(VERSION.CODENAME).isEqualTo("REL");
+ assertThat(AndroidVersions.L.SDK_INT).isEqualTo(21);
assertThat(AndroidVersions.L.SHORT_CODE).isEqualTo("L");
assertThat(new AndroidVersions.L().getVersion()).isEqualTo("5.0");
assertThat(AndroidVersions.CURRENT.getShortCode()).isEqualTo("L");
@@ -162,9 +143,7 @@ public final class AndroidVersionsTest {
@Test
@Config(sdk = 19)
public void testStandardInitializationK() {
- assertThat(VERSION.SDK_INT).isEqualTo(19);
- assertThat(VERSION.RELEASE).isEqualTo("4.4");
- assertThat(VERSION.CODENAME).isEqualTo("REL");
+ assertThat(AndroidVersions.K.SDK_INT).isEqualTo(19);
assertThat(AndroidVersions.K.SHORT_CODE).isEqualTo("K");
assertThat(new AndroidVersions.K().getVersion()).isEqualTo("4.4");
assertThat(AndroidVersions.CURRENT.getShortCode()).isEqualTo("K");
diff --git a/shadows/versioning/src/test/resources/AndroidManifest.xml b/integration_tests/versioning/src/test/resources/AndroidManifest.xml
index 65383ac0b..65383ac0b 100644
--- a/shadows/versioning/src/test/resources/AndroidManifest.xml
+++ b/integration_tests/versioning/src/test/resources/AndroidManifest.xml
diff --git a/preinstrumented/build.gradle b/preinstrumented/build.gradle
index 0de49e0bf..8e6481622 100644
--- a/preinstrumented/build.gradle
+++ b/preinstrumented/build.gradle
@@ -19,7 +19,6 @@ java {
dependencies {
implementation libs.guava
implementation project(":sandbox")
- implementation project(":shadows:versioning")
testImplementation libs.junit4
testImplementation libs.mockito
@@ -35,7 +34,7 @@ tasks.register('instrumentAll') {
sdksToInstrument().each { androidSdk ->
println("Instrumenting ${androidSdk.coordinates}")
def inputPath = "${androidAllMavenLocal}/${androidSdk.version}/${androidSdk.jarFileName}"
- def outputPath = "${buildDir}/${androidSdk.preinstrumentedJarFileName}"
+ def outputPath = layout.buildDirectory.file(androidSdk.preinstrumentedJarFileName).get().asFile.path
javaexec {
classpath = sourceSets.main.runtimeClasspath
@@ -66,7 +65,7 @@ if (System.getenv('PUBLISH_PREINSTRUMENTED_JARS') == "true") {
publications {
sdksToInstrument().each { androidSdk ->
"sdk${androidSdk.apiLevel}"(MavenPublication) {
- artifact "${buildDir}/${androidSdk.preinstrumentedJarFileName}"
+ artifact = layout.buildDirectory.file(androidSdk.preinstrumentedJarFileName).get().asFile.path
artifactId 'android-all-instrumented'
artifact emptySourcesJar
artifact emptyJavadocJar
@@ -120,34 +119,34 @@ if (System.getenv('PUBLISH_PREINSTRUMENTED_JARS') == "true") {
}
- // Workaround for https://github.com/gradle/gradle/issues/26132
- // For some reason, Gradle has inferred that all publishing tasks depend on all signing tasks,
- // so we must explicitly declare this here.
- afterEvaluate {
- tasks.all {
- if (name.startsWith("publishSdk")) {
- sdksToInstrument().each { androidSdk ->
- dependsOn(tasks.named("signSdk${androidSdk.apiLevel}Publication"))
+ // Workaround for https://github.com/gradle/gradle/issues/26132
+ // For some reason, Gradle has inferred that all publishing tasks depend on all signing tasks,
+ // so we must explicitly declare this here.
+ afterEvaluate {
+ tasks.configureEach {
+ if (name.startsWith("publishSdk")) {
+ sdksToInstrument().each { androidSdk ->
+ dependsOn(tasks.named("signSdk${androidSdk.apiLevel}Publication"))
+ }
+ }
}
- }
}
- }
}
static def sdksToInstrument() {
- var result = AndroidSdk.ALL_SDKS
- var preInstrumentedSdkVersions = (System.getenv('PREINSTRUMENTED_SDK_VERSIONS') ?: "")
- if (preInstrumentedSdkVersions.length() > 0) {
- var sdkFilter = preInstrumentedSdkVersions.split(",").collect { it as Integer }
- if (sdkFilter.size > 0) {
- result = result.findAll { sdkFilter.contains(it.apiLevel) }
+ var result = AndroidSdk.ALL_SDKS
+ var preInstrumentedSdkVersions = (System.getenv('PREINSTRUMENTED_SDK_VERSIONS') ?: "")
+ if (preInstrumentedSdkVersions.length() > 0) {
+ var sdkFilter = preInstrumentedSdkVersions.split(",").collect { it as Integer }
+ if (sdkFilter.size > 0) {
+ result = result.findAll { sdkFilter.contains(it.apiLevel) }
+ }
}
- }
- return result
+ return result
}
clean.doFirst {
AndroidSdk.ALL_SDKS.each { androidSdk ->
- delete "${buildDir}/${androidSdk.preinstrumentedJarFileName}"
+ delete layout.buildDirectory.file(androidSdk.preinstrumentedJarFileName)
}
}
diff --git a/processor/Android.bp b/processor/Android.bp
index 7b0772aa6..be7320178 100644
--- a/processor/Android.bp
+++ b/processor/Android.bp
@@ -12,11 +12,13 @@ package {
default_applicable_licenses: ["external_robolectric_license"],
}
-java_library_host {
+java_library {
name: "libRobolectric_processor_upstream",
+ host_supported: true,
+ device_supported: false,
srcs: ["src/main/java/**/*.java"],
java_resource_dirs: ["src/main/resources"],
- java_resources: ["sdks.txt"],
+ //java_resources: ["sdks.txt"],
use_tools_jar: true,
plugins: [
"auto_service_plugin",
diff --git a/processor/build.gradle b/processor/build.gradle
index c2f00d77d..695bc8182 100644
--- a/processor/build.gradle
+++ b/processor/build.gradle
@@ -6,10 +6,11 @@ apply plugin: RoboJavaModulePlugin
apply plugin: DeployedRoboJavaModulePlugin
class GenerateSdksFileTask extends DefaultTask {
- @OutputFile File outFile
+ @OutputFile
+ File outFile
@TaskAction
- public void writeProperties() throws Exception {
+ void writeProperties() throws Exception {
File outDir = outFile.parentFile
if (!outDir.directory) outDir.mkdirs()
outFile.withPrintWriter { out ->
@@ -26,15 +27,16 @@ class GenerateSdksFileTask extends DefaultTask {
}
task('generateSdksFile', type: GenerateSdksFileTask) {
- outFile = new File(project.rootProject.buildDir, 'sdks.txt')
+ outFile = project.rootProject.layout.buildDirectory.file('sdks.txt').get().asFile
}
-tasks['classes'].dependsOn(generateSdksFile)
+tasks.named("classes").configure { task ->
+ task.dependsOn(generateSdksFile)
+}
dependencies {
api project(":annotations")
api project(":shadowapi")
- implementation project(":shadows:versioning")
compileOnly libs.findbugs.jsr305
api libs.asm
diff --git a/processor/src/main/java/org/robolectric/annotation/processing/RobolectricProcessor.java b/processor/src/main/java/org/robolectric/annotation/processing/RobolectricProcessor.java
index f767765a4..b82622885 100644
--- a/processor/src/main/java/org/robolectric/annotation/processing/RobolectricProcessor.java
+++ b/processor/src/main/java/org/robolectric/annotation/processing/RobolectricProcessor.java
@@ -5,6 +5,7 @@ import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
@@ -27,21 +28,25 @@ import org.robolectric.annotation.processing.validator.ResetterValidator;
import org.robolectric.annotation.processing.validator.SdkStore;
import org.robolectric.annotation.processing.validator.Validator;
-/**
- * Annotation processor entry point for Robolectric annotations.
- */
+/** Annotation processor entry point for Robolectric annotations. */
@SupportedOptions({
- RobolectricProcessor.PACKAGE_OPT,
- RobolectricProcessor.SHOULD_INSTRUMENT_PKG_OPT})
+ RobolectricProcessor.PACKAGE_OPT,
+ RobolectricProcessor.SHOULD_INSTRUMENT_PKG_OPT
+})
@SupportedAnnotationTypes("org.robolectric.annotation.*")
public class RobolectricProcessor extends AbstractProcessor {
static final String PACKAGE_OPT = "org.robolectric.annotation.processing.shadowPackage";
- static final String SHOULD_INSTRUMENT_PKG_OPT =
+ static final String SHOULD_INSTRUMENT_PKG_OPT =
"org.robolectric.annotation.processing.shouldInstrumentPackage";
static final String JSON_DOCS_DIR = "org.robolectric.annotation.processing.jsonDocsDir";
static final String JSON_DOCS_ENABLED = "org.robolectric.annotation.processing.jsonDocsEnabled";
static final String SDK_CHECK_MODE = "org.robolectric.annotation.processing.sdkCheckMode";
private static final String SDKS_FILE = "org.robolectric.annotation.processing.sdks";
+
+ /** required for Android Development. */
+ private static final String VALIDATE_COMPILE_SDKS =
+ "org.robolectric.annotation.processing.validateCompileSdk";
+
private static final String PRIORITY = "org.robolectric.annotation.processing.priority";
private RobolectricModel.Builder modelBuilder;
@@ -56,24 +61,30 @@ public class RobolectricProcessor extends AbstractProcessor {
private final Map<TypeElement, Validator> elementValidators = new HashMap<>(13);
private File jsonDocsDir;
private boolean jsonDocsEnabled;
+ private boolean validateCompiledSdk;
+ private String overrideSdkLocation;
+ private int overrideSdkInt;
- /**
- * Default constructor.
- */
- public RobolectricProcessor() {
- }
+ /** Default constructor. */
+ public RobolectricProcessor() {}
/**
- * Constructor to use for testing passing options in. Only
- * necessary until compile-testing supports passing options
- * in.
+ * Constructor to use for testing passing options in. Only necessary until compile-testing
+ * supports passing options in.
*
- * @param options simulated options that would ordinarily
- * be passed in the {@link ProcessingEnvironment}.
+ * @param options simulated options that would ordinarily be passed in the {@link
+ * ProcessingEnvironment}.
*/
@VisibleForTesting
public RobolectricProcessor(Map<String, String> options) {
+ this(options, null, -1);
+ }
+
+ public RobolectricProcessor(
+ Map<String, String> options, String overrideSdkLocation, int overrideSdkInt) {
processOptions(options);
+ this.overrideSdkLocation = overrideSdkLocation;
+ this.overrideSdkInt = overrideSdkInt;
}
@Override
@@ -82,7 +93,8 @@ public class RobolectricProcessor extends AbstractProcessor {
processOptions(environment.getOptions());
modelBuilder = new RobolectricModel.Builder(environment);
- SdkStore sdkStore = new SdkStore(sdksFile);
+ SdkStore sdkStore =
+ new SdkStore(sdksFile, validateCompiledSdk, overrideSdkLocation, overrideSdkInt);
addValidator(new ImplementationValidator(modelBuilder, environment));
addValidator(new ImplementsValidator(modelBuilder, environment, sdkCheckMode, sdkStore));
@@ -132,10 +144,12 @@ public class RobolectricProcessor extends AbstractProcessor {
this.jsonDocsDir = new File(options.getOrDefault(JSON_DOCS_DIR, "build/docs/json"));
this.jsonDocsEnabled = "true".equalsIgnoreCase(options.get(JSON_DOCS_ENABLED));
this.sdkCheckMode =
- SdkCheckMode.valueOf(options.getOrDefault(SDK_CHECK_MODE, "WARN").toUpperCase());
+ SdkCheckMode.valueOf(
+ options.getOrDefault(SDK_CHECK_MODE, "WARN").toUpperCase(Locale.ROOT));
+ this.validateCompiledSdk =
+ "true".equalsIgnoreCase(options.getOrDefault(VALIDATE_COMPILE_SDKS, "false"));
this.sdksFile = getSdksFile(options, SDKS_FILE);
- this.priority =
- Integer.parseInt(options.getOrDefault(PRIORITY, "0"));
+ this.priority = Integer.parseInt(options.getOrDefault(PRIORITY, "0"));
if (this.shadowPackage == null) {
throw new IllegalArgumentException("no package specified for " + PACKAGE_OPT);
diff --git a/processor/src/main/java/org/robolectric/annotation/processing/validator/SdkStore.java b/processor/src/main/java/org/robolectric/annotation/processing/validator/SdkStore.java
index e08b3acd1..06008a57b 100644
--- a/processor/src/main/java/org/robolectric/annotation/processing/validator/SdkStore.java
+++ b/processor/src/main/java/org/robolectric/annotation/processing/validator/SdkStore.java
@@ -16,16 +16,23 @@ import java.io.InputStreamReader;
import java.net.URI;
import java.nio.charset.Charset;
import java.nio.file.Files;
+import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.TreeSet;
+import java.util.function.Supplier;
import java.util.jar.JarFile;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
+import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
@@ -39,17 +46,27 @@ import org.objectweb.asm.Type;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.MethodNode;
import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.InDevelopment;
import org.robolectric.versioning.AndroidVersionInitTools;
+import org.robolectric.versioning.AndroidVersions;
/** Encapsulates a collection of Android framework jars. */
public class SdkStore {
private final Set<Sdk> sdks = new TreeSet<>();
private boolean loaded = false;
+ private final boolean loadFromClasspath;
+ private final String overrideSdkLocation;
+ private final int overrideSdkInt;
private final String sdksFile;
- public SdkStore(String sdksFile) {
+ /** */
+ public SdkStore(
+ String sdksFile, boolean loadFromClasspath, String overrideSdkLocation, int overrideSdkInt) {
this.sdksFile = sdksFile;
+ this.loadFromClasspath = loadFromClasspath;
+ this.overrideSdkLocation = overrideSdkLocation;
+ this.overrideSdkInt = overrideSdkInt;
}
List<Sdk> sdksMatching(Implementation implementation, int classMinSdk, int classMaxSdk) {
@@ -73,7 +90,7 @@ public class SdkStore {
List<Sdk> matchingSdks = new ArrayList<>();
for (Sdk sdk : sdks) {
- Integer sdkInt = sdk.sdkInt;
+ int sdkInt = sdk.sdkRelease.getSdkInt();
if (sdkInt >= minSdk && sdkInt <= maxSdk) {
matchingSdks.add(sdk);
}
@@ -83,21 +100,90 @@ public class SdkStore {
private synchronized void loadSdksOnce() {
if (!loaded) {
- sdks.addAll(loadFromSdksFile(sdksFile));
+ sdks.addAll(
+ loadFromSources(loadFromClasspath, sdksFile, overrideSdkLocation, overrideSdkInt));
loaded = true;
}
}
- private static ImmutableList<Sdk> loadFromSdksFile(String fileName) {
- if (fileName == null || Files.notExists(Paths.get(fileName))) {
- return ImmutableList.of();
+ /**
+ * @return a list of sdk_int's to jar locations as a string, one tuple per line.
+ */
+ @Override
+ @SuppressWarnings("JdkCollectors")
+ public String toString() {
+ loadSdksOnce();
+ StringBuilder builder = new StringBuilder();
+ builder.append("SdkStore [");
+ for (Sdk sdk : sdks.stream().sorted().collect(Collectors.toList())) {
+ builder.append(" " + sdk.sdkRelease.getSdkInt() + " : " + sdk.path + "\n");
}
+ builder.append("]");
+ return builder.toString();
+ }
- try (InputStream resIn = new FileInputStream(fileName)) {
- if (resIn == null) {
- throw new RuntimeException("no such file " + fileName);
+ /**
+ * Scans the jvm properties for the command that executed it, in this command will be the
+ * classpath. <br>
+ * <br>
+ * Scans all jars on the classpath for the first one with a /build.prop on resource. This is
+ * assumed to be the sdk that the processor is running with.
+ *
+ * @return the detected sdk location.
+ */
+ private static String compilationSdkTarget() {
+ String cmd = System.getProperty("sun.java.command");
+ Pattern pattern = Pattern.compile("((-cp)|(-classpath))\\s(?<cp>[a-zA-Z-_0-9\\-\\:\\/\\.]*)");
+ Matcher matcher = pattern.matcher(cmd);
+ if (matcher.find()) {
+ String classpathString = matcher.group("cp");
+ List<String> cp = Arrays.asList(classpathString.split(":"));
+ for (String fileStr : cp) {
+ try (JarFile jarFile = new JarFile(fileStr)) {
+ ZipEntry entry = jarFile.getEntry("build.prop");
+ if (entry != null) {
+ return fileStr;
+ }
+ } catch (IOException ioe) {
+ System.out.println("Error detecting compilation SDK: " + ioe.getMessage());
+ ioe.printStackTrace();
+ }
}
+ }
+ return null;
+ }
+ /**
+ * Returns a list of sdks to process, either the compilation's classpaths sdk in a list of size
+ * one, or the list of sdks in a sdkFile.
+ *
+ * @param localSdk validate sdk found in compile time classpath, takes precedence over sdkFile
+ * @param sdkFileName the sdkFile name, may be null, or empty
+ * @param overrideSdkLocation if provided overrides the default lookup of the localSdk, iff
+ * localSdk is on.
+ * @return a list of sdks to check with annotation processing validators.
+ */
+ private static ImmutableList<Sdk> loadFromSources(
+ boolean localSdk, String sdkFileName, String overrideSdkLocation, int overrideSdkInt) {
+ if (localSdk) {
+ Sdk sdk = null;
+ if (overrideSdkLocation != null) {
+ sdk = new Sdk(overrideSdkLocation, overrideSdkInt);
+ } else {
+ String target = compilationSdkTarget();
+ if (target != null) {
+ sdk = new Sdk(target);
+ }
+ }
+ return sdk == null ? ImmutableList.of() : ImmutableList.of(sdk);
+ }
+ if (sdkFileName == null || Files.notExists(Paths.get(sdkFileName))) {
+ return ImmutableList.of();
+ }
+ try (InputStream resIn = new FileInputStream(sdkFileName)) {
+ if (resIn == null) {
+ throw new RuntimeException("no such file " + sdkFileName);
+ }
BufferedReader in =
new BufferedReader(new InputStreamReader(resIn, Charset.defaultCharset()));
List<Sdk> sdks = new ArrayList<>();
@@ -109,7 +195,7 @@ public class SdkStore {
}
return ImmutableList.copyOf(sdks);
} catch (IOException e) {
- throw new RuntimeException("failed reading " + fileName, e);
+ throw new RuntimeException("failed reading " + sdkFileName, e);
}
}
@@ -132,14 +218,29 @@ public class SdkStore {
private final String path;
private final JarFile jarFile;
+ final AndroidVersions.AndroidRelease sdkRelease;
final int sdkInt;
private final Map<String, ClassInfo> classInfos = new HashMap<>();
private static File tempDir;
Sdk(String path) {
+ this(path, null);
+ }
+
+ Sdk(String path, Integer sdkInt) {
this.path = path;
- this.jarFile = ensureJar();
- this.sdkInt = readSdkInt();
+ if (path.startsWith("classpath:") || path.endsWith(".jar")) {
+ this.jarFile = ensureJar();
+ } else {
+ this.jarFile = null;
+ }
+ if (sdkInt == null) {
+ this.sdkRelease = readSdkVersion();
+ this.sdkInt = sdkRelease.getSdkInt();
+ } else {
+ this.sdkRelease = AndroidVersions.getReleaseForSdkInt(sdkInt);
+ this.sdkInt = sdkRelease.getSdkInt();
+ }
}
/**
@@ -156,34 +257,40 @@ public class SdkStore {
String className = getClassFQName(sdkClassElem);
ClassInfo classInfo = getClassInfo(className);
- if (classInfo == null) {
+ if (classInfo == null && !suppressWarnings(sdkClassElem, null)) {
return "No such class " + className;
}
MethodExtraInfo sdkMethod = classInfo.findMethod(methodElement, looseSignatures);
- if (sdkMethod == null) {
+ if (sdkMethod == null && !suppressWarnings(methodElement, null)) {
return "No such method in " + className;
}
-
- MethodExtraInfo implMethod = new MethodExtraInfo(methodElement);
- if (!sdkMethod.equals(implMethod)
- && !suppressWarnings(methodElement, "robolectric.ShadowReturnTypeMismatch")) {
- if (implMethod.isStatic != sdkMethod.isStatic) {
- return "@Implementation for " + methodElement.getSimpleName()
- + " is " + (implMethod.isStatic ? "static" : "not static")
- + " unlike the SDK method";
- }
- if (!implMethod.returnType.equals(sdkMethod.returnType)) {
- if (
- (looseSignatures && typeIsOkForLooseSignatures(implMethod, sdkMethod))
- || (looseSignatures && implMethod.returnType.equals("java.lang.Object[]"))
- // Number is allowed for int or long return types
- || typeIsNumeric(sdkMethod, implMethod)) {
- return null;
- } else {
- return "@Implementation for " + methodElement.getSimpleName()
- + " has a return type of " + implMethod.returnType
- + ", not " + sdkMethod.returnType + " as in the SDK method";
+ if (sdkMethod != null) {
+ MethodExtraInfo implMethod = new MethodExtraInfo(methodElement);
+ if (!sdkMethod.equals(implMethod)
+ && !suppressWarnings(methodElement, "robolectric.ShadowReturnTypeMismatch")) {
+ if (implMethod.isStatic != sdkMethod.isStatic) {
+ return "@Implementation for "
+ + methodElement.getSimpleName()
+ + " is "
+ + (implMethod.isStatic ? "static" : "not static")
+ + " unlike the SDK method";
+ }
+ if (!implMethod.returnType.equals(sdkMethod.returnType)) {
+ if ((looseSignatures && typeIsOkForLooseSignatures(implMethod, sdkMethod))
+ || (looseSignatures && implMethod.returnType.equals("java.lang.Object[]"))
+ // Number is allowed for int or long return types
+ || typeIsNumeric(sdkMethod, implMethod)) {
+ return null;
+ } else {
+ return "@Implementation for "
+ + methodElement.getSimpleName()
+ + " has a return type of "
+ + implMethod.returnType
+ + ", not "
+ + sdkMethod.returnType
+ + " as in the SDK method";
+ }
}
}
}
@@ -191,32 +298,45 @@ public class SdkStore {
return null;
}
- private static boolean suppressWarnings(ExecutableElement methodElement, String warningName) {
+ /**
+ * Warnings (or potentially Errors, depending on processing flags) can be suppressed in one of
+ * two ways, either with @SuppressWarnings("robolectric.<warningName>"), or with
+ * the @InDevelopment annotation, if and only the target Sdk is in development.
+ *
+ * @param annotatedElement element to inspect for annotations
+ * @param warningName the name of the warning, if null, @InDevelopment will still be honored.
+ * @return true if the warning should be suppressed, else false
+ */
+ private boolean suppressWarnings(Element annotatedElement, String warningName) {
SuppressWarnings[] suppressWarnings =
- methodElement.getAnnotationsByType(SuppressWarnings.class);
+ annotatedElement.getAnnotationsByType(SuppressWarnings.class);
for (SuppressWarnings suppression : suppressWarnings) {
for (String name : suppression.value()) {
- if (warningName.equals(name)) {
+ if (warningName != null && warningName.equals(name)) {
return true;
}
}
}
+ InDevelopment[] inDev = annotatedElement.getAnnotationsByType(InDevelopment.class);
+ if (inDev.length > 0 && !sdkRelease.isReleased()) {
+ return true;
+ }
return false;
}
private static boolean typeIsNumeric(MethodExtraInfo sdkMethod, MethodExtraInfo implMethod) {
return implMethod.returnType.equals("java.lang.Number")
- && isNumericType(sdkMethod.returnType);
+ && isNumericType(sdkMethod.returnType);
}
private static boolean typeIsOkForLooseSignatures(
MethodExtraInfo implMethod, MethodExtraInfo sdkMethod) {
return
- // loose signatures allow a return type of Object...
- implMethod.returnType.equals("java.lang.Object")
- // or Object[] for arrays...
- || (implMethod.returnType.equals("java.lang.Object[]")
- && sdkMethod.returnType.endsWith("[]"));
+ // loose signatures allow a return type of Object...
+ implMethod.returnType.equals("java.lang.Object")
+ // or Object[] for arrays...
+ || (implMethod.returnType.equals("java.lang.Object[]")
+ && sdkMethod.returnType.endsWith("[]"));
}
private static boolean isNumericType(String type) {
@@ -250,9 +370,9 @@ public class SdkStore {
*
* @return the API level
*/
- private int readSdkInt() {
+ private AndroidVersions.AndroidRelease readSdkVersion() {
try {
- return AndroidVersionInitTools.computeReleaseVersion(jarFile).getSdkInt();
+ return AndroidVersionInitTools.computeReleaseVersion(jarFile);
} catch (IOException e) {
throw new RuntimeException("failed to read build.prop from " + path);
}
@@ -267,12 +387,13 @@ public class SdkStore {
}
} catch (IOException e) {
- throw new RuntimeException("failed to open SDK " + sdkInt + " at " + path, e);
+ throw new RuntimeException(
+ "failed to open SDK " + sdkRelease.getSdkInt() + " at " + path, e);
}
}
private static File copyResourceToFile(String resourcePath) throws IOException {
- if (tempDir == null){
+ if (tempDir == null) {
File tempFile = File.createTempFile("prefix", "suffix");
tempFile.deleteOnExit();
tempDir = tempFile.getParentFile();
@@ -296,15 +417,45 @@ public class SdkStore {
private ClassNode loadClassNode(String name) {
String classFileName = name.replace('.', '/') + ".class";
- ZipEntry entry = jarFile.getEntry(classFileName);
- if (entry == null) {
+ Supplier<InputStream> inputStreamSupplier = null;
+
+ if (jarFile != null) {
+ // working with a jar file.
+ ZipEntry entry = jarFile.getEntry(classFileName);
+ if (entry == null) {
+ return null;
+ }
+ inputStreamSupplier =
+ () -> {
+ try {
+ return jarFile.getInputStream(entry);
+ } catch (IOException ioe) {
+ throw new RuntimeException("could not read zip entry", ioe);
+ }
+ };
+ } else {
+ // working with an exploded path location.
+ Path working = Path.of(path, classFileName);
+ File classFile = working.toFile();
+ if (classFile.isFile()) {
+ inputStreamSupplier =
+ () -> {
+ try {
+ return new FileInputStream(classFile);
+ } catch (IOException ioe) {
+ throw new RuntimeException("could not read file in path " + working, ioe);
+ }
+ };
+ }
+ }
+ if (inputStreamSupplier == null) {
return null;
}
- try (InputStream inputStream = jarFile.getInputStream(entry)) {
+ try (InputStream inputStream = inputStreamSupplier.get()) {
ClassReader classReader = new ClassReader(inputStream);
ClassNode classNode = new ClassNode();
- classReader.accept(classNode,
- ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
+ classReader.accept(
+ classNode, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
return classNode;
} catch (IOException e) {
throw new RuntimeException("failed to analyze " + classFileName + " in " + path, e);
@@ -313,7 +464,7 @@ public class SdkStore {
@Override
public int compareTo(Sdk sdk) {
- return sdk.sdkInt - sdkInt;
+ return sdk.sdkRelease.getSdkInt() - sdkRelease.getSdkInt();
}
}
@@ -321,8 +472,7 @@ public class SdkStore {
private final Map<MethodInfo, MethodExtraInfo> methods = new HashMap<>();
private final Map<MethodInfo, MethodExtraInfo> erasedParamTypesMethods = new HashMap<>();
- private ClassInfo() {
- }
+ private ClassInfo() {}
public ClassInfo(ClassNode classNode) {
for (Object aMethod : classNode.methods) {
@@ -401,20 +551,17 @@ public class SdkStore {
return false;
}
MethodInfo that = (MethodInfo) o;
- return Objects.equals(name, that.name)
- && Objects.equals(paramTypes, that.paramTypes);
+ return Objects.equals(name, that.name) && Objects.equals(paramTypes, that.paramTypes);
}
@Override
public int hashCode() {
return Objects.hash(name, paramTypes);
}
+
@Override
public String toString() {
- return "MethodInfo{"
- + "name='" + name + '\''
- + ", paramTypes=" + paramTypes
- + '}';
+ return "MethodInfo{" + "name='" + name + '\'' + ", paramTypes=" + paramTypes + '}';
}
}
diff --git a/processor/src/test/java/org/robolectric/annotation/processing/validator/InDevelopmentValidatorTest.java b/processor/src/test/java/org/robolectric/annotation/processing/validator/InDevelopmentValidatorTest.java
new file mode 100644
index 000000000..6e3581357
--- /dev/null
+++ b/processor/src/test/java/org/robolectric/annotation/processing/validator/InDevelopmentValidatorTest.java
@@ -0,0 +1,71 @@
+package org.robolectric.annotation.processing.validator;
+
+import static com.google.common.truth.Truth.assertAbout;
+import static org.robolectric.annotation.processing.validator.SingleClassSubject.singleClass;
+
+import com.example.objects.Dummy;
+import java.io.File;
+import java.net.URI;
+import java.net.URL;
+import java.util.HashMap;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.robolectric.versioning.AndroidVersions;
+
+@RunWith(JUnit4.class)
+public final class InDevelopmentValidatorTest {
+
+ private static String getClassRootDir(Class<?> clazz) {
+ // Get the URL representation of the class location
+ URL resource = clazz.getResource(clazz.getSimpleName() + ".class");
+ if (resource == null) {
+ return null; // dynamic class
+ }
+ if (resource.getProtocol().equals("file")) {
+ File path = new File(URI.create(resource.toString()).getPath()).getParentFile();
+ int packagePartSize = clazz.getPackageName().split("\\.").length;
+ for (int i = 0; i < packagePartSize; i++) {
+ path = path.getParentFile();
+ }
+ return path.getAbsolutePath();
+ } else if (resource.getProtocol().equals("jar")) {
+ // Extract path to JAR and find root
+ String path = resource.getPath().substring(5, resource.getPath().indexOf("!"));
+ File jarFile = new File(URI.create(path).getPath());
+ return jarFile.getAbsolutePath();
+ } else {
+ return null; // Not a supported class location
+ }
+ }
+
+ @Test
+ public void implementationWithInDevelopmentCompiles() {
+ AndroidVersions.AndroidRelease unreleased =
+ AndroidVersions.getUnreleased().stream().findFirst().get();
+ final String testClass =
+ "org.robolectric.annotation.processing.shadows.ShadowImplementsInDevelopment";
+ HashMap<String, String> props = new HashMap<>();
+ props.put("org.robolectric.annotation.processing.sdkCheckMode", "ERROR");
+ props.put("org.robolectric.annotation.processing.validateCompileSdk", "true");
+ assertAbout(singleClass(props, getClassRootDir(Dummy.class), unreleased.getSdkInt()))
+ .that(testClass)
+ .compilesWithoutError();
+ }
+
+ @Test
+ public void implementationWithoutInDevelopmentFailsToCompiles() {
+ AndroidVersions.AndroidRelease unreleased =
+ AndroidVersions.getUnreleased().stream().findFirst().get();
+ final String testClass =
+ "org.robolectric.annotation.processing.shadows.ShadowImplementsInDevelopmentMissing";
+ HashMap<String, String> props = new HashMap<>();
+ props.put("org.robolectric.annotation.processing.sdkCheckMode", "ERROR");
+ props.put("org.robolectric.annotation.processing.validateCompileSdk", "true");
+ assertAbout(singleClass(props, getClassRootDir(Dummy.class), unreleased.getSdkInt()))
+ .that(testClass)
+ .failsToCompile()
+ .withErrorContaining("No such method in com.example.objects.Dummy for SDK 35")
+ .onLine(11);
+ }
+}
diff --git a/processor/src/test/java/org/robolectric/annotation/processing/validator/SingleClassSubject.java b/processor/src/test/java/org/robolectric/annotation/processing/validator/SingleClassSubject.java
index c016495a2..c883443ec 100644
--- a/processor/src/test/java/org/robolectric/annotation/processing/validator/SingleClassSubject.java
+++ b/processor/src/test/java/org/robolectric/annotation/processing/validator/SingleClassSubject.java
@@ -13,6 +13,8 @@ import com.google.testing.compile.CompileTester.LineClause;
import com.google.testing.compile.CompileTester.SuccessfulCompilationClause;
import com.google.testing.compile.CompileTester.UnsuccessfulCompilationClause;
import com.google.testing.compile.JavaFileObjects;
+import java.util.HashMap;
+import java.util.Map;
import javax.tools.JavaFileObject;
import org.robolectric.annotation.processing.RobolectricProcessor;
import org.robolectric.annotation.processing.Utils;
@@ -20,21 +22,47 @@ import org.robolectric.annotation.processing.Utils;
public final class SingleClassSubject extends Subject {
public static Subject.Factory<SingleClassSubject, String> singleClass() {
-
return SingleClassSubject::new;
}
+ public static Subject.Factory<SingleClassSubject, String> singleClass(
+ Map<String, String> processorOpts, String sdkLocation, int sdkInt) {
+ return (FailureMetadata failureMetadata, String subject) ->
+ new SingleClassSubject(failureMetadata, subject, processorOpts, sdkLocation, sdkInt);
+ }
+
+ public static Subject.Factory<SingleClassSubject, String> singleClass(
+ Map<String, String> processorOpts) {
+ return (FailureMetadata failureMetadata, String subject) ->
+ new SingleClassSubject(failureMetadata, subject, processorOpts);
+ }
JavaFileObject source;
CompileTester tester;
-
+
public SingleClassSubject(FailureMetadata failureMetadata, String subject) {
+ this(failureMetadata, subject, DEFAULT_OPTS);
+ }
+
+ public SingleClassSubject(
+ FailureMetadata failureMetadata, String subject, Map<String, String> processorOpts) {
+ this(failureMetadata, subject, processorOpts, null, -1);
+ }
+
+ public SingleClassSubject(
+ FailureMetadata failureMetadata,
+ String subject,
+ Map<String, String> processorOpts,
+ String sdkLocation,
+ int sdkInt) {
super(failureMetadata, subject);
source = JavaFileObjects.forResource(Utils.toResourcePath(subject));
+ Map<String, String> opts = new HashMap<>(DEFAULT_OPTS);
+ opts.putAll(processorOpts);
tester =
assertAbout(javaSources())
.that(ImmutableList.of(source, Utils.SHADOW_EXTRACTOR_SOURCE))
- .processedWith(new RobolectricProcessor(DEFAULT_OPTS));
+ .processedWith(new RobolectricProcessor(opts, sdkLocation, sdkInt));
}
public SuccessfulCompilationClause compilesWithoutError() {
@@ -45,7 +73,7 @@ public final class SingleClassSubject extends Subject {
}
return null;
}
-
+
public SingleFileClause failsToCompile() {
try {
return new SingleFileClause(tester.failsToCompile(), source);
@@ -54,17 +82,17 @@ public final class SingleClassSubject extends Subject {
}
return null;
}
-
+
final class SingleFileClause implements CompileTester.ChainingClause<SingleFileClause> {
UnsuccessfulCompilationClause unsuccessful;
JavaFileObject source;
-
+
public SingleFileClause(UnsuccessfulCompilationClause unsuccessful, JavaFileObject source) {
this.unsuccessful = unsuccessful;
this.source = source;
}
-
+
public SingleLineClause withErrorContaining(final String messageFragment) {
try {
return new SingleLineClause(unsuccessful.withErrorContaining(messageFragment).in(source));
@@ -86,7 +114,7 @@ public final class SingleClassSubject extends Subject {
return this;
}
-
+
@Override
public SingleFileClause and() {
return this;
@@ -95,11 +123,11 @@ public final class SingleClassSubject extends Subject {
final class SingleLineClause implements CompileTester.ChainingClause<SingleFileClause> {
LineClause lineClause;
-
+
public SingleLineClause(LineClause lineClause) {
this.lineClause = lineClause;
}
-
+
public CompileTester.ChainingClause<SingleFileClause> onLine(long lineNumber) {
try {
lineClause.onLine(lineNumber);
@@ -114,12 +142,11 @@ public final class SingleClassSubject extends Subject {
}
return null;
}
-
+
@Override
public SingleFileClause and() {
return SingleFileClause.this;
}
-
}
}
}
diff --git a/processor/src/test/resources/org/robolectric/annotation/processing/shadows/ShadowImplementsInDevelopment.java b/processor/src/test/resources/org/robolectric/annotation/processing/shadows/ShadowImplementsInDevelopment.java
new file mode 100644
index 000000000..4330c7a79
--- /dev/null
+++ b/processor/src/test/resources/org/robolectric/annotation/processing/shadows/ShadowImplementsInDevelopment.java
@@ -0,0 +1,16 @@
+package org.robolectric.annotation.processing.shadows;
+
+import com.example.objects.Dummy;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.InDevelopment;
+
+@Implements(Dummy.class)
+public final class ShadowImplementsInDevelopment {
+
+ @Implementation
+ @InDevelopment
+ protected Object doSomething() {
+ return null;
+ }
+}
diff --git a/processor/src/test/resources/org/robolectric/annotation/processing/shadows/ShadowImplementsInDevelopmentMissing.java b/processor/src/test/resources/org/robolectric/annotation/processing/shadows/ShadowImplementsInDevelopmentMissing.java
new file mode 100644
index 000000000..80f69ef02
--- /dev/null
+++ b/processor/src/test/resources/org/robolectric/annotation/processing/shadows/ShadowImplementsInDevelopmentMissing.java
@@ -0,0 +1,14 @@
+package org.robolectric.annotation.processing.shadows;
+
+import com.example.objects.Dummy;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+
+@Implements(Dummy.class)
+public final class ShadowImplementsInDevelopmentMissing {
+
+ @Implementation
+ protected Object doSomething() {
+ return null;
+ }
+}
diff --git a/resources/src/main/java/org/robolectric/manifest/AndroidManifest.java b/resources/src/main/java/org/robolectric/manifest/AndroidManifest.java
index d90117be8..60f805d72 100644
--- a/resources/src/main/java/org/robolectric/manifest/AndroidManifest.java
+++ b/resources/src/main/java/org/robolectric/manifest/AndroidManifest.java
@@ -614,7 +614,7 @@ public class AndroidManifest implements UsesSdk {
* <p>Note that if {@link #targetSdkVersion} isn't set, this value changes the behavior of some
* Android code (notably {@link android.content.SharedPreferences}) to emulate old bugs.
*
- * @return the minimum SDK version, or Jelly Bean (16) by default
+ * @return the minimum SDK version, or KitKat (19) by default
*/
@Override
public int getMinSdkVersion() {
@@ -629,7 +629,7 @@ public class AndroidManifest implements UsesSdk {
* <p>Note that this value changes the behavior of some Android code (notably {@link
* android.content.SharedPreferences}) to emulate old bugs.
*
- * @return the minimum SDK version, or Jelly Bean (16) by default
+ * @return the target SDK version, or KitKat (19) by default
*/
@Override
public int getTargetSdkVersion() {
diff --git a/robolectric/src/main/java/org/robolectric/RobolectricTestRunner.java b/robolectric/src/main/java/org/robolectric/RobolectricTestRunner.java
index def624b88..4cfb8f30c 100644
--- a/robolectric/src/main/java/org/robolectric/RobolectricTestRunner.java
+++ b/robolectric/src/main/java/org/robolectric/RobolectricTestRunner.java
@@ -50,7 +50,6 @@ import org.robolectric.pluginapi.SdkPicker;
import org.robolectric.pluginapi.config.ConfigurationStrategy;
import org.robolectric.pluginapi.config.ConfigurationStrategy.Configuration;
import org.robolectric.pluginapi.config.GlobalConfigProvider;
-import org.robolectric.plugins.HierarchicalConfigurationStrategy.ConfigurationImpl;
import org.robolectric.util.Logger;
import org.robolectric.util.PerfStatsCollector;
import org.robolectric.util.ReflectionHelpers;
@@ -411,42 +410,9 @@ public class RobolectricTestRunner extends SandboxTestRunner {
manifestIdentifier.getApkFile());
}
- /**
- * Compute the effective Robolectric configuration for a given test method.
- *
- * <p>Configuration information is collected from package-level {@code robolectric.properties}
- * files and {@link Config} annotations on test classes, superclasses, and methods.
- *
- * <p>Custom TestRunner subclasses may wish to override this method to provide alternate
- * configuration.
- *
- * @param method the test method
- * @return the effective Robolectric configuration for the given test method
- * @deprecated Provide an implementation of {@link javax.inject.Provider<Config>} instead. This
- * method will be removed in Robolectric 4.3.
- * @since 2.0
- * @see <a href="http://robolectric.org/migrating/#migrating-to-40">Migration Notes</a> for more
- * details.
- */
- @Deprecated
- public Config getConfig(Method method) {
- throw new UnsupportedOperationException();
- }
-
/** Calculate the configuration for a given test method. */
private Configuration getConfiguration(Method method) {
- Configuration configuration =
- configurationStrategy.getConfig(getTestClass().getJavaClass(), method);
-
- // in case #getConfig(Method) has been overridden...
- try {
- Config config = getConfig(method);
- ((ConfigurationImpl) configuration).put(Config.class, config);
- } catch (UnsupportedOperationException e) {
- // no problem
- }
-
- return configuration;
+ return configurationStrategy.getConfig(getTestClass().getJavaClass(), method);
}
/**
diff --git a/robolectric/src/main/java/org/robolectric/plugins/DefaultSdkProvider.java b/robolectric/src/main/java/org/robolectric/plugins/DefaultSdkProvider.java
index 3b6f76da9..0f9272c03 100644
--- a/robolectric/src/main/java/org/robolectric/plugins/DefaultSdkProvider.java
+++ b/robolectric/src/main/java/org/robolectric/plugins/DefaultSdkProvider.java
@@ -46,7 +46,7 @@ public class DefaultSdkProvider implements SdkProvider {
private static final int RUNNING_JAVA_VERSION = Util.getJavaVersion();
- private static final int PREINSTRUMENTED_VERSION = 5;
+ private static final int PREINSTRUMENTED_VERSION = 6;
private final DependencyResolver dependencyResolver;
diff --git a/robolectric/src/test/java/org/robolectric/RobolectricTestRunnerTest.java b/robolectric/src/test/java/org/robolectric/RobolectricTestRunnerTest.java
index 69b17a2f4..dfdb9d495 100644
--- a/robolectric/src/test/java/org/robolectric/RobolectricTestRunnerTest.java
+++ b/robolectric/src/test/java/org/robolectric/RobolectricTestRunnerTest.java
@@ -42,11 +42,9 @@ import org.junit.runner.notification.RunListener;
import org.junit.runner.notification.RunNotifier;
import org.junit.runners.JUnit4;
import org.junit.runners.MethodSorters;
-import org.junit.runners.model.FrameworkMethod;
import org.robolectric.RobolectricTestRunner.RobolectricFrameworkMethod;
import org.robolectric.android.internal.AndroidTestEnvironment;
import org.robolectric.annotation.Config;
-import org.robolectric.annotation.Config.Implementation;
import org.robolectric.annotation.experimental.LazyApplication;
import org.robolectric.annotation.experimental.LazyApplication.LazyLoad;
import org.robolectric.config.ConfigurationRegistry;
@@ -143,20 +141,6 @@ public class RobolectricTestRunnerTest {
}
@Test
- public void supportsOldGetConfigUntil4dot3() throws Exception {
- Implementation overriddenConfig = Config.Builder.defaults().build();
- List<FrameworkMethod> children = new SingleSdkRobolectricTestRunner(TestWithTwoMethods.class) {
- @Override
- public Config getConfig(Method method) {
- return overriddenConfig;
- }
- }.getChildren();
- Config config = ((RobolectricFrameworkMethod) children.get(0))
- .getConfiguration().get(Config.class);
- assertThat(config).isSameInstanceAs(overriddenConfig);
- }
-
- @Test
public void failureInResetterDoesntBreakAllTests() throws Exception {
RobolectricTestRunner runner =
new SingleSdkRobolectricTestRunner(
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowBatteryManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowBatteryManagerTest.java
index 0b8986f0d..aee4779e0 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowBatteryManagerTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowBatteryManagerTest.java
@@ -2,7 +2,9 @@ package org.robolectric.shadows;
import static android.os.Build.VERSION_CODES.LOLLIPOP;
import static android.os.Build.VERSION_CODES.M;
+import static android.os.Build.VERSION_CODES.P;
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertThrows;
import static org.robolectric.Shadows.shadowOf;
import android.content.Context;
@@ -70,4 +72,26 @@ public class ShadowBatteryManagerTest {
shadowBatteryManager.setLongProperty(TEST_ID, Long.MAX_VALUE);
assertThat(batteryManager.getLongProperty(TEST_ID)).isEqualTo(Long.MAX_VALUE);
}
+
+ @Test
+ @Config(minSdk = P)
+ public void testChargeTimeRemaining() {
+ shadowBatteryManager.setChargeTimeRemaining(0L);
+ assertThat(batteryManager.computeChargeTimeRemaining()).isEqualTo(0L);
+
+ shadowBatteryManager.setChargeTimeRemaining(20L);
+ assertThat(batteryManager.computeChargeTimeRemaining()).isEqualTo(20L);
+
+ shadowBatteryManager.setChargeTimeRemaining(-1L);
+ assertThat(batteryManager.computeChargeTimeRemaining()).isEqualTo(-1L);
+ }
+
+ @Test
+ @Config(minSdk = P)
+ public void testChargeTimeRemainingRejectsInvalidValues() {
+ assertThrows(
+ IllegalArgumentException.class, () -> shadowBatteryManager.setChargeTimeRemaining(-50L));
+ assertThrows(
+ IllegalArgumentException.class, () -> shadowBatteryManager.setChargeTimeRemaining(-100L));
+ }
}
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowConnectivityManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowConnectivityManagerTest.java
index 5fbe161d6..dc7230dff 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowConnectivityManagerTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowConnectivityManagerTest.java
@@ -7,6 +7,7 @@ import static android.os.Build.VERSION_CODES.LOLLIPOP;
import static android.os.Build.VERSION_CODES.M;
import static android.os.Build.VERSION_CODES.N;
import static android.os.Build.VERSION_CODES.O;
+import static android.os.Build.VERSION_CODES.S;
import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
@@ -398,6 +399,16 @@ public class ShadowConnectivityManagerTest {
}
@Test
+ @Config(minSdk = S)
+ public void registerBestMatchingNetworkCallback_shouldAddCallback() {
+ NetworkRequest.Builder builder = new NetworkRequest.Builder();
+ ConnectivityManager.NetworkCallback callback = createSimpleCallback();
+ connectivityManager.registerBestMatchingNetworkCallback(
+ builder.build(), callback, new Handler());
+ assertThat(shadowOf(connectivityManager).getNetworkCallbacks()).hasSize(1);
+ }
+
+ @Test
@Config(minSdk = LOLLIPOP)
public void unregisterCallback_shouldRemoveCallbacks() {
NetworkRequest.Builder builder = new NetworkRequest.Builder();
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowContextHubClientTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowContextHubClientTest.java
index fbc9fa41c..d2d663eae 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowContextHubClientTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowContextHubClientTest.java
@@ -3,6 +3,7 @@ package org.robolectric.shadows;
import static android.os.Build.VERSION_CODES.Q;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.fail;
+import static org.robolectric.Shadows.shadowOf;
import android.content.Context;
import android.hardware.location.ContextHubClient;
@@ -13,8 +14,10 @@ import android.hardware.location.ContextHubTransaction;
import android.hardware.location.NanoAppMessage;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import org.robolectric.shadow.api.Shadow;
@@ -22,6 +25,12 @@ import org.robolectric.shadow.api.Shadow;
@Config(minSdk = Q)
public final class ShadowContextHubClientTest {
+ @Before
+ public void setUp() {
+ shadowOf(RuntimeEnvironment.getApplication())
+ .grantPermissions(android.Manifest.permission.ACCESS_CONTEXT_HUB);
+ }
+
@Test
public void whenAMessageIsSent_ensureItIsReturnedInTheMessageList() {
ContextHubClient contextHubClient = (ContextHubClient) createContextHubClient();
@@ -73,12 +82,12 @@ public final class ShadowContextHubClientTest {
@Test
public void whenContextHubRevokedPermission_ensureSendingAMessageThrowsSecurityException() {
ContextHubClient contextHubClient = (ContextHubClient) createContextHubClient();
- ShadowContextHubClient shadowContextHubClient = Shadow.extract(contextHubClient);
NanoAppMessage message =
NanoAppMessage.createMessageToNanoApp(
/* targetNanoAppId= */ 0L, /* messageType= */ 0, /* messageBody= */ new byte[10]);
- shadowContextHubClient.revokePermission();
+ shadowOf(RuntimeEnvironment.getApplication())
+ .denyPermissions(android.Manifest.permission.ACCESS_CONTEXT_HUB);
try {
contextHubClient.sendMessageToNanoApp(message);
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowSurfaceViewTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowSurfaceViewTest.java
index 2105d786b..b6719eae7 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowSurfaceViewTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowSurfaceViewTest.java
@@ -4,27 +4,36 @@ import static com.google.common.truth.Truth.assertThat;
import static org.robolectric.Robolectric.buildActivity;
import android.app.Activity;
+import android.graphics.PixelFormat;
import android.os.Bundle;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.Window;
import androidx.test.ext.junit.runners.AndroidJUnit4;
+import org.junit.After;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.Shadows;
+import org.robolectric.android.controller.ActivityController;
@RunWith(AndroidJUnit4.class)
public class ShadowSurfaceViewTest {
- private SurfaceHolder.Callback callback1 = new TestCallback();
- private SurfaceHolder.Callback callback2 = new TestCallback();
+ private final SurfaceHolder.Callback callback1 = new TestCallback();
+ private final SurfaceHolder.Callback callback2 = new TestCallback();
+ private final ActivityController<Activity> defaultController = buildActivity(Activity.class);
- private SurfaceView view = new SurfaceView(buildActivity(Activity.class).create().get());
- private SurfaceHolder surfaceHolder = view.getHolder();
- private ShadowSurfaceView shadowSurfaceView = (ShadowSurfaceView) Shadows.shadowOf(view);
- private ShadowSurfaceView.FakeSurfaceHolder fakeSurfaceHolder =
+ private final SurfaceView view = new SurfaceView(defaultController.create().get());
+ private final SurfaceHolder surfaceHolder = view.getHolder();
+ private final ShadowSurfaceView shadowSurfaceView = Shadows.shadowOf(view);
+ private final ShadowSurfaceView.FakeSurfaceHolder fakeSurfaceHolder =
shadowSurfaceView.getFakeSurfaceHolder();
+ @After
+ public void tearDown() {
+ defaultController.close();
+ }
+
@Test
public void addCallback() {
assertThat(fakeSurfaceHolder.getCallbacks()).isEmpty();
@@ -54,8 +63,27 @@ public class ShadowSurfaceViewTest {
@Test
public void canCreateASurfaceView_attachedToAWindowWithActionBar() {
- TestActivity testActivity = buildActivity(TestActivity.class).create().start().resume().visible().get();
- assertThat(testActivity).isNotNull();
+ try (ActivityController<TestActivity> controller = buildActivity(TestActivity.class)) {
+ TestActivity testActivity = controller.create().start().resume().visible().get();
+ assertThat(testActivity).isNotNull();
+ }
+ }
+
+ @Test
+ public void requestedFormat_default_getRGB565() {
+ assertThat(fakeSurfaceHolder.getRequestedFormat()).isEqualTo(PixelFormat.RGB_565);
+ }
+
+ @Test
+ public void requestedFormat_setNewFormat_getNewFormat() {
+ view.getHolder().setFormat(PixelFormat.HSV_888);
+ assertThat(fakeSurfaceHolder.getRequestedFormat()).isEqualTo(PixelFormat.HSV_888);
+ }
+
+ @Test
+ public void requestedFormat_setFormatOpaque_getRGB565() {
+ view.getHolder().setFormat(PixelFormat.OPAQUE);
+ assertThat(fakeSurfaceHolder.getRequestedFormat()).isEqualTo(PixelFormat.RGB_565);
}
private static class TestCallback implements SurfaceHolder.Callback {
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowTelephonyManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowTelephonyManagerTest.java
index afabf1db0..d560d60ea 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowTelephonyManagerTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowTelephonyManagerTest.java
@@ -867,6 +867,15 @@ public class ShadowTelephonyManagerTest {
}
@Test
+ @Config(minSdk = P)
+ public void shouldGetSimCarrierIdName() {
+ String expectedCarrierIdName = "Fi";
+ shadowOf(telephonyManager).setSimCarrierIdName(expectedCarrierIdName);
+
+ assertThat(telephonyManager.getSimCarrierIdName().toString()).isEqualTo(expectedCarrierIdName);
+ }
+
+ @Test
@Config(minSdk = Q)
public void shouldGetCarrierIdFromSimMccMnc() {
int expectedCarrierId = 419;
diff --git a/sandbox/src/main/java/org/robolectric/internal/bytecode/ShadowWrangler.java b/sandbox/src/main/java/org/robolectric/internal/bytecode/ShadowWrangler.java
index 1c6d8c19c..ab480d69a 100644
--- a/sandbox/src/main/java/org/robolectric/internal/bytecode/ShadowWrangler.java
+++ b/sandbox/src/main/java/org/robolectric/internal/bytecode/ShadowWrangler.java
@@ -18,6 +18,7 @@ import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import javax.annotation.Nonnull;
import javax.annotation.Priority;
@@ -305,13 +306,8 @@ public class ShadowWrangler implements ClassHandler {
Class<?>[] types,
ShadowInfo shadowInfo,
Class<?> shadowClass) {
- Method method = findShadowMethodDeclaredOnClass(shadowClass, name, types);
-
- if (method == null && shadowInfo.looseSignatures) {
- Class<?>[] genericTypes = MethodType.genericMethodType(types.length).parameterArray();
- method = findShadowMethodDeclaredOnClass(shadowClass, name, genericTypes);
- }
-
+ Method method =
+ findShadowMethodDeclaredOnClass(shadowClass, name, types, shadowInfo.looseSignatures);
if (method != null) {
return method;
} else {
@@ -334,41 +330,49 @@ public class ShadowWrangler implements ClassHandler {
}
private Method findShadowMethodDeclaredOnClass(
- Class<?> shadowClass, String methodName, Class<?>[] paramClasses) {
- try {
- Method method = shadowClass.getDeclaredMethod(methodName, paramClasses);
-
- // todo: allow per-version overloading
- // if (method == null) {
- // String methodPrefix = name + "$$";
- // for (Method candidateMethod : shadowClass.getDeclaredMethods()) {
- // if (candidateMethod.getName().startsWith(methodPrefix)) {
- //
- // }
- // }
- // }
-
- if (isValidShadowMethod(method)) {
- method.setAccessible(true);
- return method;
- } else {
- return null;
+ Class<?> shadowClass, String methodName, Class<?>[] paramClasses, boolean looseSignatures) {
+ Method foundMethod = null;
+ for (Method method : shadowClass.getDeclaredMethods()) {
+ if (!method.getName().equals(methodName)
+ || method.getParameterCount() != paramClasses.length) {
+ continue;
}
- } catch (NoSuchMethodException e) {
- return null;
- }
- }
+ if (!Modifier.isPublic(method.getModifiers())
+ && !Modifier.isProtected(method.getModifiers())) {
+ continue;
+ }
- private boolean isValidShadowMethod(Method method) {
- int modifiers = method.getModifiers();
- if (!Modifier.isPublic(modifiers) && !Modifier.isProtected(modifiers)) {
- return false;
+ if (Arrays.equals(method.getParameterTypes(), paramClasses)
+ && shadowMatcher.matches(method)) {
+ // Found an exact match, we can exit early.
+ foundMethod = method;
+ break;
+ }
+ if (looseSignatures) {
+ boolean allParameterTypesAreObject = true;
+ for (Class<?> paramClass : method.getParameterTypes()) {
+ if (!paramClass.equals(Object.class)) {
+ allParameterTypesAreObject = false;
+ break;
+ }
+ }
+ if (allParameterTypesAreObject && shadowMatcher.matches(method)) {
+ // Found a looseSignatures match, but continue looking for an exact match.
+ foundMethod = method;
+ }
+ }
}
- return shadowMatcher.matches(method);
+ if (foundMethod != null) {
+ foundMethod.setAccessible(true);
+ return foundMethod;
+ } else {
+ return null;
+ }
}
+
@Override
public Object intercept(String signature, Object instance, Object[] params, Class theClass)
throws Throwable {
diff --git a/shadows/framework/Android.bp b/shadows/framework/Android.bp
index 8e9f0e13b..493b84870 100644
--- a/shadows/framework/Android.bp
+++ b/shadows/framework/Android.bp
@@ -11,8 +11,10 @@ package {
default_applicable_licenses: ["external_robolectric_license"],
}
-java_library_host {
+java_library {
name: "Robolectric_shadows_framework_upstream",
+ host_supported: true,
+ device_supported: false,
srcs: [
"src/main/java/**/*.java",
"src/main/java/**/*.kt",
@@ -21,6 +23,7 @@ java_library_host {
javacflags: [
"-Aorg.robolectric.annotation.processing.shadowPackage=org.robolectric",
"-Aorg.robolectric.annotation.processing.sdkCheckMode=ERROR",
+ "-Aorg.robolectric.annotation.processing.validateCompileSdk=true",
// Uncomment the below to debug annotation processors not firing.
//"-verbose",
//"-XprintRounds",
@@ -41,7 +44,6 @@ java_library_host {
"auto_value_annotations",
"jsr305",
"icu4j",
-
"robolectric-accessibility-test-framework-2.1",
"robolectric-javax.annotation-api-1.2",
"robolectric-sqlite4java-0.282",
@@ -54,9 +56,7 @@ java_library_host {
// aar files that make up android and jetpack
"robolectric-host-android_all",
"robolectric-host-androidx-test-core_upstream",
- //"robolectric-host-androidx-test-ext-junit_upstream",
"robolectric-host-androidx-test-monitor_upstream",
- //"robolectric-host-androidx-test-runner_upstream",
],
plugins: [
"auto_value_plugin_1.9",
diff --git a/shadows/framework/build.gradle b/shadows/framework/build.gradle
index 74409a64f..492f86502 100644
--- a/shadows/framework/build.gradle
+++ b/shadows/framework/build.gradle
@@ -33,7 +33,7 @@ tasks.register('copySqliteNatives', Copy) {
}
}
}
- into project.file("$buildDir/resources/main/sqlite4java")
+ into project.file(layout.buildDirectory.dir("resources/main/sqlite4java"))
}
jar {
@@ -51,7 +51,6 @@ dependencies {
api project(":pluginapi")
api project(":sandbox")
api project(":shadowapi")
- api project(":shadows:versioning")
api project(":utils")
api project(":utils:reflector")
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowArscAssetManager14.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowArscAssetManager14.java
index 7d3a1d5c6..b01322af8 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowArscAssetManager14.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowArscAssetManager14.java
@@ -6,6 +6,7 @@ import android.content.res.ApkAssets;
import android.content.res.AssetManager;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.InDevelopment;
import org.robolectric.versioning.AndroidVersions.U;
import org.robolectric.versioning.AndroidVersions.V;
@@ -74,6 +75,7 @@ public class ShadowArscAssetManager14 extends ShadowArscAssetManager10 {
}
@Implementation(minSdk = V.SDK_INT)
+ @InDevelopment
protected static void nativeSetConfiguration(
long ptr,
int mcc,
@@ -129,6 +131,7 @@ public class ShadowArscAssetManager14 extends ShadowArscAssetManager10 {
}
@Implementation(minSdk = V.SDK_INT)
+ @InDevelopment
protected static void nativeSetApkAssets(
long ptr, @NonNull ApkAssets[] apkAssets, boolean invalidateCaches, boolean preset) {
nativeSetApkAssets(ptr, apkAssets, invalidateCaches);
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBatteryManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBatteryManager.java
index 6dad75085..aeceac4db 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBatteryManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBatteryManager.java
@@ -2,8 +2,10 @@ package org.robolectric.shadows;
import static android.os.Build.VERSION_CODES.LOLLIPOP;
import static android.os.Build.VERSION_CODES.M;
+import static android.os.Build.VERSION_CODES.P;
import android.os.BatteryManager;
+import com.google.common.base.Preconditions;
import java.util.HashMap;
import java.util.Map;
import org.robolectric.annotation.Implementation;
@@ -12,6 +14,7 @@ import org.robolectric.annotation.Implements;
@Implements(BatteryManager.class)
public class ShadowBatteryManager {
private boolean isCharging = false;
+ private long chargeTimeRemaining = 0;
private final Map<Integer, Long> longProperties = new HashMap<>();
private final Map<Integer, Integer> intProperties = new HashMap<>();
@@ -41,4 +44,17 @@ public class ShadowBatteryManager {
public void setLongProperty(int id, long value) {
longProperties.put(id, value);
}
+
+ @Implementation(minSdk = P)
+ protected long computeChargeTimeRemaining() {
+ return chargeTimeRemaining;
+ }
+
+ /** Sets the value to be returned from {@link BatteryManager#computeChargeTimeRemaining} */
+ public void setChargeTimeRemaining(long chargeTimeRemaining) {
+ Preconditions.checkArgument(
+ chargeTimeRemaining == -1 || chargeTimeRemaining >= 0,
+ "chargeTimeRemaining must be -1 or non-negative.");
+ this.chargeTimeRemaining = chargeTimeRemaining;
+ }
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowConnectivityManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowConnectivityManager.java
index a051af131..010f9619b 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowConnectivityManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowConnectivityManager.java
@@ -4,6 +4,7 @@ import static android.os.Build.VERSION_CODES.LOLLIPOP;
import static android.os.Build.VERSION_CODES.M;
import static android.os.Build.VERSION_CODES.N;
import static android.os.Build.VERSION_CODES.O;
+import static android.os.Build.VERSION_CODES.S;
import static org.robolectric.RuntimeEnvironment.getApiLevel;
import static org.robolectric.Shadows.shadowOf;
@@ -152,6 +153,14 @@ public class ShadowConnectivityManager {
networkCallbacks.add(networkCallback);
}
+ @Implementation(minSdk = S)
+ protected void registerBestMatchingNetworkCallback(
+ NetworkRequest request,
+ ConnectivityManager.NetworkCallback networkCallback,
+ Handler handler) {
+ networkCallbacks.add(networkCallback);
+ }
+
@Implementation(minSdk = LOLLIPOP)
protected void unregisterNetworkCallback(ConnectivityManager.NetworkCallback networkCallback) {
if (networkCallback == null) {
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowContextHubClient.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowContextHubClient.java
index 6b8dc0c04..04d264a5d 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowContextHubClient.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowContextHubClient.java
@@ -13,6 +13,7 @@ import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
+import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.HiddenApi;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
@@ -26,12 +27,12 @@ import org.robolectric.util.reflector.ForType;
public class ShadowContextHubClient {
@RealObject private ContextHubClient realContextHubClient;
private final List<NanoAppMessage> messages = new ArrayList<>();
- private Boolean hasPermission = true;
@Implementation(minSdk = VERSION_CODES.P)
@HiddenApi
protected int sendMessageToNanoApp(NanoAppMessage message) {
- if (!hasPermission) {
+ if (!ShadowInstrumentation.hasRequiredPermission(
+ RuntimeEnvironment.getApplication(), android.Manifest.permission.ACCESS_CONTEXT_HUB)) {
throw new SecurityException("ShadowContextHubClient does not have permission");
}
if (isClosed()) {
@@ -47,10 +48,6 @@ public class ShadowContextHubClient {
reflector(ContextHubClientReflector.class, realContextHubClient).getIsClosed().set(true);
}
- public void revokePermission() {
- hasPermission = false;
- }
-
public List<NanoAppMessage> getMessages() {
return ImmutableList.copyOf(messages);
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowInstrumentation.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowInstrumentation.java
index aff4fd06e..d33cf20d9 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowInstrumentation.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowInstrumentation.java
@@ -361,8 +361,8 @@ public class ShadowInstrumentation {
// The receiver must hold the permission specified by sendBroadcast, and the broadcaster must
// hold the permission specified by registerReceiver.
boolean hasPermissionFromManifest =
- hasRequiredPermissionForBroadcast(wrapper.context, receiverPermission)
- && hasRequiredPermissionForBroadcast(broadcastContext, wrapper.broadcastPermission);
+ hasRequiredPermission(wrapper.context, receiverPermission)
+ && hasRequiredPermission(broadcastContext, wrapper.broadcastPermission);
// Many existing tests don't declare manifest permissions, relying on the old equality check.
boolean hasPermissionForBackwardsCompatibility =
TextUtils.equals(receiverPermission, wrapper.broadcastPermission);
@@ -379,8 +379,7 @@ public class ShadowInstrumentation {
}
/** A null {@code requiredPermission} indicates that no permission is required. */
- private static boolean hasRequiredPermissionForBroadcast(
- Context context, @Nullable String requiredPermission) {
+ static boolean hasRequiredPermission(Context context, @Nullable String requiredPermission) {
if (requiredPermission == null) {
return true;
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowKeyCharacterMap.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowKeyCharacterMap.java
index ea29a43dc..92ff331f6 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowKeyCharacterMap.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowKeyCharacterMap.java
@@ -182,11 +182,8 @@ public class ShadowKeyCharacterMap {
return ReflectionHelpers.callConstructor(KeyCharacterMap.class);
}
- @Implementation
- protected KeyEvent[] getEvents(char[] chars) {
- if (chars == null) {
- throw new IllegalArgumentException("chars must not be null.");
- }
+ @Implementation(minSdk = KITKAT_WATCH)
+ protected static KeyEvent[] nativeGetEvents(long ptr, char[] chars) {
int eventsPerChar = 2;
KeyEvent[] events = new KeyEvent[chars.length * eventsPerChar];
@@ -198,13 +195,28 @@ public class ShadowKeyCharacterMap {
return events;
}
- @Implementation
- protected int getKeyboardType() {
+ @Implementation(maxSdk = KITKAT)
+ protected static KeyEvent[] nativeGetEvents(int ptr, char[] chars) {
+ return nativeGetEvents((long) ptr, chars);
+ }
+
+ @Implementation(minSdk = KITKAT_WATCH)
+ protected static int nativeGetKeyboardType(long ptr) {
return KeyCharacterMap.FULL;
}
- @Implementation
- protected int get(int keyCode, int metaState) {
+ @Implementation(maxSdk = KITKAT)
+ protected static int nativeGetKeyboardType(int ptr) {
+ return KeyCharacterMap.FULL;
+ }
+
+ @Implementation(maxSdk = KITKAT)
+ protected static char nativeGetCharacter(int ptr, int keyCode, int metaState) {
+ return nativeGetCharacter((long) ptr, keyCode, metaState);
+ }
+
+ @Implementation(minSdk = KITKAT_WATCH)
+ protected static char nativeGetCharacter(long ptr, int keyCode, int metaState) {
boolean metaShiftOn = (metaState & KeyEvent.META_SHIFT_ON) != 0;
Character character = KEY_CODE_TO_CHAR.get(keyCode);
if (character == null) {
@@ -216,7 +228,7 @@ public class ShadowKeyCharacterMap {
}
}
- public KeyEvent getDownEvent(char a) {
+ private static KeyEvent getDownEvent(char a) {
return new KeyEvent(
0,
0,
@@ -228,7 +240,7 @@ public class ShadowKeyCharacterMap {
0);
}
- public KeyEvent getUpEvent(char a) {
+ private static KeyEvent getUpEvent(char a) {
return new KeyEvent(
0,
0,
@@ -240,24 +252,14 @@ public class ShadowKeyCharacterMap {
0);
}
- @Implementation
- protected char getDisplayLabel(int keyCode) {
+ @Implementation(minSdk = KITKAT_WATCH)
+ protected static char nativeGetDisplayLabel(long ptr, int keyCode) {
return KEY_CODE_TO_CHAR.getOrDefault(keyCode, (char) 0);
}
- @Implementation
- protected boolean isPrintingKey(int keyCode) {
- int type = Character.getType(getDisplayLabel(keyCode));
- switch (type) {
- case Character.SPACE_SEPARATOR:
- case Character.LINE_SEPARATOR:
- case Character.PARAGRAPH_SEPARATOR:
- case Character.CONTROL:
- case Character.FORMAT:
- return false;
- default:
- return true;
- }
+ @Implementation(maxSdk = KITKAT)
+ protected static char nativeGetDisplayLabel(int ptr, int keyCode) {
+ return KEY_CODE_TO_CHAR.getOrDefault(keyCode, (char) 0);
}
@Implementation(minSdk = KITKAT_WATCH)
@@ -278,7 +280,7 @@ public class ShadowKeyCharacterMap {
return character;
}
- private int toCharKeyCode(char a) {
+ private static int toCharKeyCode(char a) {
if (CHAR_TO_KEY_CODE.containsKey(Character.toUpperCase(a))) {
return CHAR_TO_KEY_CODE.get(Character.toUpperCase(a));
} else if (CHAR_TO_KEY_CODE_SHIFT_ON.containsKey(a)) {
@@ -288,7 +290,7 @@ public class ShadowKeyCharacterMap {
}
}
- private int getMetaState(char a) {
+ private static int getMetaState(char a) {
if (Character.isUpperCase(a) || CHAR_TO_KEY_CODE_SHIFT_ON.containsKey(a)) {
return KeyEvent.META_SHIFT_ON;
} else {
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowKeyEvent.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowKeyEvent.java
new file mode 100644
index 000000000..8f41061d3
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowKeyEvent.java
@@ -0,0 +1,53 @@
+package org.robolectric.shadows;
+
+import static org.robolectric.util.reflector.Reflector.reflector;
+
+import android.view.KeyEvent;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.HashMap;
+import java.util.Map;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.util.reflector.Accessor;
+import org.robolectric.util.reflector.ForType;
+import org.robolectric.versioning.AndroidVersions.L;
+
+/** Shadow for {@link KeyEvent}. */
+@Implements(KeyEvent.class)
+public class ShadowKeyEvent extends ShadowInputEvent {
+
+ private static final Map<String, Integer> keyCodeMap;
+
+ static {
+ keyCodeMap = new HashMap<>();
+ if (RuntimeEnvironment.getApiLevel() >= L.SDK_INT) {
+ String keyCodeConstantPrefix = reflector(KeyEventReflector.class).getLabelPrefix();
+ for (Field field : KeyEvent.class.getDeclaredFields()) {
+ if (field.getName().startsWith(keyCodeConstantPrefix)
+ && Modifier.isStatic(field.getModifiers())) {
+ String keyCodeString = field.getName().substring(keyCodeConstantPrefix.length());
+ try {
+ keyCodeMap.put(keyCodeString, field.getInt(null));
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(
+ "unable to get reflectively get value for " + keyCodeString, e);
+ }
+ }
+ }
+ }
+ }
+
+ @Implementation(minSdk = L.SDK_INT)
+ protected static int nativeKeyCodeFromString(String keyCode) {
+ return keyCodeMap.getOrDefault(keyCode, KeyEvent.KEYCODE_UNKNOWN);
+ }
+
+ @ForType(KeyEvent.class)
+ private interface KeyEventReflector {
+
+ @Accessor("LABEL_PREFIX")
+ String getLabelPrefix();
+ }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLog.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLog.java
index 82e4e43d2..b71d462b3 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLog.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLog.java
@@ -115,17 +115,17 @@ public class ShadowLog {
protected static int wtf(String tag, String msg, Throwable throwable) {
addLog(Log.ASSERT, tag, msg, throwable);
// invoking the wtfHandler
- TerribleFailure terribleFailure =
+ Throwable terribleFailure =
reflector(TerribleFailureReflector.class).newTerribleFailure(msg, throwable);
if (wtfIsFatal) {
Util.sneakyThrow(terribleFailure);
}
TerribleFailureHandler terribleFailureHandler = reflector(LogReflector.class).getWtfHandler();
if (RuntimeEnvironment.getApiLevel() >= L.SDK_INT) {
- terribleFailureHandler.onTerribleFailure(tag, terribleFailure, false);
+ terribleFailureHandler.onTerribleFailure(tag, (TerribleFailure) terribleFailure, false);
} else {
reflector(TerribleFailureHandlerReflector.class, terribleFailureHandler)
- .onTerribleFailure(tag, terribleFailure);
+ .onTerribleFailure(tag, (TerribleFailure) terribleFailure);
}
return 0;
}
@@ -374,7 +374,8 @@ public class ShadowLog {
@ForType(TerribleFailure.class)
interface TerribleFailureReflector {
+ // The return value should be generic because TerribleFailure is a hidden class.
@Constructor
- TerribleFailure newTerribleFailure(String msg, Throwable cause);
+ Throwable newTerribleFailure(String msg, Throwable cause);
}
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMotionEvent.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMotionEvent.java
index b36cfb55b..dc5345ace 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMotionEvent.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMotionEvent.java
@@ -28,6 +28,7 @@ import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.HiddenApi;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.InDevelopment;
import org.robolectric.annotation.RealObject;
import org.robolectric.annotation.Resetter;
import org.robolectric.res.android.NativeObjRegistry;
@@ -317,25 +318,24 @@ public class ShadowMotionEvent extends ShadowInputEvent {
int pointerCount,
PointerProperties[] pointerIds,
PointerCoords[] pointerCoords) {
- return
- nativeInitialize(
- nativePtr,
- deviceId,
- source,
- action,
- flags,
- edgeFlags,
- metaState,
- buttonState,
- xOffset,
- yOffset,
- xPrecision,
- yPrecision,
- downTimeNanos,
- eventTimeNanos,
- pointerCount,
- pointerIds,
- pointerCoords);
+ return nativeInitialize(
+ nativePtr,
+ deviceId,
+ source,
+ action,
+ flags,
+ edgeFlags,
+ metaState,
+ buttonState,
+ xOffset,
+ yOffset,
+ xPrecision,
+ yPrecision,
+ downTimeNanos,
+ eventTimeNanos,
+ pointerCount,
+ pointerIds,
+ pointerCoords);
}
@Implementation(maxSdk = KITKAT_WATCH)
@@ -816,6 +816,7 @@ public class ShadowMotionEvent extends ShadowInputEvent {
@Implementation(minSdk = LOLLIPOP)
@HiddenApi
+ @InDevelopment
protected static float nativeGetXOffset(long nativePtr) {
NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr);
return event.getXOffset();
@@ -829,6 +830,7 @@ public class ShadowMotionEvent extends ShadowInputEvent {
@Implementation(minSdk = LOLLIPOP)
@HiddenApi
+ @InDevelopment
protected static float nativeGetYOffset(long nativePtr) {
NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr);
return event.getYOffset();
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPaint.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPaint.java
index f064905d8..8a3eaee0f 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPaint.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPaint.java
@@ -23,6 +23,7 @@ import android.graphics.Shader;
import android.graphics.Typeface;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.InDevelopment;
import org.robolectric.annotation.RealObject;
import org.robolectric.annotation.TextLayoutMode;
import org.robolectric.config.ConfigurationRegistry;
@@ -574,6 +575,7 @@ public class ShadowPaint {
}
@Implementation(minSdk = V.SDK_INT)
+ @InDevelopment
protected static float nGetRunCharacterAdvance(
Object /* long */ paintPtr,
Object /* char[] */ text,
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSpeechRecognizer.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSpeechRecognizer.java
index dc206ae80..ec5a63cc4 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSpeechRecognizer.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSpeechRecognizer.java
@@ -21,6 +21,7 @@ import java.util.Queue;
import java.util.concurrent.Executor;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.InDevelopment;
import org.robolectric.annotation.RealObject;
import org.robolectric.annotation.Resetter;
import org.robolectric.util.ReflectionHelpers;
@@ -114,6 +115,7 @@ public class ShadowSpeechRecognizer {
* sets the latest SpeechRecognizer.
*/
@Implementation(maxSdk = U.SDK_INT) // TODO(hoisie): Update this to support Android V
+ @InDevelopment
protected void handleChangeListener(RecognitionListener listener) {
recognitionListener = listener;
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSurfaceView.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSurfaceView.java
index 7a258dad8..2a2dec73b 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSurfaceView.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSurfaceView.java
@@ -1,6 +1,7 @@
package org.robolectric.shadows;
import android.graphics.Canvas;
+import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.view.Surface;
import android.view.SurfaceHolder;
@@ -32,6 +33,8 @@ public class ShadowSurfaceView extends ShadowView {
*/
public static class FakeSurfaceHolder implements SurfaceHolder {
private final Set<Callback> callbacks = new HashSet<>();
+ // The default format is RGB_565.
+ private int requestedFormat = PixelFormat.RGB_565;
@Override
public void addCallback(Callback callback) {
@@ -65,7 +68,12 @@ public class ShadowSurfaceView extends ShadowView {
}
@Override
- public void setFormat(int i) {
+ public void setFormat(int format) {
+ if (format == PixelFormat.OPAQUE) {
+ requestedFormat = PixelFormat.RGB_565;
+ } else {
+ requestedFormat = format;
+ }
}
@Override
@@ -95,5 +103,15 @@ public class ShadowSurfaceView extends ShadowView {
public Surface getSurface() {
return null;
}
+
+ /**
+ * Retrieve the requested format by the developers or by Android Frameworks internal logic.
+ *
+ * @return The requested format, and the default value is {@link PixelFormat#RGB_565}.
+ * @see PixelFormat
+ */
+ public int getRequestedFormat() {
+ return requestedFormat;
+ }
}
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTelephonyManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTelephonyManager.java
index 871e9f3a8..4c8c369fa 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTelephonyManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTelephonyManager.java
@@ -153,6 +153,7 @@ public class ShadowTelephonyManager {
private static final Map<Integer, String> simCountryIsoMap =
Collections.synchronizedMap(new LinkedHashMap<>());
private int simCarrierId;
+ private CharSequence simCarrierIdName;
private int carrierIdFromSimMccMnc;
private String subscriberId;
private static volatile /*UiccSlotInfo[]*/ Object uiccSlotInfos;
@@ -1225,6 +1226,16 @@ public class ShadowTelephonyManager {
this.simCarrierId = simCarrierId;
}
+ @Implementation(minSdk = P)
+ protected CharSequence getSimCarrierIdName() {
+ return simCarrierIdName;
+ }
+
+ /** Sets the value to be returned by {@link #getSimCarrierIdName()}. */
+ public void setSimCarrierIdName(CharSequence simCarrierIdName) {
+ this.simCarrierIdName = simCarrierIdName;
+ }
+
@Implementation
protected String getSubscriberId() {
checkReadPhoneStatePermission();
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTrace.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTrace.java
index 63a7d7c8c..5d8b52be7 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTrace.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTrace.java
@@ -18,6 +18,7 @@ import java.util.Set;
import java.util.function.Supplier;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.InDevelopment;
import org.robolectric.annotation.Resetter;
/**
@@ -112,6 +113,7 @@ public class ShadowTrace {
}
@Implementation
+ @InDevelopment
protected static long nativeGetEnabledTags() {
return tags;
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowViewConfiguration.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowViewConfiguration.java
index d2c28b87a..33f800f7d 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowViewConfiguration.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowViewConfiguration.java
@@ -37,25 +37,16 @@ import org.robolectric.util.reflector.ForType;
public class ShadowViewConfiguration {
private static final int SCROLL_BAR_SIZE = 10;
- private static final int SCROLL_BAR_FADE_DURATION = 250;
- private static final int SCROLL_BAR_DEFAULT_DELAY = 300;
- private static final int FADING_EDGE_LENGTH = 12;
private static final int PRESSED_STATE_DURATION = 125;
private static final int LONG_PRESS_TIMEOUT = 500;
- private static final int GLOBAL_ACTIONS_KEY_TIMEOUT = 500;
private static final int TAP_TIMEOUT = 115;
- private static final int JUMP_TAP_TIMEOUT = 500;
private static final int DOUBLE_TAP_TIMEOUT = 300;
- private static final int ZOOM_CONTROLS_TIMEOUT = 3000;
- private static final int EDGE_SLOP = 12;
private static final int TOUCH_SLOP = 16;
private static final int PAGING_TOUCH_SLOP = TOUCH_SLOP * 2;
private static final int DOUBLE_TAP_SLOP = 100;
private static final int WINDOW_TOUCH_SLOP = 16;
- private static final int MINIMUM_FLING_VELOCITY = 50;
private static final int MAXIMUM_FLING_VELOCITY = 4000;
private static final int MAXIMUM_DRAWING_CACHE_SIZE = 320 * 480 * 4;
- private static final float SCROLL_FRICTION = 0.015f;
private int edgeSlop;
private int fadingEdgeLength;
@@ -72,10 +63,10 @@ public class ShadowViewConfiguration {
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
float density = metrics.density;
- edgeSlop = (int) (density * EDGE_SLOP + 0.5f);
- fadingEdgeLength = (int) (density * FADING_EDGE_LENGTH + 0.5f);
- minimumFlingVelocity = (int) (density * MINIMUM_FLING_VELOCITY + 0.5f);
- maximumFlingVelocity = (int) (density * MAXIMUM_FLING_VELOCITY + 0.5f);
+ edgeSlop = (int) (density * ViewConfiguration.getEdgeSlop() + 0.5f);
+ fadingEdgeLength = (int) (density * ViewConfiguration.getFadingEdgeLength() + 0.5f);
+ minimumFlingVelocity = (int) (density * ViewConfiguration.getMinimumFlingVelocity() + 0.5f);
+ maximumFlingVelocity = (int) (density * ViewConfiguration.getMaximumFlingVelocity() + 0.5f);
scrollbarSize = (int) (density * SCROLL_BAR_SIZE + 0.5f);
touchSlop = (int) (density * TOUCH_SLOP + 0.5f);
pagingTouchSlop = (int) (density * PAGING_TOUCH_SLOP + 0.5f);
@@ -108,21 +99,6 @@ public class ShadowViewConfiguration {
}
@Implementation
- protected static int getScrollBarFadeDuration() {
- return SCROLL_BAR_FADE_DURATION;
- }
-
- @Implementation
- protected static int getScrollDefaultDelay() {
- return SCROLL_BAR_DEFAULT_DELAY;
- }
-
- @Implementation
- protected static int getFadingEdgeLength() {
- return FADING_EDGE_LENGTH;
- }
-
- @Implementation
protected int getScaledFadingEdgeLength() {
return fadingEdgeLength;
}
@@ -143,21 +119,11 @@ public class ShadowViewConfiguration {
}
@Implementation
- protected static int getJumpTapTimeout() {
- return JUMP_TAP_TIMEOUT;
- }
-
- @Implementation
protected static int getDoubleTapTimeout() {
return DOUBLE_TAP_TIMEOUT;
}
@Implementation
- protected static int getEdgeSlop() {
- return EDGE_SLOP;
- }
-
- @Implementation
protected int getScaledEdgeSlop() {
return edgeSlop;
}
@@ -193,11 +159,6 @@ public class ShadowViewConfiguration {
}
@Implementation
- protected static int getMinimumFlingVelocity() {
- return MINIMUM_FLING_VELOCITY;
- }
-
- @Implementation
protected int getScaledMinimumFlingVelocity() {
return minimumFlingVelocity;
}
@@ -218,21 +179,6 @@ public class ShadowViewConfiguration {
}
@Implementation
- protected static long getZoomControlsTimeout() {
- return ZOOM_CONTROLS_TIMEOUT;
- }
-
- @Implementation
- protected static long getGlobalActionKeyTimeout() {
- return GLOBAL_ACTIONS_KEY_TIMEOUT;
- }
-
- @Implementation
- protected static float getScrollFriction() {
- return SCROLL_FRICTION;
- }
-
- @Implementation
protected boolean hasPermanentMenuKey() {
return hasPermanentMenuKey;
}
diff --git a/shadows/playservices/build.gradle b/shadows/playservices/build.gradle
index b00983893..6b6759c33 100644
--- a/shadows/playservices/build.gradle
+++ b/shadows/playservices/build.gradle
@@ -16,18 +16,18 @@ dependencies {
api project(":annotations")
api libs.guava
- compileOnly libs.bundles.play.services.base.for.shadows
+ compileOnly libs.bundles.play.services.for.shadows
compileOnly AndroidSdk.MAX_SDK.coordinates
testCompileOnly AndroidSdk.MAX_SDK.coordinates
- testCompileOnly libs.bundles.play.services.base.for.shadows
+ testCompileOnly libs.bundles.play.services.for.shadows
testImplementation project(":robolectric")
testImplementation libs.junit4
testImplementation libs.truth
testImplementation libs.mockito
- testRuntimeOnly libs.bundles.play.services.base.for.shadows
+ testRuntimeOnly libs.bundles.play.services.for.shadows
testRuntimeOnly AndroidSdk.MAX_SDK.coordinates
}
diff --git a/shadows/playservices/src/main/java/org/robolectric/shadows/gms/ShadowGooglePlayServicesUtil.java b/shadows/playservices/src/main/java/org/robolectric/shadows/gms/ShadowGooglePlayServicesUtil.java
index deb738b27..25ed90e1a 100644
--- a/shadows/playservices/src/main/java/org/robolectric/shadows/gms/ShadowGooglePlayServicesUtil.java
+++ b/shadows/playservices/src/main/java/org/robolectric/shadows/gms/ShadowGooglePlayServicesUtil.java
@@ -93,11 +93,6 @@ public class ShadowGooglePlayServicesUtil {
}
@Implementation
- public static synchronized String getOpenSourceSoftwareLicenseInfo(Context context) {
- return googlePlayServicesUtilImpl.getOpenSourceSoftwareLicenseInfo(context);
- }
-
- @Implementation
public static synchronized int isGooglePlayServicesAvailable(Context context) {
return googlePlayServicesUtilImpl.isGooglePlayServicesAvailable(context);
}
@@ -133,10 +128,6 @@ public class ShadowGooglePlayServicesUtil {
context, requestCode, new Intent(), PendingIntent.FLAG_CANCEL_CURRENT);
}
- public String getOpenSourceSoftwareLicenseInfo(Context context) {
- return "license";
- }
-
public Context getRemoteContext(Context context) {
return RuntimeEnvironment.getApplication();
}
diff --git a/shadows/playservices/src/main/java/org/robolectric/shadows/gms/common/ShadowGoogleApiAvailability.java b/shadows/playservices/src/main/java/org/robolectric/shadows/gms/common/ShadowGoogleApiAvailability.java
index cb9bf194b..867184a39 100644
--- a/shadows/playservices/src/main/java/org/robolectric/shadows/gms/common/ShadowGoogleApiAvailability.java
+++ b/shadows/playservices/src/main/java/org/robolectric/shadows/gms/common/ShadowGoogleApiAvailability.java
@@ -45,10 +45,6 @@ public class ShadowGoogleApiAvailability {
return openSourceSoftwareLicenseInfo;
}
- public void setOpenSourceSoftwareLicenseInfo(final String openSourceSoftwareLicenseInfo){
- this.openSourceSoftwareLicenseInfo = openSourceSoftwareLicenseInfo;
- }
-
@Implementation
public Dialog getErrorDialog(Activity activity, int errorCode, int requestCode) {
return errorDialog;
diff --git a/shadows/playservices/src/test/java/org/robolectric/shadows/gms/ShadowGooglePlayServicesUtilTest.java b/shadows/playservices/src/test/java/org/robolectric/shadows/gms/ShadowGooglePlayServicesUtilTest.java
index cd75b9632..781072d51 100644
--- a/shadows/playservices/src/test/java/org/robolectric/shadows/gms/ShadowGooglePlayServicesUtilTest.java
+++ b/shadows/playservices/src/test/java/org/robolectric/shadows/gms/ShadowGooglePlayServicesUtilTest.java
@@ -107,13 +107,6 @@ public class ShadowGooglePlayServicesUtilTest {
}
@Test
- public void getOpenSourceSoftwareLicenseInfo_defaultNotNull() {
- assertNotNull(
- GooglePlayServicesUtil.getOpenSourceSoftwareLicenseInfo(
- RuntimeEnvironment.getApplication()));
- }
-
- @Test
public void isGooglePlayServicesAvailable_defaultServiceMissing() {
assertEquals(
ConnectionResult.SERVICE_MISSING,
diff --git a/shadows/playservices/src/test/java/org/robolectric/shadows/gms/common/ShadowGoogleApiAvailabilityTest.java b/shadows/playservices/src/test/java/org/robolectric/shadows/gms/common/ShadowGoogleApiAvailabilityTest.java
index 7e12ec712..796b8d933 100644
--- a/shadows/playservices/src/test/java/org/robolectric/shadows/gms/common/ShadowGoogleApiAvailabilityTest.java
+++ b/shadows/playservices/src/test/java/org/robolectric/shadows/gms/common/ShadowGoogleApiAvailabilityTest.java
@@ -89,23 +89,6 @@ public class ShadowGoogleApiAvailabilityTest {
}
@Test
- public void setOpenSourceSoftwareLicenseInfo() {
- //Given mock open source license info
- final String expected = "Mock open source license info";
- final ShadowGoogleApiAvailability shadowGoogleApiAvailability
- = Shadows.shadowOf(GoogleApiAvailability.getInstance());
- shadowGoogleApiAvailability.setOpenSourceSoftwareLicenseInfo(expected);
-
- //When getting the actual value
- final String actual = GoogleApiAvailability.getInstance()
- .getOpenSourceSoftwareLicenseInfo(roboContext);
-
- //Then verify that its not null, not empty, and equal to the expected value
- assertThat(actual)
- .isEqualTo(expected);
- }
-
- @Test
public void setErrorDialog(){
final ShadowGoogleApiAvailability shadowGoogleApiAvailability
= Shadows.shadowOf(GoogleApiAvailability.getInstance());
diff --git a/testapp/src/main/AndroidManifest.xml b/testapp/src/main/AndroidManifest.xml
index d594f2323..9ff190737 100644
--- a/testapp/src/main/AndroidManifest.xml
+++ b/testapp/src/main/AndroidManifest.xml
@@ -1,8 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="org.robolectric.testapp"
- android:versionCode="123"
- android:versionName="aVersionName">
+ package="org.robolectric.testapp">
<application
android:theme="@style/Theme.Robolectric" android:enabled="true">
diff --git a/testapp/src/main/res/font/downloadable.xml b/testapp/src/main/res/font/downloadable.xml
index 9caa5388d..3cda34a90 100644
--- a/testapp/src/main/res/font/downloadable.xml
+++ b/testapp/src/main/res/font/downloadable.xml
@@ -1,5 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<font-family xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ tools:ignore="FontValidation"
android:fontProviderAuthority="com.example.fontprovider.authority"
android:fontProviderPackage="com.example.fontprovider"
android:fontProviderQuery="example font"/>
diff --git a/utils/src/main/java/org/robolectric/util/TempDirectory.java b/utils/src/main/java/org/robolectric/util/TempDirectory.java
index b45438a05..cffcbcb59 100644
--- a/utils/src/main/java/org/robolectric/util/TempDirectory.java
+++ b/utils/src/main/java/org/robolectric/util/TempDirectory.java
@@ -13,10 +13,8 @@ import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
-import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
-import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.concurrent.ExecutorService;
@@ -35,15 +33,6 @@ public class TempDirectory {
*/
private static final int DELETE_THREAD_POOL_SIZE = 5;
- /**
- * Assets related to the Robolectric Native Runtime (shared object files, ICU dat file, fonts) are
- * extracted from a Jar file into a TempDirectory during runtime in order to be used. On Windows,
- * it is not possible for the current JVM process to delete certain files due to OS constraints.
- */
- @SuppressWarnings("ConstantCaseForConstants")
- private static final List<String> KNOWN_WINDOWS_UNDELETEABLE_FILES =
- Collections.unmodifiableList(Arrays.asList("robolectric-nativeruntime.dll", "icudt68l.dat"));
-
private static final String TEMP_DIR_PREFIX = "robolectric-";
static final String OBSOLETE_MARKER_FILE_NAME = ".obsolete";
@@ -153,15 +142,15 @@ public class TempDirectory {
Files.delete(basePath);
} catch (IOException e) {
if (isWindows()) {
+ // Windows is much more protective of files that have been opened in native code. For
+ // instance, unlike in Mac and Linux, it's not possible to delete nativeruntime files
+ // (dlls, fonts, icu data) in the same process where they were opened. Because of
+ // this, we need extra cleanup logic for Windows, and we avoid logging to prevent noise
+ // and confusion.
createFile(OBSOLETE_MARKER_FILE_NAME, "");
- // Avoid spurious logging.
- for (String s : KNOWN_WINDOWS_UNDELETEABLE_FILES) {
- if (e.getLocalizedMessage().contains(s)) {
- return;
- }
- }
+ } else {
+ Logger.error("Failed to destroy temp directory", e);
}
- Logger.error("Failed to destroy temp directory", e);
}
}
diff --git a/utils/src/test/java/org/robolectric/util/TempDirectoryTest.kt b/utils/src/test/java/org/robolectric/util/TempDirectoryTest.kt
index 5692400c1..f9b172318 100644
--- a/utils/src/test/java/org/robolectric/util/TempDirectoryTest.kt
+++ b/utils/src/test/java/org/robolectric/util/TempDirectoryTest.kt
@@ -69,7 +69,6 @@ class TempDirectoryTest {
if (!Files.exists(path)) {
latch.countDown()
} else {
- System.err.println("DSP rescheduling")
val ignored =
service.schedule(
{ waitForDirectoryDeletion(path, latch, service) },