diff options
59 files changed, 1224 insertions, 2663 deletions
diff --git a/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ClientSocketPerfTest.java b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ClientSocketPerfTest.java index 3577fcdf04d6..f20b1706129b 100644 --- a/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ClientSocketPerfTest.java +++ b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ClientSocketPerfTest.java @@ -194,7 +194,7 @@ public final class ClientSocketPerfTest { /** * Simple benchmark for the amount of time to send a given number of messages */ - // @Test Temporarily disabled + @Test @Parameters(method = "getParams") public void time(Config config) throws Exception { reset(); diff --git a/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ServerSocketPerfTest.java b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ServerSocketPerfTest.java index ac5710047db9..af3c405eab82 100644 --- a/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ServerSocketPerfTest.java +++ b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ServerSocketPerfTest.java @@ -198,7 +198,7 @@ public final class ServerSocketPerfTest { executor.awaitTermination(5, TimeUnit.SECONDS); } - // @Test Temporarily disabled + @Test @Parameters(method = "getParams") public void throughput(Config config) throws Exception { setup(config); diff --git a/apex/jobscheduler/service/java/com/android/server/AppStateTrackerImpl.java b/apex/jobscheduler/service/java/com/android/server/AppStateTrackerImpl.java index 33f6899239c6..ecb9a738aa32 100644 --- a/apex/jobscheduler/service/java/com/android/server/AppStateTrackerImpl.java +++ b/apex/jobscheduler/service/java/com/android/server/AppStateTrackerImpl.java @@ -444,8 +444,13 @@ public class AppStateTrackerImpl implements AppStateTracker { private final BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { + final String action = intent.getAction(); + if (action == null) { + return; + } + final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); - switch (intent.getAction()) { + switch (action) { case Intent.ACTION_USER_REMOVED: if (userId > 0) { mHandler.doUserRemoved(userId); diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java index e3ac780abf09..7a21697331af 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java @@ -84,9 +84,13 @@ public final class BackgroundJobsController extends StateController { private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { + final String action = intent.getAction(); + if (action == null) { + return; + } + final String pkgName = getPackageName(intent); final int pkgUid = intent.getIntExtra(Intent.EXTRA_UID, -1); - final String action = intent.getAction(); if (pkgUid == -1) { Slog.e(TAG, "Didn't get package UID in intent (" + action + ")"); return; diff --git a/core/api/current.txt b/core/api/current.txt index 2c4c1463ba62..6b342a541c97 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -16232,6 +16232,7 @@ package android.graphics { field public static final int UNKNOWN = 0; // 0x0 field public static final int Y8 = 538982489; // 0x20203859 field public static final int YCBCR_P010 = 54; // 0x36 + field @FlaggedApi("android.media.codec.p210_format_support") public static final int YCBCR_P210 = 60; // 0x3c field public static final int YUV_420_888 = 35; // 0x23 field public static final int YUV_422_888 = 39; // 0x27 field public static final int YUV_444_888 = 40; // 0x28 @@ -18580,6 +18581,7 @@ package android.hardware { field public static final long USAGE_VIDEO_ENCODE = 65536L; // 0x10000L field public static final int YCBCR_420_888 = 35; // 0x23 field public static final int YCBCR_P010 = 54; // 0x36 + field @FlaggedApi("android.media.codec.p210_format_support") public static final int YCBCR_P210 = 60; // 0x3c } @FlaggedApi("android.hardware.flags.overlayproperties_class_api") public final class OverlayProperties implements android.os.Parcelable { @@ -22871,6 +22873,7 @@ package android.media { field public static final int COLOR_FormatYUV444Flexible = 2135181448; // 0x7f444888 field @Deprecated public static final int COLOR_FormatYUV444Interleaved = 29; // 0x1d field public static final int COLOR_FormatYUVP010 = 54; // 0x36 + field @FlaggedApi("android.media.codec.p210_format_support") public static final int COLOR_FormatYUVP210 = 60; // 0x3c field @Deprecated public static final int COLOR_QCOM_FormatYUV420SemiPlanar = 2141391872; // 0x7fa30c00 field @Deprecated public static final int COLOR_TI_FormatYUV420PackedSemiPlanar = 2130706688; // 0x7f000100 field public static final String FEATURE_AdaptivePlayback = "adaptive-playback"; diff --git a/core/java/android/app/PropertyInvalidatedCache.java b/core/java/android/app/PropertyInvalidatedCache.java index 27f9f54d9522..cb31bc73b9ba 100644 --- a/core/java/android/app/PropertyInvalidatedCache.java +++ b/core/java/android/app/PropertyInvalidatedCache.java @@ -250,6 +250,7 @@ import java.util.concurrent.atomic.AtomicLong; * @hide */ @TestApi +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class PropertyInvalidatedCache<Query, Result> { /** * This is a configuration class that customizes a cache instance. diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index bca30b4fec5a..8de86d5b60a0 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -146,6 +146,7 @@ public abstract class PackageManager { * This exception is thrown when a given package, application, or component * name cannot be found. */ + @android.ravenwood.annotation.RavenwoodKeepWholeClass public static class NameNotFoundException extends AndroidException { public NameNotFoundException() { } diff --git a/core/java/android/hardware/HardwareBuffer.java b/core/java/android/hardware/HardwareBuffer.java index ce0f9f598897..0e73978127c9 100644 --- a/core/java/android/hardware/HardwareBuffer.java +++ b/core/java/android/hardware/HardwareBuffer.java @@ -66,6 +66,7 @@ public final class HardwareBuffer implements Parcelable, AutoCloseable { DS_FP32UI8, S_UI8, YCBCR_P010, + YCBCR_P210, R_8, R_16, RG_1616, @@ -111,6 +112,16 @@ public final class HardwareBuffer implements Parcelable, AutoCloseable { * little-endian value, with the lower 6 bits set to zero. */ public static final int YCBCR_P010 = 0x36; + /** + * <p>Android YUV P210 format.</p> + * + * P210 is a 4:2:2 YCbCr semiplanar format comprised of a WxH Y plane + * followed by a WxH CbCr plane. Each sample is represented by a 16-bit + * little-endian value, with the lower 6 bits set to zero. + */ + @FlaggedApi(android.media.codec.Flags.FLAG_P210_FORMAT_SUPPORT) + public static final int YCBCR_P210 = 0x3c; + /** Format: 8 bits red */ @FlaggedApi(com.android.graphics.hwui.flags.Flags.FLAG_REQUESTED_FORMATS_V) public static final int R_8 = 0x38; diff --git a/core/java/android/os/IpcDataCache.java b/core/java/android/os/IpcDataCache.java index bf44d65c4002..d7a308df4e6e 100644 --- a/core/java/android/os/IpcDataCache.java +++ b/core/java/android/os/IpcDataCache.java @@ -242,6 +242,7 @@ import java.util.concurrent.atomic.AtomicLong; */ @TestApi @SystemApi(client=SystemApi.Client.MODULE_LIBRARIES) +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class IpcDataCache<Query, Result> extends PropertyInvalidatedCache<Query, Result> { /** * {@inheritDoc} diff --git a/core/jni/android_text_Hyphenator.cpp b/core/jni/android_text_Hyphenator.cpp index 89fdeeb078d0..933781c3e924 100644 --- a/core/jni/android_text_Hyphenator.cpp +++ b/core/jni/android_text_Hyphenator.cpp @@ -14,17 +14,19 @@ * limitations under the License. */ +#include <core_jni_helpers.h> +#include <cutils/trace.h> #include <fcntl.h> +#include <minikin/Hyphenator.h> #include <sys/mman.h> #include <sys/stat.h> #include <sys/types.h> +#include <tracing_perfetto.h> +#include <unicode/uloc.h> #include <unistd.h> #include <algorithm> -#include <core_jni_helpers.h> -#include <minikin/Hyphenator.h> - namespace android { static std::string buildFileName(const std::string& locale) { @@ -79,6 +81,23 @@ static void addHyphenatorAlias(const std::string& from, const std::string& to) { minikin::addHyphenatorAlias(from, to); } +/* + * Cache the subtag key map by calling uloc_forLanguageTag with a subtag. + * minikin calls uloc_forLanguageTag with an Unicode extension specifying + * the line breaking strictness. Parsing the extension requires loading the key map + * from keyTypeData.res in the ICU. + * "lb" is the key commonly used by minikin. "ca" is a common legacy key mapping to + * the "calendar" key. It ensures that the key map is loaded and cached in icu4c. + * "en-Latn-US" is a common locale used in the Android system regardless what default locale + * is selected in the Settings app. + */ +inline static void cacheUnicodeExtensionSubtagsKeyMap() { + UErrorCode status = U_ZERO_ERROR; + char localeID[ULOC_FULLNAME_CAPACITY] = {}; + uloc_forLanguageTag("en-Latn-US-u-lb-loose-ca-gregory", localeID, ULOC_FULLNAME_CAPACITY, + nullptr, &status); +} + static void init() { // TODO: Confirm that these are the best values. Various sources suggest (1, 1), but that // appears too small. @@ -190,6 +209,10 @@ static void init() { addHyphenatorAlias("und-Orya", "or"); // Oriya addHyphenatorAlias("und-Taml", "ta"); // Tamil addHyphenatorAlias("und-Telu", "te"); // Telugu + + tracing_perfetto::traceBegin(ATRACE_TAG_VIEW, "CacheUnicodeExtensionSubtagsKeyMap"); + cacheUnicodeExtensionSubtagsKeyMap(); + tracing_perfetto::traceEnd(ATRACE_TAG_VIEW); // CacheUnicodeExtensionSubtagsKeyMap } static const JNINativeMethod gMethods[] = { diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp index 7af307317fc5..b1c48ab39396 100644 --- a/core/tests/coretests/Android.bp +++ b/core/tests/coretests/Android.bp @@ -256,6 +256,7 @@ android_ravenwood_test { "src/android/content/ContextTest.java", "src/android/content/pm/PackageManagerTest.java", "src/android/content/pm/UserInfoTest.java", + "src/android/app/PropertyInvalidatedCacheTests.java", "src/android/database/CursorWindowTest.java", "src/android/os/**/*.java", "src/android/telephony/PinResultTest.java", diff --git a/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java b/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java index cd6abddc20a1..95a4fe4811f0 100644 --- a/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java +++ b/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java @@ -39,11 +39,7 @@ import org.junit.Test; * atest FrameworksCoreTests:PropertyInvalidatedCacheTests */ @SmallTest -@IgnoreUnderRavenwood(blockedBy = PropertyInvalidatedCache.class) public class PropertyInvalidatedCacheTests { - @Rule - public final RavenwoodRule mRavenwood = new RavenwoodRule(); - // Configuration for creating caches private static final String MODULE = PropertyInvalidatedCache.MODULE_TEST; private static final String API = "testApi"; diff --git a/core/tests/coretests/src/android/os/IpcDataCacheTest.java b/core/tests/coretests/src/android/os/IpcDataCacheTest.java index b03fd6485786..5edf0cad1965 100644 --- a/core/tests/coretests/src/android/os/IpcDataCacheTest.java +++ b/core/tests/coretests/src/android/os/IpcDataCacheTest.java @@ -37,10 +37,7 @@ import org.junit.Test; * atest FrameworksCoreTests:IpcDataCacheTest */ @SmallTest -@IgnoreUnderRavenwood(blockedBy = IpcDataCache.class) public class IpcDataCacheTest { - @Rule - public final RavenwoodRule mRavenwood = new RavenwoodRule(); // Configuration for creating caches private static final String MODULE = IpcDataCache.MODULE_TEST; diff --git a/graphics/java/android/graphics/ImageFormat.java b/graphics/java/android/graphics/ImageFormat.java index cb3b64c3e6cd..93d94c9cd7eb 100644 --- a/graphics/java/android/graphics/ImageFormat.java +++ b/graphics/java/android/graphics/ImageFormat.java @@ -16,6 +16,7 @@ package android.graphics; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import java.lang.annotation.Retention; @@ -41,6 +42,7 @@ public class ImageFormat { Y8, Y16, YCBCR_P010, + YCBCR_P210, NV16, NV21, YUY2, @@ -206,6 +208,26 @@ public class ImageFormat { public static final int YCBCR_P010 = 0x36; /** + * <p>Android YUV P210 format.</p> + * + * P210 is a 4:2:2 YCbCr semiplanar format comprised of a WxH Y plane + * followed by a WxH CbCr plane. Each sample is represented by a 16-bit + * little-endian value, with the lower 6 bits set to zero. + * + * <p>For example, the {@link android.media.Image} object can provide data + * in this format from a {@link android.hardware.camera2.CameraDevice} + * through a {@link android.media.ImageReader} object if this format is + * supported by {@link android.hardware.camera2.CameraDevice}.</p> + * + * @see android.media.Image + * @see android.media.ImageReader + * @see android.hardware.camera2.CameraDevice + * + */ + @FlaggedApi(android.media.codec.Flags.FLAG_P210_FORMAT_SUPPORT) + public static final int YCBCR_P210 = 0x3c; + + /** * YCbCr format, used for video. * * <p>For the {@link android.hardware.camera2} API, the {@link #YUV_420_888} format is @@ -849,6 +871,8 @@ public class ImageFormat { return 16; case YCBCR_P010: return 24; + case YCBCR_P210: + return 32; case RAW_DEPTH10: case RAW10: return 10; @@ -899,7 +923,9 @@ public class ImageFormat { case JPEG_R: return true; } - + if (android.media.codec.Flags.p210FormatSupport() && format == YCBCR_P210) { + return true; + } return false; } } diff --git a/keystore/java/android/security/keystore/KeyProperties.java b/keystore/java/android/security/keystore/KeyProperties.java index 7f936f28ac4f..344d19af9173 100644 --- a/keystore/java/android/security/keystore/KeyProperties.java +++ b/keystore/java/android/security/keystore/KeyProperties.java @@ -23,9 +23,6 @@ import android.annotation.StringDef; import android.annotation.SystemApi; import android.os.Process; import android.security.keymaster.KeymasterDefs; - -import libcore.util.EmptyArray; - import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.security.spec.AlgorithmParameterSpec; @@ -33,6 +30,7 @@ import java.security.spec.ECParameterSpec; import java.security.spec.MGF1ParameterSpec; import java.util.Collection; import java.util.Locale; +import libcore.util.EmptyArray; /** * Properties of <a href="{@docRoot}training/articles/keystore.html">Android Keystore</a> keys. @@ -116,7 +114,7 @@ public abstract class KeyProperties { public static final int PURPOSE_AGREE_KEY = 1 << 6; /** - * Purpose of key: Signing attestaions. This purpose is incompatible with all others, meaning + * Purpose of key: Signing attestations. This purpose is incompatible with all others, meaning * that when generating a key with PURPOSE_ATTEST_KEY, no other purposes may be specified. In * addition, PURPOSE_ATTEST_KEY may not be specified for imported keys. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/OWNERS new file mode 100644 index 000000000000..752d2fd721a5 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/OWNERS @@ -0,0 +1,2 @@ +# WM Shell sub-module dagger owners +jorgegil@google.com
\ No newline at end of file diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java index 8ff4305a9817..3a19f466f7c1 100644 --- a/media/java/android/media/MediaCodecInfo.java +++ b/media/java/android/media/MediaCodecInfo.java @@ -462,6 +462,33 @@ public final class MediaCodecInfo { @SuppressLint("AllUpper") public static final int COLOR_FormatYUVP010 = 54; + /** + * P210 is 10-bit-per component 4:2:2 YCbCr semiplanar format. + * <p> + * This format uses 32 allocated bits per pixel with 20 bits of + * data per pixel. Chroma planes are subsampled by 2 both + * horizontally. Each chroma and luma component + * has 16 allocated bits in little-endian configuration with 10 + * MSB of actual data. + * + * <pre> + * byte byte + * <--------- i --------> | <------ i + 1 ------> + * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + * | UNUSED | Y/Cb/Cr | + * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + * 0 5 6 7 0 7 + * bit + * </pre> + * + * Use this format with {@link Image}. This format corresponds + * to {@link android.graphics.ImageFormat#YCBCR_P210}. + * <p> + */ + @SuppressLint("AllUpper") + @FlaggedApi(android.media.codec.Flags.FLAG_P210_FORMAT_SUPPORT) + public static final int COLOR_FormatYUVP210 = 60; + /** @deprecated Use {@link #COLOR_FormatYUV420Flexible}. */ public static final int COLOR_TI_FormatYUV420PackedSemiPlanar = 0x7f000100; // COLOR_FormatSurface indicates that the data will be a GraphicBuffer metadata reference. diff --git a/media/java/android/media/MediaMuxer.java b/media/java/android/media/MediaMuxer.java index 5e55f64da985..678150b9f3a1 100644 --- a/media/java/android/media/MediaMuxer.java +++ b/media/java/android/media/MediaMuxer.java @@ -584,45 +584,108 @@ final public class MediaMuxer { * The following table summarizes codec support for containers across android releases: * * <table> - * <thead> - * <tr> - * <th rowspan=2>OS Version(s)</th> - * <td colspan=3>Codec support</th> - * </tr><tr> - * <th>{@linkplain OutputFormat#MUXER_OUTPUT_MPEG_4 MP4}</th> - * <th>{@linkplain OutputFormat#MUXER_OUTPUT_WEBM WEBM}</th> - * </tr> - * </thead> - * <tbody> - * <tr> - * <td>{@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}</td> - * <td rowspan=6>{@link MediaFormat#MIMETYPE_AUDIO_AAC AAC},<br> - * {@link MediaFormat#MIMETYPE_AUDIO_AMR_NB NB-AMR},<br> - * {@link MediaFormat#MIMETYPE_AUDIO_AMR_WB WB-AMR},<br> - * {@link MediaFormat#MIMETYPE_VIDEO_H263 H.263},<br> - * {@link MediaFormat#MIMETYPE_VIDEO_MPEG4 MPEG-4},<br> - * {@link MediaFormat#MIMETYPE_VIDEO_AVC AVC} (H.264)</td> - * <td rowspan=3>Not supported</td> - * </tr><tr> - * <td>{@link android.os.Build.VERSION_CODES#KITKAT}</td> - * </tr><tr> - * <td>{@link android.os.Build.VERSION_CODES#KITKAT_WATCH}</td> - * </tr><tr> - * <td>{@link android.os.Build.VERSION_CODES#LOLLIPOP}</td> - * <td rowspan=3>{@link MediaFormat#MIMETYPE_AUDIO_VORBIS Vorbis},<br> - * {@link MediaFormat#MIMETYPE_VIDEO_VP8 VP8}</td> - * </tr><tr> - * <td>{@link android.os.Build.VERSION_CODES#LOLLIPOP_MR1}</td> - * </tr><tr> - * <td>{@link android.os.Build.VERSION_CODES#M}</td> - * </tr><tr> - * <td>{@link android.os.Build.VERSION_CODES#N}</td> - * <td>as above, plus<br> - * {@link MediaFormat#MIMETYPE_VIDEO_HEVC HEVC} (H.265)</td> - * <td>as above, plus<br> - * {@link MediaFormat#MIMETYPE_VIDEO_VP9 VP9}</td> - * </tr> - * </tbody> + * <thead> + * <tr> + * <th>Codec</th> + * <th>{@linkplain OutputFormat#MUXER_OUTPUT_MPEG_4 MP4}</th> + * <th>{@linkplain OutputFormat#MUXER_OUTPUT_WEBM WEBM}</th> + * <th>{@linkplain OutputFormat#MUXER_OUTPUT_OGG OGG}</th> + * <th>Supported From SDK version</th> + * </tr> + * </thead> + * <tbody> + * <tr> + * <td>{@link MediaFormat#MIMETYPE_AUDIO_AAC AAC}</td> + * <td>✓</td> + * <td></td> + * <td></td> + * <td>17</td> + * </tr> + * <tr> + * <td>{@link MediaFormat#MIMETYPE_AUDIO_AMR_NB NB-AMR}</td> + * <td>✓</td> + * <td></td> + * <td></td> + * <td>17</td> + * </tr> + * <tr> + * <td>{@link MediaFormat#MIMETYPE_AUDIO_AMR_WB WB-AMR}</td> + * <td>✓</td> + * <td></td> + * <td></td> + * <td>17</td> + * </tr> + * <tr> + * <td>{@link MediaFormat#MIMETYPE_VIDEO_H263 H.263}</td> + * <td>✓</td> + * <td></td> + * <td></td> + * <td>17</td> + * </tr> + * <tr> + * <td>{@link MediaFormat#MIMETYPE_VIDEO_MPEG4 MPEG-4}</td> + * <td>✓</td> + * <td></td> + * <td></td> + * <td>17</td> + * </tr> + * <tr> + * <td>{@link MediaFormat#MIMETYPE_VIDEO_AVC AVC} (H.264)</td> + * <td>✓</td> + * <td></td> + * <td></td> + * <td>17</td> + * </tr> + * <tr> + * <td>{@link MediaFormat#MIMETYPE_AUDIO_VORBIS Vorbis}</td> + * <td></td> + * <td>✓</td> + * <td></td> + * <td>21</td> + * </tr> + * <tr> + * <td>{@link MediaFormat#MIMETYPE_VIDEO_VP8 VP8}</td> + * <td></td> + * <td>✓</td> + * <td></td> + * <td>21</td> + * </tr> + * <tr> + * <td>{@link MediaFormat#MIMETYPE_VIDEO_VP9 VP9}</td> + * <td></td> + * <td>✓</td> + * <td></td> + * <td>24</td> + * </tr> + * <tr> + * <td>{@link MediaFormat#MIMETYPE_VIDEO_HEVC HEVC} (H.265)</td> + * <td>✓</td> + * <td></td> + * <td></td> + * <td>24</td> + * </tr> + * <tr> + * <td>{@link MediaFormat#MIMETYPE_AUDIO_OPUS OPUS}</td> + * <td></td> + * <td>✓</td> + * <td>✓</td> + * <td>26</td> + * </tr> + * <tr> + * <td>{@link MediaFormat#MIMETYPE_VIDEO_AV1 AV1}</td> + * <td>✓</td> + * <td></td> + * <td></td> + * <td>31</td> + * </tr> + * <tr> + * <td>{@link MediaFormat#MIMETYPE_VIDEO_DOLBY_VISION Dolby Vision}</td> + * <td>✓</td> + * <td></td> + * <td></td> + * <td>32</td> + * </tr> + * </tbody> * </table> * * @param format The media format for the track. This must not be an empty diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp index 9a5e623b6d45..65ea9fe3a496 100644 --- a/ravenwood/Android.bp +++ b/ravenwood/Android.bp @@ -170,7 +170,7 @@ java_library { "hoststubgen-helper-runtime.ravenwood", "mockito-ravenwood-prebuilt", ], - visibility: ["//frameworks/base"], + visibility: [":__subpackages__"], jarjar_rules: ":ravenwood-services-jarjar-rules", } diff --git a/ravenwood/TEST_MAPPING b/ravenwood/TEST_MAPPING index 72f62c5c29ee..7fa0ef114c82 100644 --- a/ravenwood/TEST_MAPPING +++ b/ravenwood/TEST_MAPPING @@ -5,8 +5,8 @@ { "name": "hoststubgen-test-tiny-test" }, { "name": "hoststubgen-invoke-test" }, { "name": "RavenwoodMockitoTest_device" }, - // TODO(b/371215487): Re-enable when the test is fixed. - // { "name": "RavenwoodBivalentTest_device" }, + { "name": "RavenwoodBivalentTest_device" }, + { "name": "RavenwoodBivalentTest_device_ravenizer" }, { "name": "RavenwoodBivalentInstTest_nonself_inst" }, { "name": "RavenwoodBivalentInstTest_self_inst_device" }, diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java new file mode 100644 index 000000000000..30a653d2da76 --- /dev/null +++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java @@ -0,0 +1,453 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.platform.test.ravenwood; + +import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_VERBOSE_LOGGING; +import static com.android.ravenwood.common.RavenwoodCommonUtils.ensureIsPublicVoidMethod; + +import static org.junit.Assert.fail; +import static org.junit.Assume.assumeTrue; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Bundle; +import android.platform.test.annotations.RavenwoodTestRunnerInitializing; +import android.platform.test.annotations.internal.InnerRunner; +import android.platform.test.ravenwood.RavenwoodTestStats.Result; +import android.util.Log; + +import androidx.test.platform.app.InstrumentationRegistry; + +import org.junit.AssumptionViolatedException; +import org.junit.rules.TestRule; +import org.junit.runner.Description; +import org.junit.runner.Runner; +import org.junit.runner.manipulation.Filter; +import org.junit.runner.manipulation.Filterable; +import org.junit.runner.manipulation.NoTestsRemainException; +import org.junit.runner.notification.Failure; +import org.junit.runner.notification.RunNotifier; +import org.junit.runners.BlockJUnit4ClassRunner; +import org.junit.runners.Suite; +import org.junit.runners.model.Statement; +import org.junit.runners.model.TestClass; + +import java.lang.annotation.Annotation; +import java.lang.reflect.InvocationTargetException; +import java.util.function.BiConsumer; + +/** + * A test runner used for Ravenwood. + * + * It will delegate to another runner specified with {@link InnerRunner} + * (default = {@link BlockJUnit4ClassRunner}) with the following features. + * - Add a called before the inner runner gets a chance to run. This can be used to initialize + * stuff used by the inner runner. + * - Add hook points with help from the four test rules such as {@link #sImplicitClassOuterRule}, + * which are also injected by the ravenizer tool. + * + * We use this runner to: + * - Initialize the Ravenwood environment. + * - Handle {@link android.platform.test.annotations.DisabledOnRavenwood}. + */ +public final class RavenwoodAwareTestRunner extends RavenwoodAwareTestRunnerBase { + public static final String TAG = "Ravenwood"; + + /** Scope of a hook. */ + public enum Scope { + Class, + Instance, + } + + /** Order of a hook. */ + public enum Order { + Outer, + Inner, + } + + private record HookRule(Scope scope, Order order) implements TestRule { + @Override + public Statement apply(Statement base, Description description) { + return getCurrentRunner().wrapWithHooks(base, description, scope, order); + } + } + + // The following four rule instances will be injected to tests by the Ravenizer tool. + public static final TestRule sImplicitClassOuterRule = new HookRule(Scope.Class, Order.Outer); + public static final TestRule sImplicitClassInnerRule = new HookRule(Scope.Class, Order.Inner); + public static final TestRule sImplicitInstOuterRule = new HookRule(Scope.Instance, Order.Outer); + public static final TestRule sImplicitInstInnerRule = new HookRule(Scope.Instance, Order.Inner); + + /** Keeps track of the runner on the current thread. */ + private static final ThreadLocal<RavenwoodAwareTestRunner> sCurrentRunner = new ThreadLocal<>(); + + static RavenwoodAwareTestRunner getCurrentRunner() { + var runner = sCurrentRunner.get(); + if (runner == null) { + throw new RuntimeException("Current test runner not set!"); + } + return runner; + } + + private final Class<?> mTestJavaClass; + private TestClass mTestClass = null; + private Runner mRealRunner = null; + private Description mDescription = null; + private Throwable mExceptionInConstructor = null; + + /** + * Stores internal states / methods associated with this runner that's only needed in + * junit-impl. + */ + final RavenwoodRunnerState mState = new RavenwoodRunnerState(this); + + public TestClass getTestClass() { + return mTestClass; + } + + /** + * Constructor. + */ + public RavenwoodAwareTestRunner(Class<?> testClass) { + RavenwoodRuntimeEnvironmentController.globalInitOnce(); + mTestJavaClass = testClass; + try { + /* + * If the class has @DisabledOnRavenwood, then we'll delegate to + * ClassSkippingTestRunner, which simply skips it. + * + * We need to do it before instantiating TestClass for b/367694651. + */ + if (!RavenwoodEnablementChecker.shouldRunClassOnRavenwood(testClass, true)) { + mRealRunner = new ClassSkippingTestRunner(testClass); + mDescription = mRealRunner.getDescription(); + return; + } + + mTestClass = new TestClass(testClass); + + Log.v(TAG, "RavenwoodAwareTestRunner starting for " + testClass.getCanonicalName()); + + onRunnerInitializing(); + + mRealRunner = instantiateRealRunner(mTestClass); + mDescription = mRealRunner.getDescription(); + } catch (Throwable th) { + // If we throw in the constructor, Tradefed may not report it and just ignore the class, + // so record it and throw it when the test actually started. + Log.e(TAG, "Fatal: Exception detected in constructor", th); + mExceptionInConstructor = new RuntimeException("Exception detected in constructor", + th); + mDescription = Description.createTestDescription(testClass, "Constructor"); + + // This is for testing if tradefed is fixed. + if ("1".equals(System.getenv("RAVENWOOD_THROW_EXCEPTION_IN_TEST_RUNNER"))) { + throw th; + } + } + } + + @Override + Runner getRealRunner() { + return mRealRunner; + } + + /** + * Run the bare minimum setup to initialize the wrapped runner. + */ + // This method is called by the ctor, so never make it virtual. + private void onRunnerInitializing() { + // This is needed to make AndroidJUnit4ClassRunner happy. + InstrumentationRegistry.registerInstance(null, Bundle.EMPTY); + + // Hook point to allow more customization. + runAnnotatedMethodsOnRavenwood(RavenwoodTestRunnerInitializing.class, null); + } + + private void runAnnotatedMethodsOnRavenwood(Class<? extends Annotation> annotationClass, + Object instance) { + Log.v(TAG, "runAnnotatedMethodsOnRavenwood() " + annotationClass.getName()); + + for (var method : getTestClass().getAnnotatedMethods(annotationClass)) { + ensureIsPublicVoidMethod(method.getMethod(), /* isStatic=*/ instance == null); + + var methodDesc = method.getDeclaringClass().getName() + "." + + method.getMethod().toString(); + try { + method.getMethod().invoke(instance); + } catch (IllegalAccessException | InvocationTargetException e) { + throw logAndFail("Caught exception while running method " + methodDesc, e); + } + } + } + + @Override + public Description getDescription() { + return mDescription; + } + + @Override + public void run(RunNotifier realNotifier) { + final var notifier = new RavenwoodRunNotifier(realNotifier); + final var description = getDescription(); + + if (mRealRunner instanceof ClassSkippingTestRunner) { + mRealRunner.run(notifier); + Log.i(TAG, "onClassSkipped: description=" + description); + RavenwoodTestStats.getInstance().onClassSkipped(description); + return; + } + + Log.v(TAG, "Starting " + mTestJavaClass.getCanonicalName()); + if (RAVENWOOD_VERBOSE_LOGGING) { + dumpDescription(description); + } + + if (maybeReportExceptionFromConstructor(notifier)) { + return; + } + + // TODO(b/365976974): handle nested classes better + final boolean skipRunnerHook = + mRealRunnerTakesRunnerBuilder && mRealRunner instanceof Suite; + + sCurrentRunner.set(this); + try { + if (!skipRunnerHook) { + try { + mState.enterTestClass(description); + } catch (Throwable th) { + notifier.reportBeforeTestFailure(description, th); + return; + } + } + + // Delegate to the inner runner. + mRealRunner.run(notifier); + } finally { + sCurrentRunner.remove(); + + if (!skipRunnerHook) { + try { + RavenwoodTestStats.getInstance().onClassFinished(description); + mState.exitTestClass(); + } catch (Throwable th) { + notifier.reportAfterTestFailure(th); + } + } + } + } + + /** Throw the exception detected in the constructor, if any. */ + private boolean maybeReportExceptionFromConstructor(RunNotifier notifier) { + if (mExceptionInConstructor == null) { + return false; + } + notifier.fireTestStarted(mDescription); + notifier.fireTestFailure(new Failure(mDescription, mExceptionInConstructor)); + notifier.fireTestFinished(mDescription); + + return true; + } + + private Statement wrapWithHooks(Statement base, Description description, Scope scope, + Order order) { + return new Statement() { + @Override + public void evaluate() throws Throwable { + runWithHooks(description, scope, order, base); + } + }; + } + + private void runWithHooks(Description description, Scope scope, Order order, Statement s) + throws Throwable { + assumeTrue(onBefore(description, scope, order)); + try { + s.evaluate(); + onAfter(description, scope, order, null); + } catch (Throwable t) { + if (onAfter(description, scope, order, t)) { + throw t; + } + } + } + + /** + * A runner that simply skips a class. It still has to support {@link Filterable} + * because otherwise the result still says "SKIPPED" even when it's not included in the + * filter. + */ + private static class ClassSkippingTestRunner extends Runner implements Filterable { + private final Description mDescription; + private boolean mFilteredOut; + + ClassSkippingTestRunner(Class<?> testClass) { + mDescription = Description.createTestDescription(testClass, testClass.getSimpleName()); + mFilteredOut = false; + } + + @Override + public Description getDescription() { + return mDescription; + } + + @Override + public void run(RunNotifier notifier) { + if (mFilteredOut) { + return; + } + notifier.fireTestSuiteStarted(mDescription); + notifier.fireTestIgnored(mDescription); + notifier.fireTestSuiteFinished(mDescription); + } + + @Override + public void filter(Filter filter) throws NoTestsRemainException { + if (filter.shouldRun(mDescription)) { + mFilteredOut = false; + } else { + throw new NoTestsRemainException(); + } + } + } + + /** + * Called before a test / class. + * + * Return false if it should be skipped. + */ + private boolean onBefore(Description description, Scope scope, Order order) { + Log.v(TAG, "onBefore: description=" + description + ", " + scope + ", " + order); + + if (scope == Scope.Instance && order == Order.Outer) { + // Start of a test method. + mState.enterTestMethod(description); + } + + final var classDescription = mState.getClassDescription(); + + // Class-level annotations are checked by the runner already, so we only check + // method-level annotations here. + if (scope == Scope.Instance && order == Order.Outer) { + if (!RavenwoodEnablementChecker.shouldEnableOnRavenwood(description, true)) { + RavenwoodTestStats.getInstance().onTestFinished( + classDescription, description, Result.Skipped); + return false; + } + } + return true; + } + + /** + * Called after a test / class. + * + * Return false if the exception should be ignored. + */ + private boolean onAfter(Description description, Scope scope, Order order, Throwable th) { + Log.v(TAG, "onAfter: description=" + description + ", " + scope + ", " + order + ", " + th); + + final var classDescription = mState.getClassDescription(); + + if (scope == Scope.Instance && order == Order.Outer) { + // End of a test method. + mState.exitTestMethod(); + + final Result result; + if (th == null) { + result = Result.Passed; + } else if (th instanceof AssumptionViolatedException) { + result = Result.Skipped; + } else { + result = Result.Failed; + } + + RavenwoodTestStats.getInstance().onTestFinished(classDescription, description, result); + } + + // If RUN_DISABLED_TESTS is set, and the method did _not_ throw, make it an error. + if (RavenwoodRule.private$ravenwood().isRunningDisabledTests() + && scope == Scope.Instance && order == Order.Outer) { + + boolean isTestEnabled = RavenwoodEnablementChecker.shouldEnableOnRavenwood( + description, false); + if (th == null) { + // Test passed. Is the test method supposed to be enabled? + if (isTestEnabled) { + // Enabled and didn't throw, okay. + return true; + } else { + // Disabled and didn't throw. We should report it. + fail("Test wasn't included under Ravenwood, but it actually " + + "passed under Ravenwood; consider updating annotations"); + return true; // unreachable. + } + } else { + // Test failed. + if (isTestEnabled) { + // Enabled but failed. We should throw the exception. + return true; + } else { + // Disabled and failed. Expected. Don't throw. + return false; + } + } + } + return true; + } + + /** + * Called by RavenwoodRule. + */ + static void onRavenwoodRuleEnter(Description description, RavenwoodRule rule) { + Log.v(TAG, "onRavenwoodRuleEnter: description=" + description); + getCurrentRunner().mState.enterRavenwoodRule(rule); + } + + /** + * Called by RavenwoodRule. + */ + static void onRavenwoodRuleExit(Description description, RavenwoodRule rule) { + Log.v(TAG, "onRavenwoodRuleExit: description=" + description); + getCurrentRunner().mState.exitRavenwoodRule(rule); + } + + private void dumpDescription(Description desc) { + dumpDescription(desc, "[TestDescription]=", " "); + } + + private void dumpDescription(Description desc, String header, String indent) { + Log.v(TAG, indent + header + desc); + + var children = desc.getChildren(); + var childrenIndent = " " + indent; + for (int i = 0; i < children.size(); i++) { + dumpDescription(children.get(i), "#" + i + ": ", childrenIndent); + } + } + + static volatile BiConsumer<String, Throwable> sCriticalErrorHandler = null; + + static void onCriticalError(@NonNull String message, @Nullable Throwable th) { + Log.e(TAG, "Critical error! " + message, th); + var handler = sCriticalErrorHandler; + if (handler == null) { + Log.e(TAG, "Ravenwood cannot continue. Killing self process.", th); + System.exit(1); + } + handler.accept(message, th); + } +} diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java deleted file mode 100644 index e0f9ec94a819..000000000000 --- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java +++ /dev/null @@ -1,209 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package android.platform.test.ravenwood; - -import static org.junit.Assert.fail; - -import android.os.Bundle; -import android.platform.test.ravenwood.RavenwoodAwareTestRunner.Order; -import android.platform.test.ravenwood.RavenwoodAwareTestRunner.Scope; -import android.platform.test.ravenwood.RavenwoodTestStats.Result; -import android.util.Log; - -import androidx.test.platform.app.InstrumentationRegistry; - -import org.junit.AssumptionViolatedException; -import org.junit.runner.Description; -import org.junit.runners.model.TestClass; - -/** - * Provide hook points created by {@link RavenwoodAwareTestRunner}. - * - * States are associated with each {@link RavenwoodAwareTestRunner} are stored in - * {@link RavenwoodRunnerState}, rather than as members of {@link RavenwoodAwareTestRunner}. - * See its javadoc for the reasons. - * - * All methods in this class must be called from the test main thread. - */ -public class RavenwoodAwareTestRunnerHook { - private static final String TAG = RavenwoodAwareTestRunner.TAG; - - private RavenwoodAwareTestRunnerHook() { - } - - /** - * Called before any code starts. Internally it will only initialize the environment once. - */ - public static void performGlobalInitialization() { - RavenwoodRuntimeEnvironmentController.globalInitOnce(); - } - - /** - * Called when a runner starts, before the inner runner gets a chance to run. - */ - public static void onRunnerInitializing(RavenwoodAwareTestRunner runner, TestClass testClass) { - Log.i(TAG, "onRunnerInitializing: testClass=" + testClass.getJavaClass() - + " runner=" + runner); - - // This is needed to make AndroidJUnit4ClassRunner happy. - InstrumentationRegistry.registerInstance(null, Bundle.EMPTY); - } - - /** - * Called when a whole test class is skipped. - */ - public static void onClassSkipped(Description description) { - Log.i(TAG, "onClassSkipped: description=" + description); - RavenwoodTestStats.getInstance().onClassSkipped(description); - } - - /** - * Called before the inner runner starts. - */ - public static void onBeforeInnerRunnerStart( - RavenwoodAwareTestRunner runner, Description description) throws Throwable { - Log.v(TAG, "onBeforeInnerRunnerStart: description=" + description); - - // Prepare the environment before the inner runner starts. - runner.mState.enterTestClass(description); - } - - /** - * Called after the inner runner finished. - */ - public static void onAfterInnerRunnerFinished( - RavenwoodAwareTestRunner runner, Description description) throws Throwable { - Log.v(TAG, "onAfterInnerRunnerFinished: description=" + description); - - RavenwoodTestStats.getInstance().onClassFinished(description); - runner.mState.exitTestClass(); - } - - /** - * Called before a test / class. - * - * Return false if it should be skipped. - */ - public static boolean onBefore(RavenwoodAwareTestRunner runner, Description description, - Scope scope, Order order) throws Throwable { - Log.v(TAG, "onBefore: description=" + description + ", " + scope + ", " + order); - - if (scope == Scope.Instance && order == Order.Outer) { - // Start of a test method. - runner.mState.enterTestMethod(description); - } - - final var classDescription = runner.mState.getClassDescription(); - - // Class-level annotations are checked by the runner already, so we only check - // method-level annotations here. - if (scope == Scope.Instance && order == Order.Outer) { - if (!RavenwoodEnablementChecker.shouldEnableOnRavenwood( - description, true)) { - RavenwoodTestStats.getInstance().onTestFinished( - classDescription, description, Result.Skipped); - return false; - } - } - return true; - } - - /** - * Called after a test / class. - * - * Return false if the exception should be ignored. - */ - public static boolean onAfter(RavenwoodAwareTestRunner runner, Description description, - Scope scope, Order order, Throwable th) { - Log.v(TAG, "onAfter: description=" + description + ", " + scope + ", " + order + ", " + th); - - final var classDescription = runner.mState.getClassDescription(); - - if (scope == Scope.Instance && order == Order.Outer) { - // End of a test method. - runner.mState.exitTestMethod(); - - final Result result; - if (th == null) { - result = Result.Passed; - } else if (th instanceof AssumptionViolatedException) { - result = Result.Skipped; - } else { - result = Result.Failed; - } - - RavenwoodTestStats.getInstance().onTestFinished(classDescription, description, result); - } - - // If RUN_DISABLED_TESTS is set, and the method did _not_ throw, make it an error. - if (RavenwoodRule.private$ravenwood().isRunningDisabledTests() - && scope == Scope.Instance && order == Order.Outer) { - - boolean isTestEnabled = RavenwoodEnablementChecker.shouldEnableOnRavenwood( - description, false); - if (th == null) { - // Test passed. Is the test method supposed to be enabled? - if (isTestEnabled) { - // Enabled and didn't throw, okay. - return true; - } else { - // Disabled and didn't throw. We should report it. - fail("Test wasn't included under Ravenwood, but it actually " - + "passed under Ravenwood; consider updating annotations"); - return true; // unreachable. - } - } else { - // Test failed. - if (isTestEnabled) { - // Enabled but failed. We should throw the exception. - return true; - } else { - // Disabled and failed. Expected. Don't throw. - return false; - } - } - } - return true; - } - - /** - * Called by {@link RavenwoodAwareTestRunner} to see if it should run a test class or not. - */ - public static boolean shouldRunClassOnRavenwood(Class<?> clazz) { - return RavenwoodEnablementChecker.shouldRunClassOnRavenwood(clazz, true); - } - - /** - * Called by RavenwoodRule. - */ - public static void onRavenwoodRuleEnter(RavenwoodAwareTestRunner runner, - Description description, RavenwoodRule rule) throws Throwable { - Log.v(TAG, "onRavenwoodRuleEnter: description=" + description); - - runner.mState.enterRavenwoodRule(rule); - } - - - /** - * Called by RavenwoodRule. - */ - public static void onRavenwoodRuleExit(RavenwoodAwareTestRunner runner, - Description description, RavenwoodRule rule) throws Throwable { - Log.v(TAG, "onRavenwoodRuleExit: description=" + description); - - runner.mState.exitRavenwoodRule(rule); - } -} diff --git a/services/core/java/com/android/server/integrity/parser/RuleParseException.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodConfigPrivate.java index c0f36a66528a..ffb642d60dcc 100644 --- a/services/core/java/com/android/server/integrity/parser/RuleParseException.java +++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodConfigPrivate.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 The Android Open Source Project + * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,20 +13,24 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +package android.platform.test.ravenwood; -package com.android.server.integrity.parser; +import android.annotation.Nullable; -import android.annotation.NonNull; +import java.util.function.BiConsumer; /** - * Thrown when rule parsing fails. + * Contains Ravenwood private APIs. */ -public class RuleParseException extends Exception { - public RuleParseException(@NonNull String message) { - super(message); +public class RavenwoodConfigPrivate { + private RavenwoodConfigPrivate() { } - public RuleParseException(@NonNull String message, @NonNull Throwable cause) { - super(message, cause); + /** + * Set a listener for onCriticalError(), for testing. If a listener is set, we won't call + * System.exit(). + */ + public static void setCriticalErrorHandler(@Nullable BiConsumer<String, Throwable> handler) { + RavenwoodAwareTestRunner.sCriticalErrorHandler = handler; } } diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRunNotifier.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRunNotifier.java new file mode 100644 index 000000000000..69030350bf82 --- /dev/null +++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRunNotifier.java @@ -0,0 +1,227 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.platform.test.ravenwood; + +import android.util.Log; + +import org.junit.AssumptionViolatedException; +import org.junit.runner.Description; +import org.junit.runner.Result; +import org.junit.runner.notification.Failure; +import org.junit.runner.notification.RunListener; +import org.junit.runner.notification.RunNotifier; +import org.junit.runner.notification.StoppedByUserException; +import org.junit.runners.model.MultipleFailureException; + +import java.util.ArrayList; +import java.util.Stack; + +/** + * A run notifier that wraps another notifier and provides the following features: + * - Handle a failure that happened before testStarted and testEnded (typically that means + * it's from @BeforeClass or @AfterClass, or a @ClassRule) and deliver it as if + * individual tests in the class reported it. This is for b/364395552. + * + * - Logging. + */ +class RavenwoodRunNotifier extends RunNotifier { + private final RunNotifier mRealNotifier; + + private final Stack<Description> mSuiteStack = new Stack<>(); + private Description mCurrentSuite = null; + private final ArrayList<Throwable> mOutOfTestFailures = new ArrayList<>(); + + private boolean mBeforeTest = true; + private boolean mAfterTest = false; + + RavenwoodRunNotifier(RunNotifier realNotifier) { + mRealNotifier = realNotifier; + } + + private boolean isInTest() { + return !mBeforeTest && !mAfterTest; + } + + @Override + public void addListener(RunListener listener) { + mRealNotifier.addListener(listener); + } + + @Override + public void removeListener(RunListener listener) { + mRealNotifier.removeListener(listener); + } + + @Override + public void addFirstListener(RunListener listener) { + mRealNotifier.addFirstListener(listener); + } + + @Override + public void fireTestRunStarted(Description description) { + Log.i(RavenwoodAwareTestRunner.TAG, "testRunStarted: " + description); + mRealNotifier.fireTestRunStarted(description); + } + + @Override + public void fireTestRunFinished(Result result) { + Log.i(RavenwoodAwareTestRunner.TAG, "testRunFinished: " + + result.getRunCount() + "," + + result.getFailureCount() + "," + + result.getAssumptionFailureCount() + "," + + result.getIgnoreCount()); + mRealNotifier.fireTestRunFinished(result); + } + + @Override + public void fireTestSuiteStarted(Description description) { + Log.i(RavenwoodAwareTestRunner.TAG, "testSuiteStarted: " + description); + mRealNotifier.fireTestSuiteStarted(description); + + mBeforeTest = true; + mAfterTest = false; + + // Keep track of the current suite, needed if the outer test is a Suite, + // in which case its children are test classes. (not test methods) + mCurrentSuite = description; + mSuiteStack.push(description); + + mOutOfTestFailures.clear(); + } + + @Override + public void fireTestSuiteFinished(Description description) { + Log.i(RavenwoodAwareTestRunner.TAG, "testSuiteFinished: " + description); + mRealNotifier.fireTestSuiteFinished(description); + + maybeHandleOutOfTestFailures(); + + mBeforeTest = true; + mAfterTest = false; + + // Restore the upper suite. + mSuiteStack.pop(); + mCurrentSuite = mSuiteStack.size() == 0 ? null : mSuiteStack.peek(); + } + + @Override + public void fireTestStarted(Description description) throws StoppedByUserException { + Log.i(RavenwoodAwareTestRunner.TAG, "testStarted: " + description); + mRealNotifier.fireTestStarted(description); + + mAfterTest = false; + mBeforeTest = false; + } + + @Override + public void fireTestFailure(Failure failure) { + Log.i(RavenwoodAwareTestRunner.TAG, "testFailure: " + failure); + + if (isInTest()) { + mRealNotifier.fireTestFailure(failure); + } else { + mOutOfTestFailures.add(failure.getException()); + } + } + + @Override + public void fireTestAssumptionFailed(Failure failure) { + Log.i(RavenwoodAwareTestRunner.TAG, "testAssumptionFailed: " + failure); + + if (isInTest()) { + mRealNotifier.fireTestAssumptionFailed(failure); + } else { + mOutOfTestFailures.add(failure.getException()); + } + } + + @Override + public void fireTestIgnored(Description description) { + Log.i(RavenwoodAwareTestRunner.TAG, "testIgnored: " + description); + mRealNotifier.fireTestIgnored(description); + } + + @Override + public void fireTestFinished(Description description) { + Log.i(RavenwoodAwareTestRunner.TAG, "testFinished: " + description); + mRealNotifier.fireTestFinished(description); + + mAfterTest = true; + } + + @Override + public void pleaseStop() { + Log.w(RavenwoodAwareTestRunner.TAG, "pleaseStop:"); + mRealNotifier.pleaseStop(); + } + + /** + * At the end of each Suite, we handle failures happened out of test methods. + * (typically in @BeforeClass or @AfterClasses) + * + * This is to work around b/364395552. + */ + private boolean maybeHandleOutOfTestFailures() { + if (mOutOfTestFailures.size() == 0) { + return false; + } + Throwable th; + if (mOutOfTestFailures.size() == 1) { + th = mOutOfTestFailures.get(0); + } else { + th = new MultipleFailureException(mOutOfTestFailures); + } + if (mBeforeTest) { + reportBeforeTestFailure(mCurrentSuite, th); + return true; + } + if (mAfterTest) { + reportAfterTestFailure(th); + return true; + } + return false; + } + + public void reportBeforeTestFailure(Description suiteDesc, Throwable th) { + // If a failure happens befere running any tests, we'll need to pretend + // as if each test in the suite reported the failure, to work around b/364395552. + for (var child : suiteDesc.getChildren()) { + if (child.isSuite()) { + // If the chiil is still a "parent" -- a test class or a test suite + // -- propagate to its children. + mRealNotifier.fireTestSuiteStarted(child); + reportBeforeTestFailure(child, th); + mRealNotifier.fireTestSuiteFinished(child); + } else { + mRealNotifier.fireTestStarted(child); + Failure f = new Failure(child, th); + if (th instanceof AssumptionViolatedException) { + mRealNotifier.fireTestAssumptionFailed(f); + } else { + mRealNotifier.fireTestFailure(f); + } + mRealNotifier.fireTestFinished(child); + } + } + } + + public void reportAfterTestFailure(Throwable th) { + // Unfortunately, there's no good way to report it, so kill the own process. + RavenwoodAwareTestRunner.onCriticalError( + "Failures detected in @AfterClass, which would be swallowed by tradefed", + th); + } +} diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRunnerState.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRunnerState.java index 03513ab0a2af..ead4a849dcff 100644 --- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRunnerState.java +++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRunnerState.java @@ -20,8 +20,8 @@ import static com.android.ravenwood.common.RavenwoodCommonUtils.ensureIsPublicMe import static org.junit.Assert.fail; import android.annotation.Nullable; +import android.util.Log; -import com.android.internal.annotations.GuardedBy; import com.android.ravenwood.common.RavenwoodRuntimeException; import org.junit.ClassRule; @@ -29,9 +29,7 @@ import org.junit.Rule; import org.junit.rules.TestRule; import org.junit.runner.Description; -import java.io.IOException; import java.lang.reflect.Field; -import java.util.WeakHashMap; /** * Used to store various states associated with the current test runner that's inly needed @@ -45,10 +43,6 @@ import java.util.WeakHashMap; public final class RavenwoodRunnerState { private static final String TAG = "RavenwoodRunnerState"; - @GuardedBy("sStates") - private static final WeakHashMap<RavenwoodAwareTestRunner, RavenwoodRunnerState> sStates = - new WeakHashMap<>(); - private final RavenwoodAwareTestRunner mRunner; /** @@ -69,7 +63,8 @@ public final class RavenwoodRunnerState { return mClassDescription; } - public void enterTestClass(Description classDescription) throws IOException { + public void enterTestClass(Description classDescription) { + Log.i(TAG, "enterTestClass: description=" + classDescription); mClassDescription = classDescription; mHasRavenwoodRule = hasRavenwoodRule(mRunner.getTestClass().getJavaClass()); @@ -81,6 +76,7 @@ public final class RavenwoodRunnerState { } public void exitTestClass() { + Log.i(TAG, "exitTestClass: description=" + mClassDescription); if (mCurrentConfig != null) { try { RavenwoodRuntimeEnvironmentController.reset(); @@ -98,7 +94,7 @@ public final class RavenwoodRunnerState { mMethodDescription = null; } - public void enterRavenwoodRule(RavenwoodRule rule) throws IOException { + public void enterRavenwoodRule(RavenwoodRule rule) { if (!mHasRavenwoodRule) { fail("If you have a RavenwoodRule in your test, make sure the field type is" + " RavenwoodRule so Ravenwood can detect it."); diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java index c2806daf99a1..de4357c4e7c5 100644 --- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java +++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java @@ -191,7 +191,7 @@ public class RavenwoodRuntimeEnvironmentController { /** * Initialize the environment. */ - public static void init(RavenwoodConfig config) throws IOException { + public static void init(RavenwoodConfig config) { if (RAVENWOOD_VERBOSE_LOGGING) { Log.i(TAG, "init() called here", new RuntimeException("STACKTRACE")); } diff --git a/ravenwood/junit-src/android/platform/test/annotations/RavenwoodTestRunnerInitializing.java b/ravenwood/junit-src/android/platform/test/annotations/RavenwoodTestRunnerInitializing.java new file mode 100644 index 000000000000..3bba27ab8f1d --- /dev/null +++ b/ravenwood/junit-src/android/platform/test/annotations/RavenwoodTestRunnerInitializing.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.platform.test.annotations; + +import static java.lang.annotation.ElementType.METHOD; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * An annotation similar to JUnit's BeforeClass, but this gets executed before + * the inner runner is instantiated, and only on Ravenwood. + * It can be used to initialize what's needed by the inner runner. + */ +@Target({METHOD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface RavenwoodTestRunnerInitializing { +} diff --git a/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodRunnerState.java b/ravenwood/junit-src/android/platform/test/annotations/internal/InnerRunner.java index 83cbc5265d8f..dde53a5ea03c 100644 --- a/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodRunnerState.java +++ b/ravenwood/junit-src/android/platform/test/annotations/internal/InnerRunner.java @@ -13,10 +13,20 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package android.platform.test.ravenwood; +package android.platform.test.annotations.internal; -/** Stub class. The actual implementaetion is in junit-impl-src. */ -public class RavenwoodRunnerState { - public RavenwoodRunnerState(RavenwoodAwareTestRunner runner) { - } +import static java.lang.annotation.ElementType.TYPE; + +import org.junit.runner.Runner; + +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Inherited +@Target({TYPE}) +@Retention(RetentionPolicy.RUNTIME) +public @interface InnerRunner { + Class<? extends Runner> value(); } diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java deleted file mode 100644 index 5ba972df1193..000000000000 --- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java +++ /dev/null @@ -1,733 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package android.platform.test.ravenwood; - -import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_VERBOSE_LOGGING; -import static com.android.ravenwood.common.RavenwoodCommonUtils.ensureIsPublicVoidMethod; -import static com.android.ravenwood.common.RavenwoodCommonUtils.isOnRavenwood; - -import static java.lang.annotation.ElementType.METHOD; -import static java.lang.annotation.ElementType.TYPE; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.util.Log; - -import org.junit.Assume; -import org.junit.AssumptionViolatedException; -import org.junit.internal.builders.AllDefaultPossibilitiesBuilder; -import org.junit.rules.TestRule; -import org.junit.runner.Description; -import org.junit.runner.Result; -import org.junit.runner.Runner; -import org.junit.runner.manipulation.Filter; -import org.junit.runner.manipulation.Filterable; -import org.junit.runner.manipulation.InvalidOrderingException; -import org.junit.runner.manipulation.NoTestsRemainException; -import org.junit.runner.manipulation.Orderable; -import org.junit.runner.manipulation.Orderer; -import org.junit.runner.manipulation.Sortable; -import org.junit.runner.manipulation.Sorter; -import org.junit.runner.notification.Failure; -import org.junit.runner.notification.RunListener; -import org.junit.runner.notification.RunNotifier; -import org.junit.runner.notification.StoppedByUserException; -import org.junit.runners.BlockJUnit4ClassRunner; -import org.junit.runners.Suite; -import org.junit.runners.model.MultipleFailureException; -import org.junit.runners.model.RunnerBuilder; -import org.junit.runners.model.Statement; -import org.junit.runners.model.TestClass; - -import java.lang.annotation.Annotation; -import java.lang.annotation.Inherited; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; -import java.lang.reflect.InvocationTargetException; -import java.util.ArrayList; -import java.util.Stack; -import java.util.function.BiConsumer; - -/** - * A test runner used for Ravenwood. - * - * It will delegate to another runner specified with {@link InnerRunner} - * (default = {@link BlockJUnit4ClassRunner}) with the following features. - * - Add a {@link RavenwoodAwareTestRunnerHook#onRunnerInitializing} hook, which is called before - * the inner runner gets a chance to run. This can be used to initialize stuff used by the - * inner runner. - * - Add hook points, which are handed by RavenwoodAwareTestRunnerHook, with help from - * the four test rules such as {@link #sImplicitClassOuterRule}, which are also injected by - * the ravenizer tool. - * - * We use this runner to: - * - Initialize the bare minimum environmnet just to be enough to make the actual test runners - * happy. - * - Handle {@link android.platform.test.annotations.DisabledOnRavenwood}. - * - * This class is built such that it can also be used on a real device, but in that case - * it will basically just delegate to the inner wrapper, and won't do anything special. - * (no hooks, etc.) - */ -public final class RavenwoodAwareTestRunner extends Runner implements Filterable, Orderable { - public static final String TAG = "Ravenwood"; - - @Inherited - @Target({TYPE}) - @Retention(RetentionPolicy.RUNTIME) - public @interface InnerRunner { - Class<? extends Runner> value(); - } - - /** - * An annotation similar to JUnit's BeforeClass, but this gets executed before - * the inner runner is instantiated, and only on Ravenwood. - * It can be used to initialize what's needed by the inner runner. - */ - @Target({METHOD}) - @Retention(RetentionPolicy.RUNTIME) - public @interface RavenwoodTestRunnerInitializing { - } - - /** Scope of a hook. */ - public enum Scope { - Class, - Instance, - } - - /** Order of a hook. */ - public enum Order { - Outer, - Inner, - } - - // The following four rule instances will be injected to tests by the Ravenizer tool. - private static class RavenwoodClassOuterRule implements TestRule { - @Override - public Statement apply(Statement base, Description description) { - return getCurrentRunner().wrapWithHooks(base, description, Scope.Class, Order.Outer); - } - } - - private static class RavenwoodClassInnerRule implements TestRule { - @Override - public Statement apply(Statement base, Description description) { - return getCurrentRunner().wrapWithHooks(base, description, Scope.Class, Order.Inner); - } - } - - private static class RavenwoodInstanceOuterRule implements TestRule { - @Override - public Statement apply(Statement base, Description description) { - return getCurrentRunner().wrapWithHooks( - base, description, Scope.Instance, Order.Outer); - } - } - - private static class RavenwoodInstanceInnerRule implements TestRule { - @Override - public Statement apply(Statement base, Description description) { - return getCurrentRunner().wrapWithHooks( - base, description, Scope.Instance, Order.Inner); - } - } - - public static final TestRule sImplicitClassOuterRule = new RavenwoodClassOuterRule(); - public static final TestRule sImplicitClassInnerRule = new RavenwoodClassInnerRule(); - public static final TestRule sImplicitInstOuterRule = new RavenwoodInstanceOuterRule(); - public static final TestRule sImplicitInstInnerRule = new RavenwoodInstanceOuterRule(); - - public static final String IMPLICIT_CLASS_OUTER_RULE_NAME = "sImplicitClassOuterRule"; - public static final String IMPLICIT_CLASS_INNER_RULE_NAME = "sImplicitClassInnerRule"; - public static final String IMPLICIT_INST_OUTER_RULE_NAME = "sImplicitInstOuterRule"; - public static final String IMPLICIT_INST_INNER_RULE_NAME = "sImplicitInstInnerRule"; - - /** Keeps track of the runner on the current thread. */ - private static final ThreadLocal<RavenwoodAwareTestRunner> sCurrentRunner = new ThreadLocal<>(); - - static RavenwoodAwareTestRunner getCurrentRunner() { - var runner = sCurrentRunner.get(); - if (runner == null) { - throw new RuntimeException("Current test runner not set!"); - } - return runner; - } - - private final Class<?> mTestJavaClass; - private TestClass mTestClass = null; - private Runner mRealRunner = null; - private Description mDescription = null; - private Throwable mExceptionInConstructor = null; - private boolean mRealRunnerTakesRunnerBuilder = false; - - /** - * Stores internal states / methods associated with this runner that's only needed in - * junit-impl. - */ - final RavenwoodRunnerState mState = new RavenwoodRunnerState(this); - - private Error logAndFail(String message, Throwable exception) { - Log.e(TAG, message, exception); - throw new AssertionError(message, exception); - } - - public TestClass getTestClass() { - return mTestClass; - } - - /** - * Constructor. - */ - public RavenwoodAwareTestRunner(Class<?> testClass) { - mTestJavaClass = testClass; - try { - performGlobalInitialization(); - - /* - * If the class has @DisabledOnRavenwood, then we'll delegate to - * ClassSkippingTestRunner, which simply skips it. - * - * We need to do it before instantiating TestClass for b/367694651. - */ - if (isOnRavenwood() && !RavenwoodAwareTestRunnerHook.shouldRunClassOnRavenwood( - testClass)) { - mRealRunner = new ClassSkippingTestRunner(testClass); - mDescription = mRealRunner.getDescription(); - return; - } - - mTestClass = new TestClass(testClass); - - Log.v(TAG, "RavenwoodAwareTestRunner starting for " + testClass.getCanonicalName()); - - onRunnerInitializing(); - - // Find the real runner. - final Class<? extends Runner> realRunnerClass; - final InnerRunner innerRunnerAnnotation = mTestClass.getAnnotation(InnerRunner.class); - if (innerRunnerAnnotation != null) { - realRunnerClass = innerRunnerAnnotation.value(); - } else { - // Default runner. - realRunnerClass = BlockJUnit4ClassRunner.class; - } - - try { - Log.i(TAG, "Initializing the inner runner: " + realRunnerClass); - - mRealRunner = instantiateRealRunner(realRunnerClass, testClass); - mDescription = mRealRunner.getDescription(); - - } catch (InstantiationException | IllegalAccessException - | InvocationTargetException | NoSuchMethodException e) { - throw logAndFail("Failed to instantiate " + realRunnerClass, e); - } - } catch (Throwable th) { - // If we throw in the constructor, Tradefed may not report it and just ignore the class, - // so record it and throw it when the test actually started. - Log.e(TAG, "Fatal: Exception detected in constructor", th); - mExceptionInConstructor = new RuntimeException("Exception detected in constructor", - th); - mDescription = Description.createTestDescription(testClass, "Constructor"); - - // This is for testing if tradefed is fixed. - if ("1".equals(System.getenv("RAVENWOOD_THROW_EXCEPTION_IN_TEST_RUNNER"))) { - throw th; - } - } - } - - private Runner instantiateRealRunner( - Class<? extends Runner> realRunnerClass, - Class<?> testClass) - throws NoSuchMethodException, InvocationTargetException, InstantiationException, - IllegalAccessException { - try { - return realRunnerClass.getConstructor(Class.class).newInstance(testClass); - } catch (NoSuchMethodException e) { - var constructor = realRunnerClass.getConstructor(Class.class, RunnerBuilder.class); - mRealRunnerTakesRunnerBuilder = true; - return constructor.newInstance(testClass, new AllDefaultPossibilitiesBuilder()); - } - } - - private void performGlobalInitialization() { - if (!isOnRavenwood()) { - return; - } - RavenwoodAwareTestRunnerHook.performGlobalInitialization(); - } - - /** - * Run the bare minimum setup to initialize the wrapped runner. - */ - // This method is called by the ctor, so never make it virtual. - private void onRunnerInitializing() { - if (!isOnRavenwood()) { - return; - } - - RavenwoodAwareTestRunnerHook.onRunnerInitializing(this, mTestClass); - - // Hook point to allow more customization. - runAnnotatedMethodsOnRavenwood(RavenwoodTestRunnerInitializing.class, null); - } - - private void runAnnotatedMethodsOnRavenwood(Class<? extends Annotation> annotationClass, - Object instance) { - if (!isOnRavenwood()) { - return; - } - Log.v(TAG, "runAnnotatedMethodsOnRavenwood() " + annotationClass.getName()); - - for (var method : getTestClass().getAnnotatedMethods(annotationClass)) { - ensureIsPublicVoidMethod(method.getMethod(), /* isStatic=*/ instance == null); - - var methodDesc = method.getDeclaringClass().getName() + "." - + method.getMethod().toString(); - try { - method.getMethod().invoke(instance); - } catch (IllegalAccessException | InvocationTargetException e) { - throw logAndFail("Caught exception while running method " + methodDesc, e); - } - } - } - - @Override - public Description getDescription() { - return mDescription; - } - - @Override - public void run(RunNotifier realNotifier) { - final RavenwoodRunNotifier notifier = new RavenwoodRunNotifier(realNotifier); - - if (mRealRunner instanceof ClassSkippingTestRunner) { - mRealRunner.run(notifier); - RavenwoodAwareTestRunnerHook.onClassSkipped(getDescription()); - return; - } - - Log.v(TAG, "Starting " + mTestJavaClass.getCanonicalName()); - if (RAVENWOOD_VERBOSE_LOGGING) { - dumpDescription(getDescription()); - } - - if (maybeReportExceptionFromConstructor(notifier)) { - return; - } - - // TODO(b/365976974): handle nested classes better - final boolean skipRunnerHook = - mRealRunnerTakesRunnerBuilder && mRealRunner instanceof Suite; - - sCurrentRunner.set(this); - try { - if (!skipRunnerHook) { - try { - RavenwoodAwareTestRunnerHook.onBeforeInnerRunnerStart( - this, getDescription()); - } catch (Throwable th) { - notifier.reportBeforeTestFailure(getDescription(), th); - return; - } - } - - // Delegate to the inner runner. - mRealRunner.run(notifier); - } finally { - sCurrentRunner.remove(); - - if (!skipRunnerHook) { - try { - RavenwoodAwareTestRunnerHook.onAfterInnerRunnerFinished( - this, getDescription()); - } catch (Throwable th) { - notifier.reportAfterTestFailure(th); - } - } - } - } - - /** Throw the exception detected in the constructor, if any. */ - private boolean maybeReportExceptionFromConstructor(RunNotifier notifier) { - if (mExceptionInConstructor == null) { - return false; - } - notifier.fireTestStarted(mDescription); - notifier.fireTestFailure(new Failure(mDescription, mExceptionInConstructor)); - notifier.fireTestFinished(mDescription); - - return true; - } - - @Override - public void filter(Filter filter) throws NoTestsRemainException { - if (mRealRunner instanceof Filterable r) { - r.filter(filter); - } - } - - @Override - public void order(Orderer orderer) throws InvalidOrderingException { - if (mRealRunner instanceof Orderable r) { - r.order(orderer); - } - } - - @Override - public void sort(Sorter sorter) { - if (mRealRunner instanceof Sortable r) { - r.sort(sorter); - } - } - - private Statement wrapWithHooks(Statement base, Description description, Scope scope, - Order order) { - if (!isOnRavenwood()) { - return base; - } - return new Statement() { - @Override - public void evaluate() throws Throwable { - runWithHooks(description, scope, order, base); - } - }; - } - - private void runWithHooks(Description description, Scope scope, Order order, Runnable r) - throws Throwable { - runWithHooks(description, scope, order, new Statement() { - @Override - public void evaluate() throws Throwable { - r.run(); - } - }); - } - - private void runWithHooks(Description description, Scope scope, Order order, Statement s) - throws Throwable { - if (isOnRavenwood()) { - Assume.assumeTrue( - RavenwoodAwareTestRunnerHook.onBefore(this, description, scope, order)); - } - try { - s.evaluate(); - if (isOnRavenwood()) { - RavenwoodAwareTestRunnerHook.onAfter(this, description, scope, order, null); - } - } catch (Throwable t) { - boolean shouldThrow = true; - if (isOnRavenwood()) { - shouldThrow = RavenwoodAwareTestRunnerHook.onAfter( - this, description, scope, order, t); - } - if (shouldThrow) { - throw t; - } - } - } - - /** - * A runner that simply skips a class. It still has to support {@link Filterable} - * because otherwise the result still says "SKIPPED" even when it's not included in the - * filter. - */ - private static class ClassSkippingTestRunner extends Runner implements Filterable { - private final Description mDescription; - private boolean mFilteredOut; - - ClassSkippingTestRunner(Class<?> testClass) { - mDescription = Description.createTestDescription(testClass, testClass.getSimpleName()); - mFilteredOut = false; - } - - @Override - public Description getDescription() { - return mDescription; - } - - @Override - public void run(RunNotifier notifier) { - if (mFilteredOut) { - return; - } - notifier.fireTestSuiteStarted(mDescription); - notifier.fireTestIgnored(mDescription); - notifier.fireTestSuiteFinished(mDescription); - } - - @Override - public void filter(Filter filter) throws NoTestsRemainException { - if (filter.shouldRun(mDescription)) { - mFilteredOut = false; - } else { - throw new NoTestsRemainException(); - } - } - } - - private void dumpDescription(Description desc) { - dumpDescription(desc, "[TestDescription]=", " "); - } - - private void dumpDescription(Description desc, String header, String indent) { - Log.v(TAG, indent + header + desc); - - var children = desc.getChildren(); - var childrenIndent = " " + indent; - for (int i = 0; i < children.size(); i++) { - dumpDescription(children.get(i), "#" + i + ": ", childrenIndent); - } - } - - /** - * A run notifier that wraps another notifier and provides the following features: - * - Handle a failure that happened before testStarted and testEnded (typically that means - * it's from @BeforeClass or @AfterClass, or a @ClassRule) and deliver it as if - * individual tests in the class reported it. This is for b/364395552. - * - * - Logging. - */ - private class RavenwoodRunNotifier extends RunNotifier { - private final RunNotifier mRealNotifier; - - private final Stack<Description> mSuiteStack = new Stack<>(); - private Description mCurrentSuite = null; - private final ArrayList<Throwable> mOutOfTestFailures = new ArrayList<>(); - - private boolean mBeforeTest = true; - private boolean mAfterTest = false; - - private RavenwoodRunNotifier(RunNotifier realNotifier) { - mRealNotifier = realNotifier; - } - - private boolean isInTest() { - return !mBeforeTest && !mAfterTest; - } - - @Override - public void addListener(RunListener listener) { - mRealNotifier.addListener(listener); - } - - @Override - public void removeListener(RunListener listener) { - mRealNotifier.removeListener(listener); - } - - @Override - public void addFirstListener(RunListener listener) { - mRealNotifier.addFirstListener(listener); - } - - @Override - public void fireTestRunStarted(Description description) { - Log.i(TAG, "testRunStarted: " + description); - mRealNotifier.fireTestRunStarted(description); - } - - @Override - public void fireTestRunFinished(Result result) { - Log.i(TAG, "testRunFinished: " - + result.getRunCount() + "," - + result.getFailureCount() + "," - + result.getAssumptionFailureCount() + "," - + result.getIgnoreCount()); - mRealNotifier.fireTestRunFinished(result); - } - - @Override - public void fireTestSuiteStarted(Description description) { - Log.i(TAG, "testSuiteStarted: " + description); - mRealNotifier.fireTestSuiteStarted(description); - - mBeforeTest = true; - mAfterTest = false; - - // Keep track of the current suite, needed if the outer test is a Suite, - // in which case its children are test classes. (not test methods) - mCurrentSuite = description; - mSuiteStack.push(description); - - mOutOfTestFailures.clear(); - } - - @Override - public void fireTestSuiteFinished(Description description) { - Log.i(TAG, "testSuiteFinished: " + description); - mRealNotifier.fireTestSuiteFinished(description); - - maybeHandleOutOfTestFailures(); - - mBeforeTest = true; - mAfterTest = false; - - // Restore the upper suite. - mSuiteStack.pop(); - mCurrentSuite = mSuiteStack.size() == 0 ? null : mSuiteStack.peek(); - } - - @Override - public void fireTestStarted(Description description) throws StoppedByUserException { - Log.i(TAG, "testStarted: " + description); - mRealNotifier.fireTestStarted(description); - - mAfterTest = false; - mBeforeTest = false; - } - - @Override - public void fireTestFailure(Failure failure) { - Log.i(TAG, "testFailure: " + failure); - - if (isInTest()) { - mRealNotifier.fireTestFailure(failure); - } else { - mOutOfTestFailures.add(failure.getException()); - } - } - - @Override - public void fireTestAssumptionFailed(Failure failure) { - Log.i(TAG, "testAssumptionFailed: " + failure); - - if (isInTest()) { - mRealNotifier.fireTestAssumptionFailed(failure); - } else { - mOutOfTestFailures.add(failure.getException()); - } - } - - @Override - public void fireTestIgnored(Description description) { - Log.i(TAG, "testIgnored: " + description); - mRealNotifier.fireTestIgnored(description); - } - - @Override - public void fireTestFinished(Description description) { - Log.i(TAG, "testFinished: " + description); - mRealNotifier.fireTestFinished(description); - - mAfterTest = true; - } - - @Override - public void pleaseStop() { - Log.w(TAG, "pleaseStop:"); - mRealNotifier.pleaseStop(); - } - - /** - * At the end of each Suite, we handle failures happened out of test methods. - * (typically in @BeforeClass or @AfterClasses) - * - * This is to work around b/364395552. - */ - private boolean maybeHandleOutOfTestFailures() { - if (mOutOfTestFailures.size() == 0) { - return false; - } - Throwable th; - if (mOutOfTestFailures.size() == 1) { - th = mOutOfTestFailures.get(0); - } else { - th = new MultipleFailureException(mOutOfTestFailures); - } - if (mBeforeTest) { - reportBeforeTestFailure(mCurrentSuite, th); - return true; - } - if (mAfterTest) { - reportAfterTestFailure(th); - return true; - } - return false; - } - - public void reportBeforeTestFailure(Description suiteDesc, Throwable th) { - // If a failure happens befere running any tests, we'll need to pretend - // as if each test in the suite reported the failure, to work around b/364395552. - for (var child : suiteDesc.getChildren()) { - if (child.isSuite()) { - // If the chiil is still a "parent" -- a test class or a test suite - // -- propagate to its children. - mRealNotifier.fireTestSuiteStarted(child); - reportBeforeTestFailure(child, th); - mRealNotifier.fireTestSuiteFinished(child); - } else { - mRealNotifier.fireTestStarted(child); - Failure f = new Failure(child, th); - if (th instanceof AssumptionViolatedException) { - mRealNotifier.fireTestAssumptionFailed(f); - } else { - mRealNotifier.fireTestFailure(f); - } - mRealNotifier.fireTestFinished(child); - } - } - } - - public void reportAfterTestFailure(Throwable th) { - // Unfortunately, there's no good way to report it, so kill the own process. - onCriticalError( - "Failures detected in @AfterClass, which would be swallowed by tradefed", - th); - } - } - - private static volatile BiConsumer<String, Throwable> sCriticalErrorHanler; - - private void onCriticalError(@NonNull String message, @Nullable Throwable th) { - Log.e(TAG, "Critical error! " + message, th); - var handler = sCriticalErrorHanler; - if (handler == null) { - handler = sDefaultCriticalErrorHandler; - } - handler.accept(message, th); - } - - private static BiConsumer<String, Throwable> sDefaultCriticalErrorHandler = (message, th) -> { - Log.e(TAG, "Ravenwood cannot continue. Killing self process.", th); - System.exit(1); - }; - - /** - * Contains Ravenwood private APIs. - */ - public static class RavenwoodPrivate { - private RavenwoodPrivate() { - } - - /** - * Set a listener for onCriticalError(), for testing. If a listener is set, we won't call - * System.exit(). - */ - public void setCriticalErrorHandler( - @Nullable BiConsumer<String, Throwable> handler) { - sCriticalErrorHanler = handler; - } - } - - private static final RavenwoodPrivate sRavenwoodPrivate = new RavenwoodPrivate(); - - public static RavenwoodPrivate private$ravenwood() { - return sRavenwoodPrivate; - } -} diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerBase.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerBase.java new file mode 100644 index 000000000000..7c72f6bd8619 --- /dev/null +++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerBase.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.platform.test.ravenwood; + +import android.platform.test.annotations.internal.InnerRunner; +import android.util.Log; + +import org.junit.internal.builders.AllDefaultPossibilitiesBuilder; +import org.junit.runner.Description; +import org.junit.runner.Runner; +import org.junit.runner.manipulation.Filter; +import org.junit.runner.manipulation.Filterable; +import org.junit.runner.manipulation.InvalidOrderingException; +import org.junit.runner.manipulation.NoTestsRemainException; +import org.junit.runner.manipulation.Orderable; +import org.junit.runner.manipulation.Orderer; +import org.junit.runner.manipulation.Sortable; +import org.junit.runner.manipulation.Sorter; +import org.junit.runner.notification.RunNotifier; +import org.junit.runners.BlockJUnit4ClassRunner; +import org.junit.runners.model.RunnerBuilder; +import org.junit.runners.model.TestClass; + +abstract class RavenwoodAwareTestRunnerBase extends Runner implements Filterable, Orderable { + private static final String TAG = "Ravenwood"; + + boolean mRealRunnerTakesRunnerBuilder = false; + + abstract Runner getRealRunner(); + + final Runner instantiateRealRunner(TestClass testClass) { + // Find the real runner. + final Class<? extends Runner> runnerClass; + final InnerRunner innerRunnerAnnotation = testClass.getAnnotation(InnerRunner.class); + if (innerRunnerAnnotation != null) { + runnerClass = innerRunnerAnnotation.value(); + } else { + // Default runner. + runnerClass = BlockJUnit4ClassRunner.class; + } + + try { + Log.i(TAG, "Initializing the inner runner: " + runnerClass); + try { + return runnerClass.getConstructor(Class.class) + .newInstance(testClass.getJavaClass()); + } catch (NoSuchMethodException e) { + var constructor = runnerClass.getConstructor(Class.class, RunnerBuilder.class); + mRealRunnerTakesRunnerBuilder = true; + return constructor.newInstance( + testClass.getJavaClass(), new AllDefaultPossibilitiesBuilder()); + } + } catch (ReflectiveOperationException e) { + throw logAndFail("Failed to instantiate " + runnerClass, e); + } + } + + final Error logAndFail(String message, Throwable exception) { + Log.e(TAG, message, exception); + return new AssertionError(message, exception); + } + + @Override + public Description getDescription() { + return getRealRunner().getDescription(); + } + + @Override + public final void filter(Filter filter) throws NoTestsRemainException { + if (getRealRunner() instanceof Filterable r) { + r.filter(filter); + } + } + + @Override + public final void order(Orderer orderer) throws InvalidOrderingException { + if (getRealRunner() instanceof Orderable r) { + r.order(orderer); + } + } + + @Override + public final void sort(Sorter sorter) { + if (getRealRunner() instanceof Sortable r) { + r.sort(sorter); + } + } +} diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java index 93a6806ed1f4..773dba1b4620 100644 --- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java +++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java @@ -242,15 +242,11 @@ public final class RavenwoodRule implements TestRule { return new Statement() { @Override public void evaluate() throws Throwable { - RavenwoodAwareTestRunnerHook.onRavenwoodRuleEnter( - RavenwoodAwareTestRunner.getCurrentRunner(), description, - RavenwoodRule.this); + RavenwoodAwareTestRunner.onRavenwoodRuleEnter(description, RavenwoodRule.this); try { base.evaluate(); } finally { - RavenwoodAwareTestRunnerHook.onRavenwoodRuleExit( - RavenwoodAwareTestRunner.getCurrentRunner(), description, - RavenwoodRule.this); + RavenwoodAwareTestRunner.onRavenwoodRuleExit(description, RavenwoodRule.this); } } }; diff --git a/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java b/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java new file mode 100644 index 000000000000..b4b751788c6e --- /dev/null +++ b/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.platform.test.ravenwood; + +import android.util.Log; + +import org.junit.rules.TestRule; +import org.junit.runner.Description; +import org.junit.runner.Runner; +import org.junit.runner.notification.RunNotifier; +import org.junit.runners.model.Statement; +import org.junit.runners.model.TestClass; + +/** + * A simple pass-through runner that just delegates to the inner runner without doing + * anything special (no hooks, etc.). + * + * This is only used when a real device-side test has Ravenizer enabled. + */ +public class RavenwoodAwareTestRunner extends RavenwoodAwareTestRunnerBase { + private static final String TAG = "Ravenwood"; + + private static class NopRule implements TestRule { + @Override + public Statement apply(Statement base, Description description) { + return base; + } + } + + public static final TestRule sImplicitClassOuterRule = new NopRule(); + public static final TestRule sImplicitClassInnerRule = sImplicitClassOuterRule; + public static final TestRule sImplicitInstOuterRule = sImplicitClassOuterRule; + public static final TestRule sImplicitInstInnerRule = sImplicitClassOuterRule; + + private final Runner mRealRunner; + + public RavenwoodAwareTestRunner(Class<?> clazz) { + Log.v(TAG, "RavenwoodAwareTestRunner starting for " + clazz.getCanonicalName()); + mRealRunner = instantiateRealRunner(new TestClass(clazz)); + } + + @Override + Runner getRealRunner() { + return mRealRunner; + } + + @Override + public void run(RunNotifier notifier) { + mRealRunner.run(notifier); + } + + static void onRavenwoodRuleEnter(Description description, RavenwoodRule rule) { + } + + static void onRavenwoodRuleExit(Description description, RavenwoodRule rule) { + } +} diff --git a/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java b/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java deleted file mode 100644 index aa8c29936082..000000000000 --- a/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package android.platform.test.ravenwood; - -import android.platform.test.ravenwood.RavenwoodAwareTestRunner.Order; -import android.platform.test.ravenwood.RavenwoodAwareTestRunner.Scope; - -import org.junit.runner.Description; -import org.junit.runners.model.TestClass; - -/** - * Provide hook points created by {@link RavenwoodAwareTestRunner}. This is a version - * that's used on a device side test. - * - * All methods are no-op in real device tests. - * - * TODO: Use some kind of factory to provide different implementation for the device test - * and the ravenwood test. - */ -public class RavenwoodAwareTestRunnerHook { - private RavenwoodAwareTestRunnerHook() { - } - - /** - * Called before any code starts. Internally it will only initialize the environment once. - */ - public static void performGlobalInitialization() { - } - - /** - * Called when a runner starts, before the inner runner gets a chance to run. - */ - public static void onRunnerInitializing(RavenwoodAwareTestRunner runner, TestClass testClass) { - } - - /** - * Called when a whole test class is skipped. - */ - public static void onClassSkipped(Description description) { - } - - /** - * Called before the inner runner starts. - */ - public static void onBeforeInnerRunnerStart( - RavenwoodAwareTestRunner runner, Description description) throws Throwable { - } - - /** - * Called after the inner runner finished. - */ - public static void onAfterInnerRunnerFinished( - RavenwoodAwareTestRunner runner, Description description) throws Throwable { - } - - /** - * Called before a test / class. - * - * Return false if it should be skipped. - */ - public static boolean onBefore(RavenwoodAwareTestRunner runner, Description description, - Scope scope, Order order) throws Throwable { - return true; - } - - public static void onRavenwoodRuleEnter(RavenwoodAwareTestRunner runner, - Description description, RavenwoodRule rule) throws Throwable { - } - - public static void onRavenwoodRuleExit(RavenwoodAwareTestRunner runner, - Description description, RavenwoodRule rule) throws Throwable { - } - - - /** - * Called after a test / class. - * - * Return false if the exception should be ignored. - */ - public static boolean onAfter(RavenwoodAwareTestRunner runner, Description description, - Scope scope, Order order, Throwable th) { - return true; - } - - public static boolean shouldRunClassOnRavenwood(Class<?> clazz) { - return true; - } -} diff --git a/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodConfigState.java b/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodConfigState.java index 43a28ba72ec9..7d3d8b9f7974 100644 --- a/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodConfigState.java +++ b/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodConfigState.java @@ -15,7 +15,7 @@ */ package android.platform.test.ravenwood; -/** Stub class. The actual implementaetion is in junit-impl-src. */ +/** Stub class. The actual implementation is in junit-impl-src. */ public class RavenwoodConfigState { public RavenwoodConfigState(RavenwoodConfig config) { } diff --git a/ravenwood/tests/bivalenttest/Android.bp b/ravenwood/tests/bivalenttest/Android.bp index d7f4b3e2955d..ce0033d02910 100644 --- a/ravenwood/tests/bivalenttest/Android.bp +++ b/ravenwood/tests/bivalenttest/Android.bp @@ -31,9 +31,8 @@ cc_library_shared { ], } -android_ravenwood_test { - name: "RavenwoodBivalentTest", - +java_defaults { + name: "ravenwood-bivalent-defaults", static_libs: [ "androidx.annotation_annotation", "androidx.test.ext.junit", @@ -51,15 +50,11 @@ android_ravenwood_test { jni_libs: [ "libravenwoodbivalenttest_jni", ], - auto_gen_config: true, } -android_test { - name: "RavenwoodBivalentTest_device", - - srcs: [ - "test/**/*.java", - ], +java_defaults { + name: "ravenwood-bivalent-device-defaults", + defaults: ["ravenwood-bivalent-defaults"], // TODO(b/371215487): migrate bivalenttest.ravenizer tests to another architecture exclude_srcs: [ "test/**/ravenizer/*.java", @@ -67,23 +62,32 @@ android_test { static_libs: [ "junit", "truth", - - "androidx.annotation_annotation", - "androidx.test.ext.junit", - "androidx.test.rules", - - "junit-params", - "platform-parametric-runner-lib", - "ravenwood-junit", ], - jni_libs: [ - "libravenwoodbivalenttest_jni", - ], test_suites: [ "device-tests", ], optimize: { enabled: false, }, + test_config_template: "AndroidTestTemplate.xml", +} + +android_ravenwood_test { + name: "RavenwoodBivalentTest", + defaults: ["ravenwood-bivalent-defaults"], + auto_gen_config: true, +} + +android_test { + name: "RavenwoodBivalentTest_device", + defaults: ["ravenwood-bivalent-device-defaults"], +} + +android_test { + name: "RavenwoodBivalentTest_device_ravenizer", + defaults: ["ravenwood-bivalent-device-defaults"], + ravenizer: { + enabled: true, + }, } diff --git a/ravenwood/tests/bivalenttest/AndroidTest.xml b/ravenwood/tests/bivalenttest/AndroidTestTemplate.xml index 9e5dd11b60cb..8f1a92c0ae17 100644 --- a/ravenwood/tests/bivalenttest/AndroidTest.xml +++ b/ravenwood/tests/bivalenttest/AndroidTestTemplate.xml @@ -19,7 +19,7 @@ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> <option name="cleanup-apks" value="true" /> - <option name="test-file-name" value="RavenwoodBivalentTest_device.apk" /> + <option name="test-file-name" value="{MODULE}.apk"/> </target_preparer> <test class="com.android.tradefed.testtype.AndroidJUnitTest" > diff --git a/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodAwareTestRunnerTest.java b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodAwareTestRunnerTest.java index d7c2c6cd73a8..637f06910d55 100644 --- a/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodAwareTestRunnerTest.java +++ b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodAwareTestRunnerTest.java @@ -18,7 +18,7 @@ package com.android.ravenwoodtest.bivalenttest.ravenizer; import static org.junit.Assert.assertFalse; import android.platform.test.annotations.DisabledOnRavenwood; -import android.platform.test.ravenwood.RavenwoodAwareTestRunner.RavenwoodTestRunnerInitializing; +import android.platform.test.annotations.RavenwoodTestRunnerInitializing; import android.platform.test.ravenwood.RavenwoodRule; import android.util.Log; diff --git a/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodNoRavenizerTest.java b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodNoRavenizerTest.java index 9d878f444e5e..77a807d5e1e5 100644 --- a/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodNoRavenizerTest.java +++ b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodNoRavenizerTest.java @@ -16,7 +16,7 @@ package com.android.ravenwoodtest.bivalenttest.ravenizer; import android.platform.test.annotations.NoRavenizer; -import android.platform.test.ravenwood.RavenwoodAwareTestRunner.RavenwoodTestRunnerInitializing; +import android.platform.test.annotations.RavenwoodTestRunnerInitializing; import org.junit.Test; diff --git a/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunDisabledTestsReallyDisabledTest.java b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunDisabledTestsReallyDisabledTest.java index c77841b1b55a..e6e617b401b6 100644 --- a/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunDisabledTestsReallyDisabledTest.java +++ b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunDisabledTestsReallyDisabledTest.java @@ -18,7 +18,7 @@ package com.android.ravenwoodtest.bivalenttest.ravenizer; import static org.junit.Assert.fail; import android.platform.test.annotations.DisabledOnRavenwood; -import android.platform.test.ravenwood.RavenwoodAwareTestRunner.RavenwoodTestRunnerInitializing; +import android.platform.test.annotations.RavenwoodTestRunnerInitializing; import android.platform.test.ravenwood.RavenwoodRule; import android.util.Log; diff --git a/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunDisabledTestsTest.java b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunDisabledTestsTest.java index ea1a29d57482..ef18c82b6d79 100644 --- a/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunDisabledTestsTest.java +++ b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunDisabledTestsTest.java @@ -18,7 +18,7 @@ package com.android.ravenwoodtest.bivalenttest.ravenizer; import static org.junit.Assert.fail; import android.platform.test.annotations.DisabledOnRavenwood; -import android.platform.test.ravenwood.RavenwoodAwareTestRunner.RavenwoodTestRunnerInitializing; +import android.platform.test.annotations.RavenwoodTestRunnerInitializing; import android.platform.test.ravenwood.RavenwoodRule; import android.util.Log; diff --git a/ravenwood/tests/coretest/Android.bp b/ravenwood/tests/coretest/Android.bp index 412744eb9d34..9dd7cc683719 100644 --- a/ravenwood/tests/coretest/Android.bp +++ b/ravenwood/tests/coretest/Android.bp @@ -16,11 +16,14 @@ android_ravenwood_test { "androidx.test.rules", "junit-params", "platform-parametric-runner-lib", - "truth", // This library should be removed by Ravenizer "mockito-target-minus-junit4", ], + libs: [ + // We access internal private classes + "ravenwood-junit-impl", + ], srcs: [ "test/**/*.java", "test/**/*.kt", diff --git a/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRunnerTestBase.java b/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRunnerTestBase.java index 9a6934bf17c5..f7a2198a9bc4 100644 --- a/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRunnerTestBase.java +++ b/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRunnerTestBase.java @@ -20,6 +20,7 @@ import static com.google.common.truth.Truth.assertWithMessage; import android.platform.test.annotations.NoRavenizer; import android.platform.test.ravenwood.RavenwoodAwareTestRunner; +import android.platform.test.ravenwood.RavenwoodConfigPrivate; import android.util.Log; import junitparams.JUnitParamsRunner; @@ -137,15 +138,14 @@ public abstract class RavenwoodRunnerTestBase { // Set a listener to critical errors. This will also prevent // {@link RavenwoodAwareTestRunner} from calling System.exit() when there's // a critical error. - RavenwoodAwareTestRunner.private$ravenwood().setCriticalErrorHandler( - listener.sCriticalErrorListener); + RavenwoodConfigPrivate.setCriticalErrorHandler(listener.sCriticalErrorListener); try { // Run the test class. junitCore.run(testClazz); } finally { // Clear the critical error listener. - RavenwoodAwareTestRunner.private$ravenwood().setCriticalErrorHandler(null); + RavenwoodConfigPrivate.setCriticalErrorHandler(null); } // Check the result. diff --git a/ravenwood/texts/ravenwood-annotation-allowed-classes.txt b/ravenwood/texts/ravenwood-annotation-allowed-classes.txt index 9c8638930df9..a26fe66da2ab 100644 --- a/ravenwood/texts/ravenwood-annotation-allowed-classes.txt +++ b/ravenwood/texts/ravenwood-annotation-allowed-classes.txt @@ -359,3 +359,6 @@ com.android.server.SystemService com.android.server.SystemServiceManager com.android.server.utils.TimingsTraceAndSlog + +android.os.IpcDataCache +android.app.PropertyInvalidatedCache diff --git a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Utils.kt b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Utils.kt index 37a797528e13..6092fcc9402d 100644 --- a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Utils.kt +++ b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Utils.kt @@ -15,6 +15,7 @@ */ package com.android.platform.test.ravenwood.ravenizer +import android.platform.test.annotations.internal.InnerRunner import android.platform.test.annotations.NoRavenizer import android.platform.test.ravenwood.RavenwoodAwareTestRunner import com.android.hoststubgen.asm.ClassNodes @@ -39,7 +40,7 @@ val testAnotType = TypeHolder(org.junit.Test::class.java) val ruleAnotType = TypeHolder(org.junit.Rule::class.java) val classRuleAnotType = TypeHolder(org.junit.ClassRule::class.java) val runWithAnotType = TypeHolder(RunWith::class.java) -val innerRunnerAnotType = TypeHolder(RavenwoodAwareTestRunner.InnerRunner::class.java) +val innerRunnerAnotType = TypeHolder(InnerRunner::class.java) val noRavenizerAnotType = TypeHolder(NoRavenizer::class.java) val testRuleType = TypeHolder(TestRule::class.java) diff --git a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/adapter/RunnerRewritingAdapter.kt b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/adapter/RunnerRewritingAdapter.kt index cf6d6f6bcae3..81fe3da8d954 100644 --- a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/adapter/RunnerRewritingAdapter.kt +++ b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/adapter/RunnerRewritingAdapter.kt @@ -15,7 +15,6 @@ */ package com.android.platform.test.ravenwood.ravenizer.adapter -import android.platform.test.ravenwood.RavenwoodAwareTestRunner import com.android.hoststubgen.ClassParseException import com.android.hoststubgen.asm.CLASS_INITIALIZER_DESC import com.android.hoststubgen.asm.CLASS_INITIALIZER_NAME @@ -28,8 +27,8 @@ import com.android.hoststubgen.log import com.android.hoststubgen.visitors.OPCODE_VERSION import com.android.platform.test.ravenwood.ravenizer.RavenizerInternalException import com.android.platform.test.ravenwood.ravenizer.classRuleAnotType -import com.android.platform.test.ravenwood.ravenizer.isTestLookingClass import com.android.platform.test.ravenwood.ravenizer.innerRunnerAnotType +import com.android.platform.test.ravenwood.ravenizer.isTestLookingClass import com.android.platform.test.ravenwood.ravenizer.noRavenizerAnotType import com.android.platform.test.ravenwood.ravenizer.ravenwoodTestRunnerType import com.android.platform.test.ravenwood.ravenizer.ruleAnotType @@ -50,7 +49,7 @@ import org.objectweb.asm.tree.ClassNode * Class visitor to update the RunWith and inject some necessary rules. * * - Change the @RunWith(RavenwoodAwareTestRunner.class). - * - If the original class has a @RunWith(...), then change it to an @OrigRunWith(...). + * - If the original class has a @RunWith(...), then change it to an @InnerRunner(...). * - Add RavenwoodAwareTestRunner's member rules as junit rules. * - Update the order of the existing JUnit rules to make sure they don't use the MIN or MAX. */ @@ -146,7 +145,7 @@ class RunnerRewritingAdapter private constructor( /** * Inject `@RunWith(RavenwoodAwareTestRunner.class)`. If the class already has - * a `@RunWith`, then change it to add a `@OrigRunWith`. + * a `@RunWith`, then change it to add a `@InnerRunner`. */ private fun injectRunWithAnnotation() { // Extract the original RunWith annotation and its value. @@ -172,7 +171,7 @@ class RunnerRewritingAdapter private constructor( + " in class ${classInternalName.toHumanReadableClassName()}") } - // Inject an @OrigRunWith. + // Inject an @InnerRunner. visitAnnotation(innerRunnerAnotType.desc, true)!!.let { av -> av.visit("value", runWithClass) av.visitEnd() @@ -302,7 +301,7 @@ class RunnerRewritingAdapter private constructor( override fun visitCode() { visitFieldInsn(Opcodes.GETSTATIC, ravenwoodTestRunnerType.internlName, - RavenwoodAwareTestRunner.IMPLICIT_CLASS_OUTER_RULE_NAME, + IMPLICIT_CLASS_OUTER_RULE_NAME, testRuleType.desc ) visitFieldInsn(Opcodes.PUTSTATIC, @@ -313,7 +312,7 @@ class RunnerRewritingAdapter private constructor( visitFieldInsn(Opcodes.GETSTATIC, ravenwoodTestRunnerType.internlName, - RavenwoodAwareTestRunner.IMPLICIT_CLASS_INNER_RULE_NAME, + IMPLICIT_CLASS_INNER_RULE_NAME, testRuleType.desc ) visitFieldInsn(Opcodes.PUTSTATIC, @@ -361,7 +360,7 @@ class RunnerRewritingAdapter private constructor( visitVarInsn(ALOAD, 0) visitFieldInsn(Opcodes.GETSTATIC, ravenwoodTestRunnerType.internlName, - RavenwoodAwareTestRunner.IMPLICIT_INST_OUTER_RULE_NAME, + IMPLICIT_INST_OUTER_RULE_NAME, testRuleType.desc ) visitFieldInsn(Opcodes.PUTFIELD, @@ -373,7 +372,7 @@ class RunnerRewritingAdapter private constructor( visitVarInsn(ALOAD, 0) visitFieldInsn(Opcodes.GETSTATIC, ravenwoodTestRunnerType.internlName, - RavenwoodAwareTestRunner.IMPLICIT_INST_INNER_RULE_NAME, + IMPLICIT_INST_INNER_RULE_NAME, testRuleType.desc ) visitFieldInsn(Opcodes.PUTFIELD, @@ -435,6 +434,11 @@ class RunnerRewritingAdapter private constructor( } companion object { + const val IMPLICIT_CLASS_OUTER_RULE_NAME = "sImplicitClassOuterRule" + const val IMPLICIT_CLASS_INNER_RULE_NAME = "sImplicitClassInnerRule" + const val IMPLICIT_INST_OUTER_RULE_NAME = "sImplicitInstOuterRule" + const val IMPLICIT_INST_INNER_RULE_NAME = "sImplicitInstInnerRule" + fun shouldProcess(classes: ClassNodes, className: String): Boolean { if (!isTestLookingClass(classes, className)) { return false @@ -463,4 +467,4 @@ class RunnerRewritingAdapter private constructor( } } } -}
\ No newline at end of file +} diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java index 55677078f939..0c99fcfd221a 100644 --- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java +++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java @@ -209,6 +209,10 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku @Override public void onReceive(Context context, Intent intent) { final String action = intent.getAction(); + if (action == null) { + return; + } + final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL); if (DEBUG) { diff --git a/services/core/java/com/android/server/am/AppRestrictionController.java b/services/core/java/com/android/server/am/AppRestrictionController.java index 4c87e1ce357c..c036605b029f 100644 --- a/services/core/java/com/android/server/am/AppRestrictionController.java +++ b/services/core/java/com/android/server/am/AppRestrictionController.java @@ -373,7 +373,10 @@ public final class AppRestrictionController { @Override public void onReceive(Context context, Intent intent) { final String action = intent.getAction(); - switch (intent.getAction()) { + if (action == null) { + return; + } + switch (action) { case Intent.ACTION_PACKAGE_ADDED: { if (!intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) { final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1); diff --git a/services/core/java/com/android/server/integrity/parser/BinaryFileOperations.java b/services/core/java/com/android/server/integrity/parser/BinaryFileOperations.java deleted file mode 100644 index f09e035ecc78..000000000000 --- a/services/core/java/com/android/server/integrity/parser/BinaryFileOperations.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.integrity.parser; - -import static com.android.server.integrity.model.ComponentBitSize.IS_HASHED_BITS; -import static com.android.server.integrity.model.ComponentBitSize.VALUE_SIZE_BITS; - -import android.content.integrity.IntegrityUtils; - -import com.android.server.integrity.model.BitInputStream; - -import java.io.IOException; -import java.nio.ByteBuffer; - -/** - * Helper methods for reading standard data structures from {@link BitInputStream}. - */ -public class BinaryFileOperations { - - /** - * Read an string value with the given size and hash status from a {@code BitInputStream}. - * - * If the value is hashed, get the hex-encoding of the value. Serialized values are in raw form. - * All hashed values are hex-encoded. - */ - public static String getStringValue(BitInputStream bitInputStream) throws IOException { - boolean isHashedValue = bitInputStream.getNext(IS_HASHED_BITS) == 1; - int valueSize = bitInputStream.getNext(VALUE_SIZE_BITS); - return getStringValue(bitInputStream, valueSize, isHashedValue); - } - - /** - * Read an string value with the given size and hash status from a {@code BitInputStream}. - * - * If the value is hashed, get the hex-encoding of the value. Serialized values are in raw form. - * All hashed values are hex-encoded. - */ - public static String getStringValue( - BitInputStream bitInputStream, int valueSize, boolean isHashedValue) - throws IOException { - if (!isHashedValue) { - StringBuilder value = new StringBuilder(); - while (valueSize-- > 0) { - value.append((char) bitInputStream.getNext(/* numOfBits= */ 8)); - } - return value.toString(); - } - ByteBuffer byteBuffer = ByteBuffer.allocate(valueSize); - while (valueSize-- > 0) { - byteBuffer.put((byte) (bitInputStream.getNext(/* numOfBits= */ 8) & 0xFF)); - } - return IntegrityUtils.getHexDigest(byteBuffer.array()); - } - - /** Read an integer value from a {@code BitInputStream}. */ - public static int getIntValue(BitInputStream bitInputStream) throws IOException { - return bitInputStream.getNext(/* numOfBits= */ 32); - } - - /** Read an boolean value from a {@code BitInputStream}. */ - public static boolean getBooleanValue(BitInputStream bitInputStream) throws IOException { - return bitInputStream.getNext(/* numOfBits= */ 1) == 1; - } -} diff --git a/services/core/java/com/android/server/integrity/parser/LimitInputStream.java b/services/core/java/com/android/server/integrity/parser/LimitInputStream.java deleted file mode 100644 index a91bbb7dbae1..000000000000 --- a/services/core/java/com/android/server/integrity/parser/LimitInputStream.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.integrity.parser; - -import java.io.FilterInputStream; -import java.io.IOException; -import java.io.InputStream; - -/** An {@link InputStream} that basically truncates another {@link InputStream} */ -public class LimitInputStream extends FilterInputStream { - private int mReadBytes; - private final int mLimit; - - public LimitInputStream(InputStream in, int limit) { - super(in); - if (limit < 0) { - throw new IllegalArgumentException("limit " + limit + " cannot be negative"); - } - mReadBytes = 0; - mLimit = limit; - } - - @Override - public int available() throws IOException { - return Math.min(super.available(), mLimit - mReadBytes); - } - - @Override - public int read() throws IOException { - if (mReadBytes == mLimit) { - return -1; - } - mReadBytes++; - return super.read(); - } - - @Override - public int read(byte[] b) throws IOException { - return read(b, 0, b.length); - } - - @Override - public int read(byte[] b, int off, int len) throws IOException { - if (len <= 0) { - return 0; - } - int available = available(); - if (available <= 0) { - return -1; - } - int result = super.read(b, off, Math.min(len, available)); - mReadBytes += result; - return result; - } - - @Override - public long skip(long n) throws IOException { - if (n <= 0) { - return 0; - } - int available = available(); - if (available <= 0) { - return 0; - } - int bytesToSkip = (int) Math.min(available, n); - long bytesSkipped = super.skip(bytesToSkip); - mReadBytes += (int) bytesSkipped; - return bytesSkipped; - } -} diff --git a/services/core/java/com/android/server/integrity/parser/RandomAccessInputStream.java b/services/core/java/com/android/server/integrity/parser/RandomAccessInputStream.java deleted file mode 100644 index 206e6a1f0197..000000000000 --- a/services/core/java/com/android/server/integrity/parser/RandomAccessInputStream.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.integrity.parser; - -import java.io.IOException; -import java.io.InputStream; - -/** A wrapper around {@link RandomAccessObject} to turn it into a {@link InputStream}. */ -public class RandomAccessInputStream extends InputStream { - - private final RandomAccessObject mRandomAccessObject; - - private int mPosition; - - public RandomAccessInputStream(RandomAccessObject object) throws IOException { - mRandomAccessObject = object; - mPosition = 0; - } - - /** Returns the position of the file pointer. */ - public int getPosition() { - return mPosition; - } - - /** See {@link RandomAccessObject#seek(int)} */ - public void seek(int position) throws IOException { - mRandomAccessObject.seek(position); - mPosition = position; - } - - @Override - public int available() throws IOException { - return mRandomAccessObject.length() - mPosition; - } - - @Override - public void close() throws IOException { - mRandomAccessObject.close(); - } - - @Override - public int read() throws IOException { - if (available() <= 0) { - return -1; - } - mPosition++; - return mRandomAccessObject.read(); - } - - @Override - public int read(byte[] b) throws IOException { - return read(b, 0, b.length); - } - - @Override - public int read(byte[] b, int off, int len) throws IOException { - if (len <= 0) { - return 0; - } - int available = available(); - if (available <= 0) { - return -1; - } - int result = mRandomAccessObject.read(b, off, Math.min(len, available)); - mPosition += result; - return result; - } - - @Override - public long skip(long n) throws IOException { - if (n <= 0) { - return 0; - } - int available = available(); - if (available <= 0) { - return 0; - } - int skipAmount = (int) Math.min(available, n); - mPosition += skipAmount; - mRandomAccessObject.seek(mPosition); - return skipAmount; - } -} diff --git a/services/core/java/com/android/server/integrity/parser/RandomAccessObject.java b/services/core/java/com/android/server/integrity/parser/RandomAccessObject.java deleted file mode 100644 index d9b2e38b0062..000000000000 --- a/services/core/java/com/android/server/integrity/parser/RandomAccessObject.java +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.integrity.parser; - -import java.io.File; -import java.io.IOException; -import java.io.RandomAccessFile; -import java.nio.ByteBuffer; - -/** An interface for random access objects like RandomAccessFile or byte arrays. */ -public abstract class RandomAccessObject { - - /** See {@link RandomAccessFile#seek(long)}. */ - public abstract void seek(int position) throws IOException; - - /** See {@link RandomAccessFile#read()}. */ - public abstract int read() throws IOException; - - /** See {@link RandomAccessFile#read(byte[], int, int)}. */ - public abstract int read(byte[] bytes, int off, int len) throws IOException; - - /** See {@link RandomAccessFile#close()}. */ - public abstract void close() throws IOException; - - /** See {@link java.io.RandomAccessFile#length()}. */ - public abstract int length(); - - /** Static constructor from a file. */ - public static RandomAccessObject ofFile(File file) throws IOException { - return new RandomAccessFileObject(file); - } - - /** Static constructor from a byte array. */ - public static RandomAccessObject ofBytes(byte[] bytes) { - return new RandomAccessByteArrayObject(bytes); - } - - private static class RandomAccessFileObject extends RandomAccessObject { - private final RandomAccessFile mRandomAccessFile; - // We cache the length since File.length() invokes file IO. - private final int mLength; - - RandomAccessFileObject(File file) throws IOException { - long length = file.length(); - if (length > Integer.MAX_VALUE) { - throw new IOException("Unsupported file size (too big) " + length); - } - - mRandomAccessFile = new RandomAccessFile(file, /* mode= */ "r"); - mLength = (int) length; - } - - @Override - public void seek(int position) throws IOException { - mRandomAccessFile.seek(position); - } - - @Override - public int read() throws IOException { - return mRandomAccessFile.read(); - } - - @Override - public int read(byte[] bytes, int off, int len) throws IOException { - return mRandomAccessFile.read(bytes, off, len); - } - - @Override - public void close() throws IOException { - mRandomAccessFile.close(); - } - - @Override - public int length() { - return mLength; - } - } - - private static class RandomAccessByteArrayObject extends RandomAccessObject { - - private final ByteBuffer mBytes; - - RandomAccessByteArrayObject(byte[] bytes) { - mBytes = ByteBuffer.wrap(bytes); - } - - @Override - public void seek(int position) throws IOException { - mBytes.position(position); - } - - @Override - public int read() throws IOException { - if (!mBytes.hasRemaining()) { - return -1; - } - - return mBytes.get() & 0xFF; - } - - @Override - public int read(byte[] bytes, int off, int len) throws IOException { - int bytesToCopy = Math.min(len, mBytes.remaining()); - if (bytesToCopy <= 0) { - return 0; - } - mBytes.get(bytes, off, len); - return bytesToCopy; - } - - @Override - public void close() throws IOException {} - - @Override - public int length() { - return mBytes.capacity(); - } - } -} diff --git a/services/core/java/com/android/server/integrity/parser/RuleBinaryParser.java b/services/core/java/com/android/server/integrity/parser/RuleBinaryParser.java deleted file mode 100644 index ea3a3d5f1c60..000000000000 --- a/services/core/java/com/android/server/integrity/parser/RuleBinaryParser.java +++ /dev/null @@ -1,193 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.integrity.parser; - -import static com.android.server.integrity.model.ComponentBitSize.ATOMIC_FORMULA_START; -import static com.android.server.integrity.model.ComponentBitSize.BYTE_BITS; -import static com.android.server.integrity.model.ComponentBitSize.COMPOUND_FORMULA_END; -import static com.android.server.integrity.model.ComponentBitSize.COMPOUND_FORMULA_START; -import static com.android.server.integrity.model.ComponentBitSize.CONNECTOR_BITS; -import static com.android.server.integrity.model.ComponentBitSize.EFFECT_BITS; -import static com.android.server.integrity.model.ComponentBitSize.FORMAT_VERSION_BITS; -import static com.android.server.integrity.model.ComponentBitSize.INSTALLER_ALLOWED_BY_MANIFEST_START; -import static com.android.server.integrity.model.ComponentBitSize.IS_HASHED_BITS; -import static com.android.server.integrity.model.ComponentBitSize.KEY_BITS; -import static com.android.server.integrity.model.ComponentBitSize.OPERATOR_BITS; -import static com.android.server.integrity.model.ComponentBitSize.SEPARATOR_BITS; -import static com.android.server.integrity.model.ComponentBitSize.SIGNAL_BIT; -import static com.android.server.integrity.model.ComponentBitSize.VALUE_SIZE_BITS; -import static com.android.server.integrity.parser.BinaryFileOperations.getBooleanValue; -import static com.android.server.integrity.parser.BinaryFileOperations.getIntValue; -import static com.android.server.integrity.parser.BinaryFileOperations.getStringValue; - -import android.content.integrity.AtomicFormula; -import android.content.integrity.CompoundFormula; -import android.content.integrity.InstallerAllowedByManifestFormula; -import android.content.integrity.IntegrityFormula; -import android.content.integrity.Rule; - -import com.android.server.integrity.model.BitInputStream; - -import java.io.BufferedInputStream; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -/** A helper class to parse rules into the {@link Rule} model from Binary representation. */ -public class RuleBinaryParser implements RuleParser { - - @Override - public List<Rule> parse(byte[] ruleBytes) throws RuleParseException { - return parse(RandomAccessObject.ofBytes(ruleBytes), Collections.emptyList()); - } - - @Override - public List<Rule> parse(RandomAccessObject randomAccessObject, List<RuleIndexRange> indexRanges) - throws RuleParseException { - try (RandomAccessInputStream randomAccessInputStream = - new RandomAccessInputStream(randomAccessObject)) { - return parseRules(randomAccessInputStream, indexRanges); - } catch (Exception e) { - throw new RuleParseException(e.getMessage(), e); - } - } - - private List<Rule> parseRules( - RandomAccessInputStream randomAccessInputStream, List<RuleIndexRange> indexRanges) - throws IOException { - - // Read the rule binary file format version. - randomAccessInputStream.skip(FORMAT_VERSION_BITS / BYTE_BITS); - - return indexRanges.isEmpty() - ? parseAllRules(randomAccessInputStream) - : parseIndexedRules(randomAccessInputStream, indexRanges); - } - - private List<Rule> parseAllRules(RandomAccessInputStream randomAccessInputStream) - throws IOException { - List<Rule> parsedRules = new ArrayList<>(); - - BitInputStream inputStream = - new BitInputStream(new BufferedInputStream(randomAccessInputStream)); - while (inputStream.hasNext()) { - if (inputStream.getNext(SIGNAL_BIT) == 1) { - parsedRules.add(parseRule(inputStream)); - } - } - - return parsedRules; - } - - private List<Rule> parseIndexedRules( - RandomAccessInputStream randomAccessInputStream, List<RuleIndexRange> indexRanges) - throws IOException { - List<Rule> parsedRules = new ArrayList<>(); - - for (RuleIndexRange range : indexRanges) { - randomAccessInputStream.seek(range.getStartIndex()); - - BitInputStream inputStream = - new BitInputStream( - new BufferedInputStream( - new LimitInputStream( - randomAccessInputStream, - range.getEndIndex() - range.getStartIndex()))); - - // Read the rules until we reach the end index. available() here is not reliable. - while (inputStream.hasNext()) { - if (inputStream.getNext(SIGNAL_BIT) == 1) { - parsedRules.add(parseRule(inputStream)); - } - } - } - - return parsedRules; - } - - private Rule parseRule(BitInputStream bitInputStream) throws IOException { - IntegrityFormula formula = parseFormula(bitInputStream); - int effect = bitInputStream.getNext(EFFECT_BITS); - - if (bitInputStream.getNext(SIGNAL_BIT) != 1) { - throw new IllegalArgumentException("A rule must end with a '1' bit."); - } - - return new Rule(formula, effect); - } - - private IntegrityFormula parseFormula(BitInputStream bitInputStream) throws IOException { - int separator = bitInputStream.getNext(SEPARATOR_BITS); - switch (separator) { - case ATOMIC_FORMULA_START: - return parseAtomicFormula(bitInputStream); - case COMPOUND_FORMULA_START: - return parseCompoundFormula(bitInputStream); - case COMPOUND_FORMULA_END: - return null; - case INSTALLER_ALLOWED_BY_MANIFEST_START: - return new InstallerAllowedByManifestFormula(); - default: - throw new IllegalArgumentException( - String.format("Unknown formula separator: %s", separator)); - } - } - - private CompoundFormula parseCompoundFormula(BitInputStream bitInputStream) throws IOException { - int connector = bitInputStream.getNext(CONNECTOR_BITS); - List<IntegrityFormula> formulas = new ArrayList<>(); - - IntegrityFormula parsedFormula = parseFormula(bitInputStream); - while (parsedFormula != null) { - formulas.add(parsedFormula); - parsedFormula = parseFormula(bitInputStream); - } - - return new CompoundFormula(connector, formulas); - } - - private AtomicFormula parseAtomicFormula(BitInputStream bitInputStream) throws IOException { - int key = bitInputStream.getNext(KEY_BITS); - int operator = bitInputStream.getNext(OPERATOR_BITS); - - switch (key) { - case AtomicFormula.PACKAGE_NAME: - case AtomicFormula.APP_CERTIFICATE: - case AtomicFormula.APP_CERTIFICATE_LINEAGE: - case AtomicFormula.INSTALLER_NAME: - case AtomicFormula.INSTALLER_CERTIFICATE: - case AtomicFormula.STAMP_CERTIFICATE_HASH: - boolean isHashedValue = bitInputStream.getNext(IS_HASHED_BITS) == 1; - int valueSize = bitInputStream.getNext(VALUE_SIZE_BITS); - String stringValue = getStringValue(bitInputStream, valueSize, isHashedValue); - return new AtomicFormula.StringAtomicFormula(key, stringValue, isHashedValue); - case AtomicFormula.VERSION_CODE: - // TODO(b/147880712): temporary hack until our input handles long - long upper = getIntValue(bitInputStream); - long lower = getIntValue(bitInputStream); - long longValue = (upper << 32) | lower; - return new AtomicFormula.LongAtomicFormula(key, operator, longValue); - case AtomicFormula.PRE_INSTALLED: - case AtomicFormula.STAMP_TRUSTED: - boolean booleanValue = getBooleanValue(bitInputStream); - return new AtomicFormula.BooleanAtomicFormula(key, booleanValue); - default: - throw new IllegalArgumentException(String.format("Unknown key: %d", key)); - } - } -} diff --git a/services/core/java/com/android/server/integrity/parser/RuleIndexRange.java b/services/core/java/com/android/server/integrity/parser/RuleIndexRange.java deleted file mode 100644 index 408df5a784ff..000000000000 --- a/services/core/java/com/android/server/integrity/parser/RuleIndexRange.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.integrity.parser; - -import android.annotation.Nullable; - -/** - * A wrapper class to represent an indexing range. - */ -public class RuleIndexRange { - private int mStartIndex; - private int mEndIndex; - - /** Constructor with start and end indexes. */ - public RuleIndexRange(int startIndex, int endIndex) { - this.mStartIndex = startIndex; - this.mEndIndex = endIndex; - } - - /** Returns the startIndex. */ - public int getStartIndex() { - return mStartIndex; - } - - /** Returns the end index. */ - public int getEndIndex() { - return mEndIndex; - } - - @Override - public boolean equals(@Nullable Object object) { - return mStartIndex == ((RuleIndexRange) object).getStartIndex() - && mEndIndex == ((RuleIndexRange) object).getEndIndex(); - } - - @Override - public String toString() { - return String.format("Range{%d, %d}", mStartIndex, mEndIndex); - } -} diff --git a/services/core/java/com/android/server/integrity/parser/RuleParser.java b/services/core/java/com/android/server/integrity/parser/RuleParser.java deleted file mode 100644 index 126dacc4fa40..000000000000 --- a/services/core/java/com/android/server/integrity/parser/RuleParser.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.integrity.parser; - -import android.content.integrity.Rule; - -import java.util.List; - -/** A helper class to parse rules into the {@link Rule} model. */ -public interface RuleParser { - - /** Parse rules from bytes. */ - List<Rule> parse(byte[] ruleBytes) throws RuleParseException; - - /** Parse rules from an input stream. */ - List<Rule> parse(RandomAccessObject randomAccessObject, List<RuleIndexRange> ruleIndexRanges) - throws RuleParseException; -} diff --git a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java index 980f40ee4a9a..9b02ed0ee0bd 100644 --- a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java +++ b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java @@ -1597,6 +1597,9 @@ public final class NotificationAttentionHelper { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); + if (action == null) { + return; + } if (action.equals(Intent.ACTION_SCREEN_ON)) { // Keep track of screen on/off state, but do not turn off the notification light diff --git a/services/core/java/com/android/server/slice/SliceManagerService.java b/services/core/java/com/android/server/slice/SliceManagerService.java index 881bdbd3bc6a..15fd35e15f83 100644 --- a/services/core/java/com/android/server/slice/SliceManagerService.java +++ b/services/core/java/com/android/server/slice/SliceManagerService.java @@ -604,6 +604,11 @@ public class SliceManagerService extends ISliceManager.Stub { private final BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { + final String action = intent.getAction(); + if (action == null) { + Slog.w(TAG, "Intent broadcast does not contain action: " + intent); + return; + } final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL); if (userId == UserHandle.USER_NULL) { Slog.w(TAG, "Intent broadcast does not contain user handle: " + intent); @@ -615,7 +620,7 @@ public class SliceManagerService extends ISliceManager.Stub { Slog.w(TAG, "Intent broadcast does not contain package name: " + intent); return; } - switch (intent.getAction()) { + switch (action) { case Intent.ACTION_PACKAGE_REMOVED: final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false); diff --git a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java index 42c171bb4351..4e868887ea1b 100644 --- a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java +++ b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java @@ -16,6 +16,8 @@ package com.android.server.profcollect; +import android.Manifest; +import android.annotation.RequiresPermission; import android.app.job.JobInfo; import android.app.job.JobParameters; import android.app.job.JobScheduler; @@ -88,10 +90,11 @@ public final class ProfcollectForwardingService extends SystemService { createAndUploadReport(sSelfService); } if (UsbManager.ACTION_USB_STATE.equals(intent.getAction())) { - boolean connected = intent.getBooleanExtra(UsbManager.USB_CONNECTED, false); boolean isADB = intent.getBooleanExtra(UsbManager.USB_FUNCTION_ADB, false); if (isADB) { - Log.d(LOG_TAG, "Received broadcast that ADB became " + connected); + boolean connected = intent.getBooleanExtra(UsbManager.USB_CONNECTED, false); + Log.d(LOG_TAG, "Received broadcast that ADB became " + connected + + ", was " + mAdbActive); mAdbActive = connected; } } @@ -117,9 +120,6 @@ public final class ProfcollectForwardingService extends SystemService { mUploadEnabled = context.getResources().getBoolean(R.bool.config_profcollectReportUploaderEnabled); - // TODO: ADB might already be active when our service started. - mAdbActive = false; - final IntentFilter filter = new IntentFilter(); filter.addAction(INTENT_UPLOAD_PROFILES); filter.addAction(UsbManager.ACTION_USB_STATE); @@ -140,7 +140,13 @@ public final class ProfcollectForwardingService extends SystemService { } @Override + @RequiresPermission(Manifest.permission.MANAGE_USB) public void onBootPhase(int phase) { + if (phase == PHASE_SYSTEM_SERVICES_READY) { + UsbManager usbManager = getContext().getSystemService(UsbManager.class); + mAdbActive = ((usbManager.getCurrentFunctions() & UsbManager.FUNCTION_ADB) == 1); + Log.d(LOG_TAG, "ADB is " + mAdbActive + " on system startup"); + } if (phase == PHASE_BOOT_COMPLETED) { if (mIProfcollect == null) { return; diff --git a/services/tests/servicestests/src/com/android/server/integrity/parser/BinaryFileOperationsTest.java b/services/tests/servicestests/src/com/android/server/integrity/parser/BinaryFileOperationsTest.java deleted file mode 100644 index 723b6c5af451..000000000000 --- a/services/tests/servicestests/src/com/android/server/integrity/parser/BinaryFileOperationsTest.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.integrity.parser; - -import static com.android.server.integrity.model.ComponentBitSize.VALUE_SIZE_BITS; -import static com.android.server.integrity.parser.BinaryFileOperations.getBooleanValue; -import static com.android.server.integrity.parser.BinaryFileOperations.getIntValue; -import static com.android.server.integrity.parser.BinaryFileOperations.getStringValue; -import static com.android.server.integrity.utils.TestUtils.getBits; -import static com.android.server.integrity.utils.TestUtils.getBytes; -import static com.android.server.integrity.utils.TestUtils.getValueBits; - -import static com.google.common.truth.Truth.assertThat; - -import android.content.integrity.IntegrityUtils; - -import com.android.server.integrity.model.BitInputStream; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.nio.charset.StandardCharsets; - -@RunWith(JUnit4.class) -public class BinaryFileOperationsTest { - - private static final String IS_NOT_HASHED = "0"; - private static final String IS_HASHED = "1"; - private static final String PACKAGE_NAME = "com.test.app"; - private static final String APP_CERTIFICATE = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; - - @Test - public void testGetStringValue() throws IOException { - byte[] stringBytes = - getBytes( - IS_NOT_HASHED - + getBits(PACKAGE_NAME.length(), VALUE_SIZE_BITS) - + getValueBits(PACKAGE_NAME)); - BitInputStream inputStream = new BitInputStream(new ByteArrayInputStream(stringBytes)); - - String resultString = getStringValue(inputStream); - - assertThat(resultString).isEqualTo(PACKAGE_NAME); - } - - @Test - public void testGetHashedStringValue() throws IOException { - byte[] ruleBytes = - getBytes( - IS_HASHED - + getBits(APP_CERTIFICATE.length(), VALUE_SIZE_BITS) - + getValueBits(APP_CERTIFICATE)); - BitInputStream inputStream = new BitInputStream(new ByteArrayInputStream(ruleBytes)); - - String resultString = getStringValue(inputStream); - - assertThat(resultString) - .isEqualTo(IntegrityUtils.getHexDigest( - APP_CERTIFICATE.getBytes(StandardCharsets.UTF_8))); - } - - @Test - public void testGetStringValue_withSizeAndHashingInfo() throws IOException { - byte[] ruleBytes = getBytes(getValueBits(PACKAGE_NAME)); - BitInputStream inputStream = new BitInputStream(new ByteArrayInputStream(ruleBytes)); - - String resultString = getStringValue(inputStream, - PACKAGE_NAME.length(), /* isHashedValue= */false); - - assertThat(resultString).isEqualTo(PACKAGE_NAME); - } - - @Test - public void testGetIntValue() throws IOException { - int randomValue = 15; - byte[] ruleBytes = getBytes(getBits(randomValue, /* numOfBits= */ 32)); - BitInputStream inputStream = new BitInputStream(new ByteArrayInputStream(ruleBytes)); - - assertThat(getIntValue(inputStream)).isEqualTo(randomValue); - } - - @Test - public void testGetBooleanValue_true() throws IOException { - String booleanValue = "1"; - byte[] ruleBytes = getBytes(booleanValue); - BitInputStream inputStream = new BitInputStream(new ByteArrayInputStream(ruleBytes)); - - assertThat(getBooleanValue(inputStream)).isEqualTo(true); - } - - @Test - public void testGetBooleanValue_false() throws IOException { - String booleanValue = "0"; - byte[] ruleBytes = getBytes(booleanValue); - BitInputStream inputStream = new BitInputStream(new ByteArrayInputStream(ruleBytes)); - - assertThat(getBooleanValue(inputStream)).isEqualTo(false); - } -} diff --git a/services/tests/servicestests/src/com/android/server/integrity/parser/RuleBinaryParserTest.java b/services/tests/servicestests/src/com/android/server/integrity/parser/RuleBinaryParserTest.java deleted file mode 100644 index 03363a100841..000000000000 --- a/services/tests/servicestests/src/com/android/server/integrity/parser/RuleBinaryParserTest.java +++ /dev/null @@ -1,693 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.integrity.parser; - -import static com.android.server.integrity.model.ComponentBitSize.ATOMIC_FORMULA_START; -import static com.android.server.integrity.model.ComponentBitSize.COMPOUND_FORMULA_END; -import static com.android.server.integrity.model.ComponentBitSize.COMPOUND_FORMULA_START; -import static com.android.server.integrity.model.ComponentBitSize.CONNECTOR_BITS; -import static com.android.server.integrity.model.ComponentBitSize.DEFAULT_FORMAT_VERSION; -import static com.android.server.integrity.model.ComponentBitSize.EFFECT_BITS; -import static com.android.server.integrity.model.ComponentBitSize.FORMAT_VERSION_BITS; -import static com.android.server.integrity.model.ComponentBitSize.KEY_BITS; -import static com.android.server.integrity.model.ComponentBitSize.OPERATOR_BITS; -import static com.android.server.integrity.model.ComponentBitSize.SEPARATOR_BITS; -import static com.android.server.integrity.model.ComponentBitSize.VALUE_SIZE_BITS; -import static com.android.server.integrity.utils.TestUtils.getBits; -import static com.android.server.integrity.utils.TestUtils.getBytes; -import static com.android.server.integrity.utils.TestUtils.getValueBits; -import static com.android.server.testutils.TestUtils.assertExpectException; - -import static com.google.common.truth.Truth.assertThat; - -import android.content.integrity.AtomicFormula; -import android.content.integrity.CompoundFormula; -import android.content.integrity.IntegrityUtils; -import android.content.integrity.Rule; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -@RunWith(JUnit4.class) -public class RuleBinaryParserTest { - - private static final String COMPOUND_FORMULA_START_BITS = - getBits(COMPOUND_FORMULA_START, SEPARATOR_BITS); - private static final String COMPOUND_FORMULA_END_BITS = - getBits(COMPOUND_FORMULA_END, SEPARATOR_BITS); - private static final String ATOMIC_FORMULA_START_BITS = - getBits(ATOMIC_FORMULA_START, SEPARATOR_BITS); - private static final int INVALID_FORMULA_SEPARATOR_VALUE = (1 << SEPARATOR_BITS) - 1; - private static final String INVALID_FORMULA_SEPARATOR_BITS = - getBits(INVALID_FORMULA_SEPARATOR_VALUE, SEPARATOR_BITS); - - private static final String NOT = getBits(CompoundFormula.NOT, CONNECTOR_BITS); - private static final String AND = getBits(CompoundFormula.AND, CONNECTOR_BITS); - private static final String OR = getBits(CompoundFormula.OR, CONNECTOR_BITS); - private static final int INVALID_CONNECTOR_VALUE = 3; - private static final String INVALID_CONNECTOR = - getBits(INVALID_CONNECTOR_VALUE, CONNECTOR_BITS); - - private static final String PACKAGE_NAME = getBits(AtomicFormula.PACKAGE_NAME, KEY_BITS); - private static final String APP_CERTIFICATE = getBits(AtomicFormula.APP_CERTIFICATE, KEY_BITS); - private static final String APP_CERTIFICATE_LINEAGE = - getBits(AtomicFormula.APP_CERTIFICATE_LINEAGE, KEY_BITS); - private static final String VERSION_CODE = getBits(AtomicFormula.VERSION_CODE, KEY_BITS); - private static final String PRE_INSTALLED = getBits(AtomicFormula.PRE_INSTALLED, KEY_BITS); - private static final int INVALID_KEY_VALUE = 9; - private static final String INVALID_KEY = getBits(INVALID_KEY_VALUE, KEY_BITS); - - private static final String EQ = getBits(AtomicFormula.EQ, OPERATOR_BITS); - private static final int INVALID_OPERATOR_VALUE = 5; - private static final String INVALID_OPERATOR = getBits(INVALID_OPERATOR_VALUE, OPERATOR_BITS); - - private static final String IS_NOT_HASHED = "0"; - private static final String IS_HASHED = "1"; - - private static final String DENY = getBits(Rule.DENY, EFFECT_BITS); - private static final int INVALID_EFFECT_VALUE = 5; - private static final String INVALID_EFFECT = getBits(INVALID_EFFECT_VALUE, EFFECT_BITS); - - private static final String START_BIT = "1"; - private static final String END_BIT = "1"; - private static final String INVALID_MARKER_BIT = "0"; - - private static final byte[] DEFAULT_FORMAT_VERSION_BYTES = - getBytes(getBits(DEFAULT_FORMAT_VERSION, FORMAT_VERSION_BITS)); - - private static final List<RuleIndexRange> NO_INDEXING = Collections.emptyList(); - - @Test - public void testBinaryStream_validCompoundFormula_noIndexing() throws Exception { - String packageName = "com.test.app"; - String ruleBits = - START_BIT - + COMPOUND_FORMULA_START_BITS - + NOT - + ATOMIC_FORMULA_START_BITS - + PACKAGE_NAME - + EQ - + IS_NOT_HASHED - + getBits(packageName.length(), VALUE_SIZE_BITS) - + getValueBits(packageName) - + COMPOUND_FORMULA_END_BITS - + DENY - + END_BIT; - byte[] ruleBytes = getBytes(ruleBits); - ByteBuffer rule = - ByteBuffer.allocate(DEFAULT_FORMAT_VERSION_BYTES.length + ruleBytes.length); - rule.put(DEFAULT_FORMAT_VERSION_BYTES); - rule.put(ruleBytes); - RuleParser binaryParser = new RuleBinaryParser(); - Rule expectedRule = - new Rule( - new CompoundFormula( - CompoundFormula.NOT, - Collections.singletonList( - new AtomicFormula.StringAtomicFormula( - AtomicFormula.PACKAGE_NAME, - packageName, - /* isHashedValue= */ false))), - Rule.DENY); - - List<Rule> rules = - binaryParser.parse(RandomAccessObject.ofBytes(rule.array()), NO_INDEXING); - - assertThat(rules).isEqualTo(Collections.singletonList(expectedRule)); - } - - @Test - public void testBinaryString_validCompoundFormula_notConnector_noIndexing() throws Exception { - String packageName = "com.test.app"; - String ruleBits = - START_BIT - + COMPOUND_FORMULA_START_BITS - + NOT - + ATOMIC_FORMULA_START_BITS - + PACKAGE_NAME - + EQ - + IS_NOT_HASHED - + getBits(packageName.length(), VALUE_SIZE_BITS) - + getValueBits(packageName) - + COMPOUND_FORMULA_END_BITS - + DENY - + END_BIT; - byte[] ruleBytes = getBytes(ruleBits); - ByteBuffer rule = - ByteBuffer.allocate(DEFAULT_FORMAT_VERSION_BYTES.length + ruleBytes.length); - rule.put(DEFAULT_FORMAT_VERSION_BYTES); - rule.put(ruleBytes); - RuleParser binaryParser = new RuleBinaryParser(); - Rule expectedRule = - new Rule( - new CompoundFormula( - CompoundFormula.NOT, - Collections.singletonList( - new AtomicFormula.StringAtomicFormula( - AtomicFormula.PACKAGE_NAME, - packageName, - /* isHashedValue= */ false))), - Rule.DENY); - - List<Rule> rules = binaryParser.parse(rule.array()); - - assertThat(rules).isEqualTo(Collections.singletonList(expectedRule)); - } - - @Test - public void testBinaryString_validCompoundFormula_andConnector_noIndexing() throws Exception { - String packageName = "com.test.app"; - String appCertificate = "test_cert"; - String ruleBits = - START_BIT - + COMPOUND_FORMULA_START_BITS - + AND - + ATOMIC_FORMULA_START_BITS - + PACKAGE_NAME - + EQ - + IS_NOT_HASHED - + getBits(packageName.length(), VALUE_SIZE_BITS) - + getValueBits(packageName) - + ATOMIC_FORMULA_START_BITS - + APP_CERTIFICATE - + EQ - + IS_NOT_HASHED - + getBits(appCertificate.length(), VALUE_SIZE_BITS) - + getValueBits(appCertificate) - + COMPOUND_FORMULA_END_BITS - + DENY - + END_BIT; - byte[] ruleBytes = getBytes(ruleBits); - ByteBuffer rule = - ByteBuffer.allocate(DEFAULT_FORMAT_VERSION_BYTES.length + ruleBytes.length); - rule.put(DEFAULT_FORMAT_VERSION_BYTES); - rule.put(ruleBytes); - RuleParser binaryParser = new RuleBinaryParser(); - Rule expectedRule = - new Rule( - new CompoundFormula( - CompoundFormula.AND, - Arrays.asList( - new AtomicFormula.StringAtomicFormula( - AtomicFormula.PACKAGE_NAME, - packageName, - /* isHashedValue= */ false), - new AtomicFormula.StringAtomicFormula( - AtomicFormula.APP_CERTIFICATE, - appCertificate, - /* isHashedValue= */ false))), - Rule.DENY); - List<Rule> rules = binaryParser.parse(rule.array()); - - assertThat(rules).isEqualTo(Collections.singletonList(expectedRule)); - } - - @Test - public void testBinaryString_validCompoundFormula_orConnector_noIndexing() throws Exception { - String packageName = "com.test.app"; - String appCertificate = "test_cert"; - String ruleBits = - START_BIT - + COMPOUND_FORMULA_START_BITS - + OR - + ATOMIC_FORMULA_START_BITS - + PACKAGE_NAME - + EQ - + IS_NOT_HASHED - + getBits(packageName.length(), VALUE_SIZE_BITS) - + getValueBits(packageName) - + ATOMIC_FORMULA_START_BITS - + APP_CERTIFICATE - + EQ - + IS_NOT_HASHED - + getBits(appCertificate.length(), VALUE_SIZE_BITS) - + getValueBits(appCertificate) - + COMPOUND_FORMULA_END_BITS - + DENY - + END_BIT; - byte[] ruleBytes = getBytes(ruleBits); - ByteBuffer rule = - ByteBuffer.allocate(DEFAULT_FORMAT_VERSION_BYTES.length + ruleBytes.length); - rule.put(DEFAULT_FORMAT_VERSION_BYTES); - rule.put(ruleBytes); - RuleParser binaryParser = new RuleBinaryParser(); - Rule expectedRule = - new Rule( - new CompoundFormula( - CompoundFormula.OR, - Arrays.asList( - new AtomicFormula.StringAtomicFormula( - AtomicFormula.PACKAGE_NAME, - packageName, - /* isHashedValue= */ false), - new AtomicFormula.StringAtomicFormula( - AtomicFormula.APP_CERTIFICATE, - appCertificate, - /* isHashedValue= */ false))), - Rule.DENY); - - List<Rule> rules = binaryParser.parse(rule.array()); - - assertThat(rules).isEqualTo(Collections.singletonList(expectedRule)); - } - - @Test - public void testBinaryString_validAtomicFormula_stringValue_noIndexing() throws Exception { - String packageName = "com.test.app"; - String ruleBits = - START_BIT - + ATOMIC_FORMULA_START_BITS - + PACKAGE_NAME - + EQ - + IS_NOT_HASHED - + getBits(packageName.length(), VALUE_SIZE_BITS) - + getValueBits(packageName) - + DENY - + END_BIT; - byte[] ruleBytes = getBytes(ruleBits); - ByteBuffer rule = - ByteBuffer.allocate(DEFAULT_FORMAT_VERSION_BYTES.length + ruleBytes.length); - rule.put(DEFAULT_FORMAT_VERSION_BYTES); - rule.put(ruleBytes); - RuleParser binaryParser = new RuleBinaryParser(); - Rule expectedRule = - new Rule( - new AtomicFormula.StringAtomicFormula( - AtomicFormula.PACKAGE_NAME, - packageName, - /* isHashedValue= */ false), - Rule.DENY); - - List<Rule> rules = binaryParser.parse(rule.array()); - - assertThat(rules).isEqualTo(Collections.singletonList(expectedRule)); - } - - @Test - public void testBinaryString_validAtomicFormula_hashedValue_noIndexing() throws Exception { - String appCertificate = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; - String ruleBits = - START_BIT - + ATOMIC_FORMULA_START_BITS - + APP_CERTIFICATE - + EQ - + IS_HASHED - + getBits(appCertificate.length(), VALUE_SIZE_BITS) - + getValueBits(appCertificate) - + DENY - + END_BIT; - byte[] ruleBytes = getBytes(ruleBits); - ByteBuffer rule = - ByteBuffer.allocate(DEFAULT_FORMAT_VERSION_BYTES.length + ruleBytes.length); - rule.put(DEFAULT_FORMAT_VERSION_BYTES); - rule.put(ruleBytes); - - RuleParser binaryParser = new RuleBinaryParser(); - Rule expectedRule = - new Rule( - new AtomicFormula.StringAtomicFormula( - AtomicFormula.APP_CERTIFICATE, - IntegrityUtils.getHexDigest( - appCertificate.getBytes(StandardCharsets.UTF_8)), - /* isHashedValue= */ true), - Rule.DENY); - - List<Rule> rules = binaryParser.parse(rule.array()); - - assertThat(rules).isEqualTo(Collections.singletonList(expectedRule)); - } - - @Test - public void testBinaryString_validAtomicFormulaWithCertificateLineage() throws Exception { - String appCertificate = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; - String ruleBits = - START_BIT - + ATOMIC_FORMULA_START_BITS - + APP_CERTIFICATE_LINEAGE - + EQ - + IS_HASHED - + getBits(appCertificate.length(), VALUE_SIZE_BITS) - + getValueBits(appCertificate) - + DENY - + END_BIT; - byte[] ruleBytes = getBytes(ruleBits); - ByteBuffer rule = - ByteBuffer.allocate(DEFAULT_FORMAT_VERSION_BYTES.length + ruleBytes.length); - rule.put(DEFAULT_FORMAT_VERSION_BYTES); - rule.put(ruleBytes); - - RuleParser binaryParser = new RuleBinaryParser(); - Rule expectedRule = - new Rule( - new AtomicFormula.StringAtomicFormula( - AtomicFormula.APP_CERTIFICATE_LINEAGE, - IntegrityUtils.getHexDigest( - appCertificate.getBytes(StandardCharsets.UTF_8)), - /* isHashedValue= */ true), - Rule.DENY); - - List<Rule> rules = binaryParser.parse(rule.array()); - - assertThat(rules).isEqualTo(Collections.singletonList(expectedRule)); - } - - @Test - public void testBinaryString_validAtomicFormula_integerValue_noIndexing() throws Exception { - int versionCode = 1; - String ruleBits = - START_BIT - + ATOMIC_FORMULA_START_BITS - + VERSION_CODE - + EQ - + getBits(versionCode, /* numOfBits= */ 64) - + DENY - + END_BIT; - byte[] ruleBytes = getBytes(ruleBits); - ByteBuffer rule = - ByteBuffer.allocate(DEFAULT_FORMAT_VERSION_BYTES.length + ruleBytes.length); - rule.put(DEFAULT_FORMAT_VERSION_BYTES); - rule.put(ruleBytes); - RuleParser binaryParser = new RuleBinaryParser(); - Rule expectedRule = - new Rule( - new AtomicFormula.LongAtomicFormula( - AtomicFormula.VERSION_CODE, AtomicFormula.EQ, 1), - Rule.DENY); - - List<Rule> rules = binaryParser.parse(rule.array()); - - assertThat(rules).isEqualTo(Collections.singletonList(expectedRule)); - } - - @Test - public void testBinaryString_validAtomicFormula_booleanValue_noIndexing() throws Exception { - String isPreInstalled = "1"; - String ruleBits = - START_BIT - + ATOMIC_FORMULA_START_BITS - + PRE_INSTALLED - + EQ - + isPreInstalled - + DENY - + END_BIT; - byte[] ruleBytes = getBytes(ruleBits); - ByteBuffer rule = - ByteBuffer.allocate(DEFAULT_FORMAT_VERSION_BYTES.length + ruleBytes.length); - rule.put(DEFAULT_FORMAT_VERSION_BYTES); - rule.put(ruleBytes); - RuleParser binaryParser = new RuleBinaryParser(); - Rule expectedRule = - new Rule( - new AtomicFormula.BooleanAtomicFormula( - AtomicFormula.PRE_INSTALLED, true), - Rule.DENY); - - List<Rule> rules = binaryParser.parse(rule.array()); - - assertThat(rules).isEqualTo(Collections.singletonList(expectedRule)); - } - - @Test - public void testBinaryString_invalidAtomicFormula_noIndexing() { - int versionCode = 1; - String ruleBits = - START_BIT - + ATOMIC_FORMULA_START_BITS - + VERSION_CODE - + EQ - + getBits(versionCode, /* numOfBits= */ 64) - + DENY; - byte[] ruleBytes = getBytes(ruleBits); - ByteBuffer rule = - ByteBuffer.allocate(DEFAULT_FORMAT_VERSION_BYTES.length + ruleBytes.length); - rule.put(DEFAULT_FORMAT_VERSION_BYTES); - rule.put(ruleBytes); - RuleParser binaryParser = new RuleBinaryParser(); - - assertExpectException( - RuleParseException.class, - /* expectedExceptionMessageRegex= */ "A rule must end with a '1' bit.", - () -> binaryParser.parse(rule.array())); - } - - @Test - public void testBinaryString_withNoRuleList_noIndexing() throws RuleParseException { - ByteBuffer rule = ByteBuffer.allocate(DEFAULT_FORMAT_VERSION_BYTES.length); - rule.put(DEFAULT_FORMAT_VERSION_BYTES); - RuleParser binaryParser = new RuleBinaryParser(); - - List<Rule> rules = binaryParser.parse(rule.array()); - - assertThat(rules).isEmpty(); - } - - @Test - public void testBinaryString_withEmptyRule_noIndexing() { - String ruleBits = START_BIT; - byte[] ruleBytes = getBytes(ruleBits); - ByteBuffer rule = - ByteBuffer.allocate(DEFAULT_FORMAT_VERSION_BYTES.length + ruleBytes.length); - rule.put(DEFAULT_FORMAT_VERSION_BYTES); - rule.put(ruleBytes); - RuleParser binaryParser = new RuleBinaryParser(); - - assertExpectException( - RuleParseException.class, - /* expectedExceptionMessageRegex */ "", - () -> binaryParser.parse(rule.array())); - } - - @Test - public void testBinaryString_invalidCompoundFormula_invalidNumberOfFormulas_noIndexing() { - String packageName = "com.test.app"; - String appCertificate = "test_cert"; - String ruleBits = - START_BIT - + COMPOUND_FORMULA_START_BITS - + NOT - + ATOMIC_FORMULA_START_BITS - + PACKAGE_NAME - + EQ - + IS_NOT_HASHED - + getBits(packageName.length(), VALUE_SIZE_BITS) - + getValueBits(packageName) - + ATOMIC_FORMULA_START_BITS - + APP_CERTIFICATE - + EQ - + IS_NOT_HASHED - + getBits(appCertificate.length(), VALUE_SIZE_BITS) - + getValueBits(appCertificate) - + COMPOUND_FORMULA_END_BITS - + DENY - + END_BIT; - byte[] ruleBytes = getBytes(ruleBits); - ByteBuffer rule = - ByteBuffer.allocate(DEFAULT_FORMAT_VERSION_BYTES.length + ruleBytes.length); - rule.put(DEFAULT_FORMAT_VERSION_BYTES); - rule.put(ruleBytes); - RuleParser binaryParser = new RuleBinaryParser(); - - assertExpectException( - RuleParseException.class, - /* expectedExceptionMessageRegex */ "Connector NOT must have 1 formula only", - () -> binaryParser.parse(rule.array())); - } - - @Test - public void testBinaryString_invalidRule_invalidOperator_noIndexing() { - int versionCode = 1; - String ruleBits = - START_BIT - + COMPOUND_FORMULA_START_BITS - + NOT - + ATOMIC_FORMULA_START_BITS - + VERSION_CODE - + INVALID_OPERATOR - + getBits(versionCode, /* numOfBits= */ 64) - + COMPOUND_FORMULA_END_BITS - + DENY - + END_BIT; - byte[] ruleBytes = getBytes(ruleBits); - ByteBuffer rule = - ByteBuffer.allocate(DEFAULT_FORMAT_VERSION_BYTES.length + ruleBytes.length); - rule.put(DEFAULT_FORMAT_VERSION_BYTES); - rule.put(ruleBytes); - RuleParser binaryParser = new RuleBinaryParser(); - - assertExpectException( - RuleParseException.class, - /* expectedExceptionMessageRegex */ String.format( - "Unknown operator: %d", INVALID_OPERATOR_VALUE), - () -> binaryParser.parse(rule.array())); - } - - @Test - public void testBinaryString_invalidRule_invalidEffect_noIndexing() { - String packageName = "com.test.app"; - String ruleBits = - START_BIT - + COMPOUND_FORMULA_START_BITS - + NOT - + ATOMIC_FORMULA_START_BITS - + PACKAGE_NAME - + EQ - + IS_NOT_HASHED - + getBits(packageName.length(), VALUE_SIZE_BITS) - + getValueBits(packageName) - + COMPOUND_FORMULA_END_BITS - + INVALID_EFFECT - + END_BIT; - byte[] ruleBytes = getBytes(ruleBits); - ByteBuffer rule = - ByteBuffer.allocate(DEFAULT_FORMAT_VERSION_BYTES.length + ruleBytes.length); - rule.put(DEFAULT_FORMAT_VERSION_BYTES); - rule.put(ruleBytes); - RuleParser binaryParser = new RuleBinaryParser(); - - assertExpectException( - RuleParseException.class, - /* expectedExceptionMessageRegex */ String.format( - "Unknown effect: %d", INVALID_EFFECT_VALUE), - () -> binaryParser.parse(rule.array())); - } - - @Test - public void testBinaryString_invalidRule_invalidConnector_noIndexing() { - String packageName = "com.test.app"; - String ruleBits = - START_BIT - + COMPOUND_FORMULA_START_BITS - + INVALID_CONNECTOR - + ATOMIC_FORMULA_START_BITS - + PACKAGE_NAME - + EQ - + IS_NOT_HASHED - + getBits(packageName.length(), VALUE_SIZE_BITS) - + getValueBits(packageName) - + COMPOUND_FORMULA_END_BITS - + DENY - + END_BIT; - byte[] ruleBytes = getBytes(ruleBits); - ByteBuffer rule = - ByteBuffer.allocate(DEFAULT_FORMAT_VERSION_BYTES.length + ruleBytes.length); - rule.put(DEFAULT_FORMAT_VERSION_BYTES); - rule.put(ruleBytes); - RuleParser binaryParser = new RuleBinaryParser(); - - assertExpectException( - RuleParseException.class, - /* expectedExceptionMessageRegex */ String.format( - "Unknown connector: %d", INVALID_CONNECTOR_VALUE), - () -> binaryParser.parse(rule.array())); - } - - @Test - public void testBinaryString_invalidRule_invalidKey_noIndexing() { - String packageName = "com.test.app"; - String ruleBits = - START_BIT - + COMPOUND_FORMULA_START_BITS - + NOT - + ATOMIC_FORMULA_START_BITS - + INVALID_KEY - + EQ - + IS_NOT_HASHED - + getBits(packageName.length(), VALUE_SIZE_BITS) - + getValueBits(packageName) - + COMPOUND_FORMULA_END_BITS - + DENY - + END_BIT; - byte[] ruleBytes = getBytes(ruleBits); - ByteBuffer rule = - ByteBuffer.allocate(DEFAULT_FORMAT_VERSION_BYTES.length + ruleBytes.length); - rule.put(DEFAULT_FORMAT_VERSION_BYTES); - rule.put(ruleBytes); - RuleParser binaryParser = new RuleBinaryParser(); - - assertExpectException( - RuleParseException.class, - /* expectedExceptionMessageRegex */ String.format( - "Unknown key: %d", INVALID_KEY_VALUE), - () -> binaryParser.parse(rule.array())); - } - - @Test - public void testBinaryString_invalidRule_invalidSeparator_noIndexing() { - String packageName = "com.test.app"; - String ruleBits = - START_BIT - + INVALID_FORMULA_SEPARATOR_BITS - + NOT - + ATOMIC_FORMULA_START_BITS - + PACKAGE_NAME - + EQ - + IS_NOT_HASHED - + getBits(packageName.length(), VALUE_SIZE_BITS) - + getValueBits(packageName) - + COMPOUND_FORMULA_END_BITS - + DENY - + END_BIT; - byte[] ruleBytes = getBytes(ruleBits); - ByteBuffer rule = - ByteBuffer.allocate(DEFAULT_FORMAT_VERSION_BYTES.length + ruleBytes.length); - rule.put(DEFAULT_FORMAT_VERSION_BYTES); - rule.put(ruleBytes); - RuleParser binaryParser = new RuleBinaryParser(); - - assertExpectException( - RuleParseException.class, - /* expectedExceptionMessageRegex */ String.format( - "Unknown formula separator: %d", INVALID_FORMULA_SEPARATOR_VALUE), - () -> binaryParser.parse(rule.array())); - } - - @Test - public void testBinaryString_invalidRule_invalidEndMarker_noIndexing() { - String packageName = "com.test.app"; - String ruleBits = - START_BIT - + COMPOUND_FORMULA_START_BITS - + NOT - + ATOMIC_FORMULA_START_BITS - + PACKAGE_NAME - + EQ - + IS_NOT_HASHED - + getBits(packageName.length(), VALUE_SIZE_BITS) - + getValueBits(packageName) - + COMPOUND_FORMULA_END_BITS - + DENY - + INVALID_MARKER_BIT; - byte[] ruleBytes = getBytes(ruleBits); - ByteBuffer rule = - ByteBuffer.allocate(DEFAULT_FORMAT_VERSION_BYTES.length + ruleBytes.length); - rule.put(DEFAULT_FORMAT_VERSION_BYTES); - rule.put(ruleBytes); - RuleParser binaryParser = new RuleBinaryParser(); - - assertExpectException( - RuleParseException.class, - /* expectedExceptionMessageRegex */ "A rule must end with a '1' bit", - () -> binaryParser.parse(rule.array())); - } -} |