diff options
author | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2024-01-04 20:01:19 +0000 |
---|---|---|
committer | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2024-01-04 20:01:19 +0000 |
commit | 3114d0b8cffe9adae8ad32922c0c337e73bf54ab (patch) | |
tree | ca12680b5b047268f7bf89297750dfb780c86bae | |
parent | ac5b3ac7a022703b338769d3ee69ae8ed65672f1 (diff) | |
parent | 0c0ed7f7abf526422d91b9d559a24b7acbc2471d (diff) | |
download | modules-utils-aml_ips_341611000.tar.gz |
Snap for 11273583 from 0c0ed7f7abf526422d91b9d559a24b7acbc2471d to mainline-ipsec-releaseaml_ips_341611000aml_ips_341510000
Change-Id: I866fa274e27e3178de87393f2cc51709d29fca2c
25 files changed, 1041 insertions, 444 deletions
diff --git a/java/Android.bp b/java/Android.bp index 670e4ed..eee1e0e 100644 --- a/java/Android.bp +++ b/java/Android.bp @@ -66,13 +66,13 @@ java_library { srcs: [ "com/android/aconfig/annotations/*.java", ], - sdk_version: "current", + sdk_version: "core_current", host_supported: true, optimize: { proguard_flags_files: ["aconfig_proguard.flags"], }, visibility: [ - "//visibility:private", + "//visibility:public", ], } @@ -80,8 +80,12 @@ filegroup { name: "framework-api-annotations", srcs: [ "android/annotation/Discouraged.java", + "android/annotation/FlaggedApi.java", + "android/annotation/IntDef.java", "android/annotation/SystemApi.java", "android/annotation/TestApi.java", + // aconfig annotations + "com/android/aconfig/annotations/*.java", ], visibility: [ @@ -114,6 +118,7 @@ filegroup { visibility: [ "//frameworks/libs/modules-utils/java/com/android/modules/utils", "//packages/modules/Bluetooth/system/binder", + "//packages/modules/Bluetooth/android/app/aidl", ], } diff --git a/java/android/annotation/FlaggedApi.java b/java/android/annotation/FlaggedApi.java index 9e43650..90c8e93 100644 --- a/java/android/annotation/FlaggedApi.java +++ b/java/android/annotation/FlaggedApi.java @@ -15,7 +15,9 @@ */ package android.annotation; +import static java.lang.annotation.ElementType.ANNOTATION_TYPE; import static java.lang.annotation.ElementType.CONSTRUCTOR; +import static java.lang.annotation.ElementType.FIELD; import static java.lang.annotation.ElementType.METHOD; import static java.lang.annotation.ElementType.TYPE; @@ -24,18 +26,37 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** - * Indicates an API is part of a feature that is guarded by an aconfig flag. - * </p> - * This annotation should only appear on APIs that are marked <pre>@hide</pre>. + * Indicates an API is part of a feature that is guarded by an aconfig flag, and only available if + * the flag is enabled. + * <p> + * Unless the API has been finalized and has become part of the SDK, callers of the annotated API + * must check that the flag is enabled before making any assumptions about the existence of the API. + * <p> + * Example: + * <code><pre> + * import com.example.foobar.Flags; * + * @FlaggedApi(Flags.FLAG_FOOBAR) + * public void foobar() { ... } + * </pre></code> + * Usage example: + * <code><pre> + * public void codeThatUsesFoobarApi() { + * if (Flags.foobar()) { + * foobar(); + * } else { + * // gracefully handle absence of the foobar API. + * } + * } + * </pre></code> * @hide */ -@Target({TYPE, METHOD, CONSTRUCTOR}) +@Target({TYPE, METHOD, CONSTRUCTOR, FIELD, ANNOTATION_TYPE}) @Retention(RetentionPolicy.SOURCE) public @interface FlaggedApi { /** - * Namespace and name of aconfig flag used to guard the feature this API is part of. Expected - * syntax: namespace/name, e.g. "the_namespace/the_name_of_the_flag". + * The aconfig flag used to guard the feature this API is part of. Use the aconfig + * auto-generated constant to refer to the flag, e.g. @FlaggedApi(Flags.FLAG_FOOBAR). */ - String flag() default ""; + String value(); } diff --git a/java/android/annotation/PermissionManuallyEnforced.java b/java/android/annotation/PermissionManuallyEnforced.java new file mode 100644 index 0000000..5641cfc --- /dev/null +++ b/java/android/annotation/PermissionManuallyEnforced.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2022 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.annotation; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.RetentionPolicy.CLASS; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * Denotes that the annotated method validates permissions manually. + * <p> + * This explicit annotation helps distinguish which of states an + * element may exist in: + * <ul> + * <li>Annotated with {@link EnforcePermission}, indicating that an element + * strictly requires one or more permissions. The verification occurs within + * the annotated element. + * <li>Annotated with {@link RequiresNoPermission}, indicating that an element + * requires no permissions. + * <li>Annotated with {@link PermissionManuallyEnforced}, indicating that the + * element requires some kind of permission which cannot be described using the + * other annotations. + * </ul> + * + * @see EnforcePermission + * @see RequiresNoPermission + * @hide + */ +@Retention(CLASS) +@Target({METHOD}) +public @interface PermissionManuallyEnforced { +} diff --git a/java/android/annotation/UserHandleAware.java b/java/android/annotation/UserHandleAware.java index 60dcbd8..2c3badc 100644 --- a/java/android/annotation/UserHandleAware.java +++ b/java/android/annotation/UserHandleAware.java @@ -38,9 +38,9 @@ import java.lang.annotation.Target; * public abstract PackageInfo getPackageInfo({@literal @}NonNull String packageName, * {@literal @}PackageInfoFlags int flags) throws NameNotFoundException; * }</pre> + * This method uses {@linkplain android.content.Context#getUser()} or + * {@linkplain android.content.Context#getUserId()} to execute across users. * - * @memberDoc This method uses {@linkplain android.content.Context#getUser} - * or {@linkplain android.content.Context#getUserId} to execute across users. * @hide */ @Retention(SOURCE) diff --git a/java/com/android/internal/util/Android.bp b/java/com/android/internal/util/Android.bp index ea21a6b..c2de1fb 100644 --- a/java/com/android/internal/util/Android.bp +++ b/java/com/android/internal/util/Android.bp @@ -38,3 +38,14 @@ java_library { "unsupportedappusage", ], } + +java_library { + name: "modules-utils-fastxmlserializer", + srcs: [ + "FastXmlSerializer.java", + ], + defaults: ["modules-utils-defaults"], + libs: [ + "unsupportedappusage", + ], +} diff --git a/java/com/android/internal/util/FastXmlSerializer.java b/java/com/android/internal/util/FastXmlSerializer.java new file mode 100644 index 0000000..929c9e8 --- /dev/null +++ b/java/com/android/internal/util/FastXmlSerializer.java @@ -0,0 +1,424 @@ +/* + * Copyright (C) 2006 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.internal.util; + +import android.compat.annotation.UnsupportedAppUsage; + +import org.xmlpull.v1.XmlSerializer; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.UnsupportedEncodingException; +import java.io.Writer; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.Charset; +import java.nio.charset.CharsetEncoder; +import java.nio.charset.CoderResult; +import java.nio.charset.CodingErrorAction; +import java.nio.charset.IllegalCharsetNameException; +import java.nio.charset.UnsupportedCharsetException; + +/** + * This is a quick and dirty implementation of XmlSerializer that isn't horribly + * painfully slow like the normal one. It only does what is needed for the + * specific XML files being written with it. + */ +public class FastXmlSerializer implements XmlSerializer { + private static final String ESCAPE_TABLE[] = new String[] { + "�", "", "", "", "", "", "", "", // 0-7 + "", "	", " ", "", "", " ", "", "", // 8-15 + "", "", "", "", "", "", "", "", // 16-23 + "", "", "", "", "", "", "", "", // 24-31 + null, null, """, null, null, null, "&", null, // 32-39 + null, null, null, null, null, null, null, null, // 40-47 + null, null, null, null, null, null, null, null, // 48-55 + null, null, null, null, "<", null, ">", null, // 56-63 + }; + + private static final int DEFAULT_BUFFER_LEN = 32*1024; + + private static String sSpace = " "; + + private final int mBufferLen; + private final char[] mText; + private int mPos; + + private Writer mWriter; + + private OutputStream mOutputStream; + private CharsetEncoder mCharset; + private ByteBuffer mBytes; + + private boolean mIndent = false; + private boolean mInTag; + + private int mNesting = 0; + private boolean mLineStart = true; + + @UnsupportedAppUsage + public FastXmlSerializer() { + this(DEFAULT_BUFFER_LEN); + } + + /** + * Allocate a FastXmlSerializer with the given internal output buffer size. If the + * size is zero or negative, then the default buffer size will be used. + * + * @param bufferSize Size in bytes of the in-memory output buffer that the writer will use. + */ + public FastXmlSerializer(int bufferSize) { + mBufferLen = (bufferSize > 0) ? bufferSize : DEFAULT_BUFFER_LEN; + mText = new char[mBufferLen]; + mBytes = ByteBuffer.allocate(mBufferLen); + } + + private void append(char c) throws IOException { + int pos = mPos; + if (pos >= (mBufferLen-1)) { + flush(); + pos = mPos; + } + mText[pos] = c; + mPos = pos+1; + } + + private void append(String str, int i, final int length) throws IOException { + if (length > mBufferLen) { + final int end = i + length; + while (i < end) { + int next = i + mBufferLen; + append(str, i, next<end ? mBufferLen : (end-i)); + i = next; + } + return; + } + int pos = mPos; + if ((pos+length) > mBufferLen) { + flush(); + pos = mPos; + } + str.getChars(i, i+length, mText, pos); + mPos = pos + length; + } + + private void append(char[] buf, int i, final int length) throws IOException { + if (length > mBufferLen) { + final int end = i + length; + while (i < end) { + int next = i + mBufferLen; + append(buf, i, next<end ? mBufferLen : (end-i)); + i = next; + } + return; + } + int pos = mPos; + if ((pos+length) > mBufferLen) { + flush(); + pos = mPos; + } + System.arraycopy(buf, i, mText, pos, length); + mPos = pos + length; + } + + private void append(String str) throws IOException { + append(str, 0, str.length()); + } + + private void appendIndent(int indent) throws IOException { + indent *= 4; + if (indent > sSpace.length()) { + indent = sSpace.length(); + } + append(sSpace, 0, indent); + } + + private void escapeAndAppendString(final String string) throws IOException { + final int N = string.length(); + final char NE = (char)ESCAPE_TABLE.length; + final String[] escapes = ESCAPE_TABLE; + int lastPos = 0; + int pos; + for (pos=0; pos<N; pos++) { + char c = string.charAt(pos); + if (c >= NE) continue; + String escape = escapes[c]; + if (escape == null) continue; + if (lastPos < pos) append(string, lastPos, pos-lastPos); + lastPos = pos + 1; + append(escape); + } + if (lastPos < pos) append(string, lastPos, pos-lastPos); + } + + private void escapeAndAppendString(char[] buf, int start, int len) throws IOException { + final char NE = (char)ESCAPE_TABLE.length; + final String[] escapes = ESCAPE_TABLE; + int end = start+len; + int lastPos = start; + int pos; + for (pos=start; pos<end; pos++) { + char c = buf[pos]; + if (c >= NE) continue; + String escape = escapes[c]; + if (escape == null) continue; + if (lastPos < pos) append(buf, lastPos, pos-lastPos); + lastPos = pos + 1; + append(escape); + } + if (lastPos < pos) append(buf, lastPos, pos-lastPos); + } + + public XmlSerializer attribute(String namespace, String name, String value) throws IOException, + IllegalArgumentException, IllegalStateException { + append(' '); + if (namespace != null) { + append(namespace); + append(':'); + } + append(name); + append("=\""); + + escapeAndAppendString(value); + append('"'); + mLineStart = false; + return this; + } + + public void cdsect(String text) throws IOException, IllegalArgumentException, + IllegalStateException { + throw new UnsupportedOperationException(); + } + + public void comment(String text) throws IOException, IllegalArgumentException, + IllegalStateException { + throw new UnsupportedOperationException(); + } + + public void docdecl(String text) throws IOException, IllegalArgumentException, + IllegalStateException { + throw new UnsupportedOperationException(); + } + + public void endDocument() throws IOException, IllegalArgumentException, IllegalStateException { + flush(); + } + + public XmlSerializer endTag(String namespace, String name) throws IOException, + IllegalArgumentException, IllegalStateException { + mNesting--; + if (mInTag) { + append(" />\n"); + } else { + if (mIndent && mLineStart) { + appendIndent(mNesting); + } + append("</"); + if (namespace != null) { + append(namespace); + append(':'); + } + append(name); + append(">\n"); + } + mLineStart = true; + mInTag = false; + return this; + } + + public void entityRef(String text) throws IOException, IllegalArgumentException, + IllegalStateException { + throw new UnsupportedOperationException(); + } + + private void flushBytes() throws IOException { + int position; + if ((position = mBytes.position()) > 0) { + mBytes.flip(); + mOutputStream.write(mBytes.array(), 0, position); + mBytes.clear(); + } + } + + public void flush() throws IOException { + //Log.i("PackageManager", "flush mPos=" + mPos); + if (mPos > 0) { + if (mOutputStream != null) { + CharBuffer charBuffer = CharBuffer.wrap(mText, 0, mPos); + CoderResult result = mCharset.encode(charBuffer, mBytes, true); + while (true) { + if (result.isError()) { + throw new IOException(result.toString()); + } else if (result.isOverflow()) { + flushBytes(); + result = mCharset.encode(charBuffer, mBytes, true); + continue; + } + break; + } + flushBytes(); + mOutputStream.flush(); + } else { + mWriter.write(mText, 0, mPos); + mWriter.flush(); + } + mPos = 0; + } + } + + public int getDepth() { + throw new UnsupportedOperationException(); + } + + public boolean getFeature(String name) { + throw new UnsupportedOperationException(); + } + + public String getName() { + throw new UnsupportedOperationException(); + } + + public String getNamespace() { + throw new UnsupportedOperationException(); + } + + public String getPrefix(String namespace, boolean generatePrefix) + throws IllegalArgumentException { + throw new UnsupportedOperationException(); + } + + public Object getProperty(String name) { + throw new UnsupportedOperationException(); + } + + public void ignorableWhitespace(String text) throws IOException, IllegalArgumentException, + IllegalStateException { + throw new UnsupportedOperationException(); + } + + public void processingInstruction(String text) throws IOException, IllegalArgumentException, + IllegalStateException { + throw new UnsupportedOperationException(); + } + + public void setFeature(String name, boolean state) throws IllegalArgumentException, + IllegalStateException { + if (name.equals("http://xmlpull.org/v1/doc/features.html#indent-output")) { + mIndent = true; + return; + } + throw new UnsupportedOperationException(); + } + + public void setOutput(OutputStream os, String encoding) throws IOException, + IllegalArgumentException, IllegalStateException { + if (os == null) + throw new IllegalArgumentException(); + if (true) { + try { + mCharset = Charset.forName(encoding).newEncoder() + .onMalformedInput(CodingErrorAction.REPLACE) + .onUnmappableCharacter(CodingErrorAction.REPLACE); + } catch (IllegalCharsetNameException e) { + throw (UnsupportedEncodingException) (new UnsupportedEncodingException( + encoding).initCause(e)); + } catch (UnsupportedCharsetException e) { + throw (UnsupportedEncodingException) (new UnsupportedEncodingException( + encoding).initCause(e)); + } + mOutputStream = os; + } else { + setOutput( + encoding == null + ? new OutputStreamWriter(os) + : new OutputStreamWriter(os, encoding)); + } + } + + public void setOutput(Writer writer) throws IOException, IllegalArgumentException, + IllegalStateException { + mWriter = writer; + } + + public void setPrefix(String prefix, String namespace) throws IOException, + IllegalArgumentException, IllegalStateException { + throw new UnsupportedOperationException(); + } + + public void setProperty(String name, Object value) throws IllegalArgumentException, + IllegalStateException { + throw new UnsupportedOperationException(); + } + + public void startDocument(String encoding, Boolean standalone) throws IOException, + IllegalArgumentException, IllegalStateException { + append("<?xml version='1.0' encoding='utf-8'"); + if (standalone != null) { + append(" standalone='" + (standalone ? "yes" : "no") + "'"); + } + append(" ?>\n"); + mLineStart = true; + } + + public XmlSerializer startTag(String namespace, String name) throws IOException, + IllegalArgumentException, IllegalStateException { + if (mInTag) { + append(">\n"); + } + if (mIndent) { + appendIndent(mNesting); + } + mNesting++; + append('<'); + if (namespace != null) { + append(namespace); + append(':'); + } + append(name); + mInTag = true; + mLineStart = false; + return this; + } + + public XmlSerializer text(char[] buf, int start, int len) throws IOException, + IllegalArgumentException, IllegalStateException { + if (mInTag) { + append(">"); + mInTag = false; + } + escapeAndAppendString(buf, start, len); + if (mIndent) { + mLineStart = buf[start+len-1] == '\n'; + } + return this; + } + + public XmlSerializer text(String text) throws IOException, IllegalArgumentException, + IllegalStateException { + if (mInTag) { + append(">"); + mInTag = false; + } + escapeAndAppendString(text); + if (mIndent) { + mLineStart = text.length() > 0 && (text.charAt(text.length()-1) == '\n'); + } + return this; + } + +} diff --git a/java/com/android/modules/expresslog/Android.bp b/java/com/android/modules/expresslog/Android.bp index cacc7f8..59504bd 100644 --- a/java/com/android/modules/expresslog/Android.bp +++ b/java/com/android/modules/expresslog/Android.bp @@ -28,12 +28,16 @@ java_library { libs: [ "framework-statsd", ], + static_libs: [ + "expresslog-catalog", + ], } genrule { name: "statslog-expresslog-java-gen", tools: ["stats-log-api-gen"], cmd: "$(location stats-log-api-gen) --java $(out) --module expresslog" + - " --javaPackage com.android.modules.expresslog --javaClass StatsExpressLog", + " --javaPackage com.android.modules.expresslog" + + " --javaClass StatsExpressLog", out: ["com/android/modules/expresslog/StatsExpressLog.java"], } diff --git a/java/com/android/modules/expresslog/Counter.java b/java/com/android/modules/expresslog/Counter.java index b788c3f..bcacb8b 100644 --- a/java/com/android/modules/expresslog/Counter.java +++ b/java/com/android/modules/expresslog/Counter.java @@ -18,8 +18,6 @@ package com.android.modules.expresslog; import android.annotation.NonNull; -import com.android.modules.expresslog.StatsExpressLog; - /** Counter encapsulates StatsD write API calls */ public final class Counter { @@ -49,7 +47,8 @@ public final class Counter { * @param amount to increment counter */ public static void logIncrement(@NonNull String metricId, long amount) { - final long metricIdHash = Utils.hashString(metricId); + final long metricIdHash = + MetricIds.getMetricIdHash(metricId, MetricIds.METRIC_TYPE_COUNTER); StatsExpressLog.write(StatsExpressLog.EXPRESS_EVENT_REPORTED, metricIdHash, amount); } @@ -60,7 +59,8 @@ public final class Counter { * @param amount to increment counter */ public static void logIncrementWithUid(@NonNull String metricId, int uid, long amount) { - final long metricIdHash = Utils.hashString(metricId); + final long metricIdHash = + MetricIds.getMetricIdHash(metricId, MetricIds.METRIC_TYPE_COUNTER_WITH_UID); StatsExpressLog.write( StatsExpressLog.EXPRESS_UID_EVENT_REPORTED, metricIdHash, amount, uid); } diff --git a/java/com/android/modules/expresslog/Histogram.java b/java/com/android/modules/expresslog/Histogram.java index be300bf..4f61c85 100644 --- a/java/com/android/modules/expresslog/Histogram.java +++ b/java/com/android/modules/expresslog/Histogram.java @@ -20,14 +20,12 @@ import android.annotation.FloatRange; import android.annotation.IntRange; import android.annotation.NonNull; -import com.android.modules.expresslog.StatsExpressLog; - import java.util.Arrays; /** Histogram encapsulates StatsD write API calls */ public final class Histogram { - private final long mMetricIdHash; + private final String mMetricId; private final BinOptions mBinOptions; /** @@ -37,7 +35,7 @@ public final class Histogram { * @param binOptions to calculate bin index for samples */ public Histogram(@NonNull String metricId, @NonNull BinOptions binOptions) { - mMetricIdHash = Utils.hashString(metricId); + mMetricId = metricId; mBinOptions = binOptions; } @@ -47,9 +45,10 @@ public final class Histogram { * @param sample value */ public void logSample(float sample) { + final long hash = MetricIds.getMetricIdHash(mMetricId, MetricIds.METRIC_TYPE_HISTOGRAM); final int binIndex = mBinOptions.getBinForSample(sample); - StatsExpressLog.write(StatsExpressLog.EXPRESS_HISTOGRAM_SAMPLE_REPORTED, mMetricIdHash, - /*count*/ 1, binIndex); + StatsExpressLog.write( + StatsExpressLog.EXPRESS_HISTOGRAM_SAMPLE_REPORTED, hash, /*count*/ 1, binIndex); } /** @@ -59,9 +58,15 @@ public final class Histogram { * @param sample value */ public void logSampleWithUid(int uid, float sample) { + final long hash = + MetricIds.getMetricIdHash(mMetricId, MetricIds.METRIC_TYPE_HISTOGRAM_WITH_UID); final int binIndex = mBinOptions.getBinForSample(sample); - StatsExpressLog.write(StatsExpressLog.EXPRESS_UID_HISTOGRAM_SAMPLE_REPORTED, - mMetricIdHash, /*count*/ 1, binIndex, uid); + StatsExpressLog.write( + StatsExpressLog.EXPRESS_UID_HISTOGRAM_SAMPLE_REPORTED, + hash, /*count*/ + 1, + binIndex, + uid); } /** Used by Histogram to map data sample to corresponding bin */ diff --git a/java/com/android/modules/expresslog/Utils.java b/java/com/android/modules/expresslog/Utils.java deleted file mode 100644 index fde90fc..0000000 --- a/java/com/android/modules/expresslog/Utils.java +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright (C) 2023 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.modules.expresslog; - -final class Utils { - static native long hashString(String stringToHash); -} diff --git a/java/com/android/modules/utils/FastDataInput.java b/java/com/android/modules/utils/FastDataInput.java index daa86d5..1437f80 100644 --- a/java/com/android/modules/utils/FastDataInput.java +++ b/java/com/android/modules/utils/FastDataInput.java @@ -207,6 +207,10 @@ public class FastDataInput implements DataInput, Closeable { return s; } else { + if (ref >= mStringRefs.length) { + throw new IOException("Invalid interned string reference " + ref + " for " + + mStringRefs.length + " interned strings"); + } return mStringRefs[ref]; } } diff --git a/java/com/android/modules/utils/build/UnboundedSdkLevel.java b/java/com/android/modules/utils/build/UnboundedSdkLevel.java index 48185d5..cc84172 100644 --- a/java/com/android/modules/utils/build/UnboundedSdkLevel.java +++ b/java/com/android/modules/utils/build/UnboundedSdkLevel.java @@ -17,6 +17,7 @@ package com.android.modules.utils.build; import android.os.Build; +import android.util.ArraySet; import android.util.SparseArray; import androidx.annotation.NonNull; @@ -50,10 +51,22 @@ public final class UnboundedSdkLevel { private static final SparseArray<Set<String>> PREVIOUS_CODENAMES = new SparseArray<>(4); static { - PREVIOUS_CODENAMES.put(29, Set.of("Q")); - PREVIOUS_CODENAMES.put(30, Set.of("Q", "R")); - PREVIOUS_CODENAMES.put(31, Set.of("Q", "R", "S")); - PREVIOUS_CODENAMES.put(32, Set.of("Q", "R", "S", "Sv2")); + PREVIOUS_CODENAMES.put(29, setOf("Q")); + PREVIOUS_CODENAMES.put(30, setOf("Q", "R")); + PREVIOUS_CODENAMES.put(31, setOf("Q", "R", "S")); + PREVIOUS_CODENAMES.put(32, setOf("Q", "R", "S", "Sv2")); + } + + private static Set<String> setOf(String ... contents) { + if (SdkLevel.isAtLeastR()) { + return Set.of(contents); + } + // legacy code for Q + Set<String> set = new ArraySet(contents.length); + for (String codename : contents) { + set.add(codename); + } + return set; } private static final UnboundedSdkLevel sInstance = diff --git a/java/com/android/modules/utils/testing/AbstractExtendedMockitoRule.java b/java/com/android/modules/utils/testing/AbstractExtendedMockitoRule.java index 837c43b..2242ca0 100644 --- a/java/com/android/modules/utils/testing/AbstractExtendedMockitoRule.java +++ b/java/com/android/modules/utils/testing/AbstractExtendedMockitoRule.java @@ -22,12 +22,13 @@ import android.util.Log; import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.util.Preconditions; import com.android.modules.utils.testing.AbstractExtendedMockitoRule.AbstractBuilder; +import com.android.modules.utils.testing.ExtendedMockitoRule.MockStatic; +import com.android.modules.utils.testing.ExtendedMockitoRule.MockStaticClasses; +import com.android.modules.utils.testing.ExtendedMockitoRule.SpyStatic; +import com.android.modules.utils.testing.ExtendedMockitoRule.SpyStaticClasses; -import org.junit.AssumptionViolatedException; import org.junit.rules.TestRule; -import org.junit.rules.TestWatcher; import org.junit.runner.Description; import org.junit.runners.model.Statement; import org.mockito.Mockito; @@ -35,11 +36,21 @@ import org.mockito.MockitoFramework; import org.mockito.MockitoSession; import org.mockito.quality.Strictness; +import java.lang.annotation.Annotation; +import java.lang.annotation.ElementType; +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Objects; +import java.util.Set; +import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Collectors; @@ -52,14 +63,20 @@ public abstract class AbstractExtendedMockitoRule<R extends AbstractExtendedMock private static final String TAG = AbstractExtendedMockitoRule.class.getSimpleName(); + private static final AnnotationFetcher<SpyStatic, SpyStaticClasses> + sSpyStaticAnnotationFetcher = new AnnotationFetcher<>(SpyStatic.class, + SpyStaticClasses.class, r -> r.value()); + private static final AnnotationFetcher<MockStatic, MockStaticClasses> + sMockStaticAnnotationFetcher = new AnnotationFetcher<>(MockStatic.class, + MockStaticClasses.class, r -> r.value()); + private final Object mTestClassInstance; private final Strictness mStrictness; private @Nullable final MockitoFramework mMockitoFramework; private @Nullable final Runnable mAfterSessionFinishedCallback; - private final List<Class<?>> mMockedStaticClasses; - private final List<Class<?>> mSpiedStaticClasses; + private final Set<Class<?>> mMockedStaticClasses; + private final Set<Class<?>> mSpiedStaticClasses; private final List<StaticMockFixture> mStaticMockFixtures; - private final @Nullable SessionBuilderVisitor mSessionBuilderConfigurator; private final boolean mClearInlineMocks; private MockitoSession mMockitoSession; @@ -70,7 +87,6 @@ public abstract class AbstractExtendedMockitoRule<R extends AbstractExtendedMock mMockitoFramework = builder.mMockitoFramework; mMockitoSession = builder.mMockitoSession; mAfterSessionFinishedCallback = builder.mAfterSessionFinishedCallback; - mSessionBuilderConfigurator = builder.mSessionBuilderConfigurator; mMockedStaticClasses = builder.mMockedStaticClasses; mSpiedStaticClasses = builder.mSpiedStaticClasses; mStaticMockFixtures = builder.mStaticMockFixtures == null ? Collections.emptyList() @@ -80,40 +96,92 @@ public abstract class AbstractExtendedMockitoRule<R extends AbstractExtendedMock + ", mockedStaticClasses=" + mMockedStaticClasses + ", spiedStaticClasses=" + mSpiedStaticClasses + ", staticMockFixtures=" + mStaticMockFixtures - + ", sessionBuilderConfigurator=" + mSessionBuilderConfigurator + ", afterSessionFinishedCallback=" + mAfterSessionFinishedCallback + ", mockitoFramework=" + mMockitoFramework + ", mockitoSession=" + mMockitoSession + ", clearInlineMocks=" + mClearInlineMocks); } - @Override - public Statement apply(Statement base, Description description) { - createMockitoSession(description); + /** + * Gets the mocked static classes present in the given test. + * + * <p>By default, it returns the classes defined by {@link AbstractBuilder#mockStatic(Class)} + * plus the classes present in the {@link MockStatic} and {@link MockStaticClasses} + * annotations (presents in the test method, its class, or its superclasses). + */ + protected Set<Class<?>> getMockedStaticClasses(Description description) { + Set<Class<?>> staticClasses = new HashSet<>(mMockedStaticClasses); + sMockStaticAnnotationFetcher.getAnnotations(description) + .forEach(a -> staticClasses.add(a.value())); + return Collections.unmodifiableSet(staticClasses); + } - return new TestWatcher() { - @Override - protected void succeeded(Description description) { - tearDown(description, /* e=*/ null); - } + /** + * Gets the spied static classes present in the given test. + * + * <p>By default, it returns the classes defined by {@link AbstractBuilder#spyStatic(Class)} + * plus the classes present in the {@link SpyStatic} and {@link SpyStaticClasses} + * annotations (presents in the test method, its class, or its superclasses). + */ + protected Set<Class<?>> getSpiedStaticClasses(Description description) { + Set<Class<?>> staticClasses = new HashSet<>(mSpiedStaticClasses); + sSpyStaticAnnotationFetcher.getAnnotations(description) + .forEach(a -> staticClasses.add(a.value())); + return Collections.unmodifiableSet(staticClasses); + } - @Override - protected void skipped(AssumptionViolatedException e, Description description) { - tearDown(description, e); - } + /** + * Gets whether the rule should clear the inline mocks after the given test. + * + * <p>By default, it returns {@code} (unless the rule was built with + * {@link AbstractBuilder#dontClearInlineMocks()}, but subclasses can override to change the + * behavior (for example, to decide based on custom annotations). + */ + protected boolean getClearInlineMethodsAtTheEnd(Description description) { + return mClearInlineMocks; + } + @Override + public Statement apply(Statement base, Description description) { + return new Statement() { @Override - protected void failed(Throwable e, Description description) { - tearDown(description, e); + public void evaluate() throws Throwable { + createMockitoSession(base, description); + Throwable error = null; + try { + // TODO(b/296937563): need to add unit tests that make sure the session is + // always closed + base.evaluate(); + } catch (Throwable t) { + error = t; + } + try { + tearDown(description, error); + } catch (Throwable t) { + if (error != null) { + Log.e(TAG, "Teardown failed for " + description.getDisplayName() + + ", but not throwing it because test also threw (" + error + ")", t); + } else { + error = t; + } + } + if (error != null) { + // TODO(b/296937563): ideally should also add unit tests to make sure the + // test error is thrown (in case tearDown() above fails) + throw error; + } } - }.apply(base, description); + }; } - private void createMockitoSession(Description description) { + private void createMockitoSession(Statement base, Description description) { + // TODO(b/296937563): might be prudent to save the session statically so it's explicitly + // closed in case it fails to be created again if for some reason it was not closed by us + // (although that should not happen) Log.v(TAG, "Creating session builder with strictness " + mStrictness); StaticMockitoSessionBuilder mSessionBuilder = mockitoSession().strictness(mStrictness); - setUpMockedClasses(mSessionBuilder); + setUpMockedClasses(description, mSessionBuilder); if (mTestClassInstance != null) { Log.v(TAG, "Initializing mocks on " + description + " using " + mSessionBuilder); @@ -132,25 +200,22 @@ public abstract class AbstractExtendedMockitoRule<R extends AbstractExtendedMock setUpMockBehaviors(); } - private void setUpMockedClasses(StaticMockitoSessionBuilder sessionBuilder) { + private void setUpMockedClasses(Description description, + StaticMockitoSessionBuilder sessionBuilder) { if (!mStaticMockFixtures.isEmpty()) { for (StaticMockFixture fixture : mStaticMockFixtures) { Log.v(TAG, "Calling setUpMockedClasses(" + sessionBuilder + ") on " + fixture); fixture.setUpMockedClasses(sessionBuilder); } } - for (Class<?> clazz: mMockedStaticClasses) { + for (Class<?> clazz: getMockedStaticClasses(description)) { Log.v(TAG, "Calling mockStatic() on " + clazz); sessionBuilder.mockStatic(clazz); } - for (Class<?> clazz: mSpiedStaticClasses) { + for (Class<?> clazz: getSpiedStaticClasses(description)) { Log.v(TAG, "Calling spyStatic() on " + clazz); sessionBuilder.spyStatic(clazz); } - if (mSessionBuilderConfigurator != null) { - Log.v(TAG, "Visiting " + mSessionBuilderConfigurator + " with " + sessionBuilder); - mSessionBuilderConfigurator.visit(sessionBuilder); - } } private void setUpMockBehaviors() { @@ -183,12 +248,13 @@ public abstract class AbstractExtendedMockitoRule<R extends AbstractExtendedMock } } } finally { - clearInlineMocks(); + clearInlineMocks(description); } } - private void clearInlineMocks() { - if (!mClearInlineMocks) { + private void clearInlineMocks(Description description) { + boolean clearIt = getClearInlineMethodsAtTheEnd(description); + if (!clearIt) { Log.d(TAG, "NOT calling clearInlineMocks() as set on builder"); return; } @@ -208,13 +274,12 @@ public abstract class AbstractExtendedMockitoRule<R extends AbstractExtendedMock public static abstract class AbstractBuilder<R extends AbstractExtendedMockitoRule<R, B>, B extends AbstractBuilder<R, B>> { final Object mTestClassInstance; - final List<Class<?>> mMockedStaticClasses = new ArrayList<>(); - final List<Class<?>> mSpiedStaticClasses = new ArrayList<>(); + final Set<Class<?>> mMockedStaticClasses = new HashSet<>(); + final Set<Class<?>> mSpiedStaticClasses = new HashSet<>(); @Nullable List<StaticMockFixture> mStaticMockFixtures; Strictness mStrictness = Strictness.LENIENT; @Nullable MockitoFramework mMockitoFramework; @Nullable MockitoSession mMockitoSession; - @Nullable SessionBuilderVisitor mSessionBuilderConfigurator; @Nullable Runnable mAfterSessionFinishedCallback; boolean mClearInlineMocks = true; @@ -248,11 +313,9 @@ public abstract class AbstractExtendedMockitoRule<R extends AbstractExtendedMock * com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder#mockStatic(Class)}. * * @throws IllegalStateException if the same class was already passed to - * {@link #mockStatic(Class)} or {@link #spyStatic(Class)} or if - * {@link #configureSessionBuilder(SessionBuilderVisitor)} was called before. + * {@link #mockStatic(Class)} or {@link #spyStatic(Class)}. */ public final B mockStatic(Class<?> clazz) { - checkConfigureSessionBuilderNotCalled(); mMockedStaticClasses.add(checkClassNotMockedOrSpied(clazz)); return thisBuilder(); } @@ -262,11 +325,9 @@ public abstract class AbstractExtendedMockitoRule<R extends AbstractExtendedMock * com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder#spyStatic(Class)}. * * @throws IllegalStateException if the same class was already passed to - * {@link #mockStatic(Class)} or {@link #spyStatic(Class)} or if - * {@link #configureSessionBuilder(SessionBuilderVisitor)} was called before. + * {@link #mockStatic(Class)} or {@link #spyStatic(Class)}. */ public final B spyStatic(Class<?> clazz) { - checkConfigureSessionBuilderNotCalled(); mSpiedStaticClasses.add(checkClassNotMockedOrSpied(clazz)); return thisBuilder(); } @@ -288,27 +349,6 @@ public abstract class AbstractExtendedMockitoRule<R extends AbstractExtendedMock return thisBuilder(); } - // TODO(b/281577492): remove once CachedAppOptimizerTest doesn't use anymore - /** - * Alternative for {@link #spyStatic(Class)} / {@link #mockStatic(Class)}; typically used - * when the same setup is shared by multiple tests. - * - * @deprecated use {@link #addStaticMockFixtures(Supplier...)} instead - * - * @throws IllegalStateException if {@link #mockStatic(Class)} or {@link #spyStatic(Class)} - * was called before. - */ - @Deprecated - public final B configureSessionBuilder( - SessionBuilderVisitor sessionBuilderConfigurator) { - Preconditions.checkState(mMockedStaticClasses.isEmpty(), - "mockStatic() already called"); - Preconditions.checkState(mSpiedStaticClasses.isEmpty(), - "spyStatic() already called"); - mSessionBuilderConfigurator = Objects.requireNonNull(sessionBuilderConfigurator); - return thisBuilder(); - } - /** * Runs the given {@code runnable} after the session finished. * @@ -354,29 +394,78 @@ public abstract class AbstractExtendedMockitoRule<R extends AbstractExtendedMock return (B) this; } - private void checkConfigureSessionBuilderNotCalled() { - Preconditions.checkState(mSessionBuilderConfigurator == null, - "configureSessionBuilder() already called"); - } - private Class<?> checkClassNotMockedOrSpied(Class<?> clazz) { Objects.requireNonNull(clazz); - Preconditions.checkState(!mMockedStaticClasses.contains(clazz), - "class %s already mocked", clazz); - Preconditions.checkState(!mSpiedStaticClasses.contains(clazz), - "class %s already spied", clazz); + checkState(!mMockedStaticClasses.contains(clazz), "class %s already mocked", clazz); + checkState(!mSpiedStaticClasses.contains(clazz), "class %s already spied", clazz); return clazz; } } - /** - * Visitor for {@link StaticMockitoSessionBuilder}. - */ - public interface SessionBuilderVisitor { + // Copied from com.android.internal.util.Preconditions, as that method is not available on RVC + private static void checkState(boolean expression, String messageTemplate, + Object... messageArgs) { + if (!expression) { + throw new IllegalStateException(String.format(messageTemplate, messageArgs)); + } + } - /** - * Visits it. - */ - void visit(StaticMockitoSessionBuilder builder); + // TODO: make it public so it can be used by other modules + private static final class AnnotationFetcher<A extends Annotation, R extends Annotation> { + + private final Class<A> mAnnotationType; + private final Class<R> mRepeatableType; + private final Function<R, A[]> mConverter; + + AnnotationFetcher(Class<A> annotationType, Class<R> repeatableType, + Function<R, A[]> converter) { + mAnnotationType = annotationType; + mRepeatableType = repeatableType; + mConverter = converter; + } + + private void add(Set<A> allAnnotations, R repeatableAnnotation) { + A[] repeatedAnnotations = mConverter.apply(repeatableAnnotation); + for (A repeatedAnnotation : repeatedAnnotations) { + allAnnotations.add(repeatedAnnotation); + } + } + + Set<A> getAnnotations(Description description) { + Set<A> allAnnotations = new HashSet<>(); + + // Gets the annotations from the method first + Collection<Annotation> annotations = description.getAnnotations(); + if (annotations != null) { + for (Annotation annotation : annotations) { + if (mAnnotationType.isInstance(annotation)) { + allAnnotations.add(mAnnotationType.cast(annotation)); + } + if (mRepeatableType.isInstance(annotation)) { + add(allAnnotations, mRepeatableType.cast(annotation)); + } + } + } + + // Then superclasses + Class<?> clazz = description.getTestClass(); + do { + A[] repeatedAnnotations = clazz.getAnnotationsByType(mAnnotationType); + if (repeatedAnnotations != null) { + for (A repeatedAnnotation : repeatedAnnotations) { + allAnnotations.add(repeatedAnnotation); + } + } + R[] repeatableAnnotations = clazz.getAnnotationsByType(mRepeatableType); + if (repeatableAnnotations != null) { + for (R repeatableAnnotation : repeatableAnnotations) { + add(allAnnotations, mRepeatableType.cast(repeatableAnnotation)); + } + } + clazz = clazz.getSuperclass(); + } while (clazz != null); + + return allAnnotations; + } } } diff --git a/java/com/android/modules/utils/testing/ExtendedMockitoRule.java b/java/com/android/modules/utils/testing/ExtendedMockitoRule.java index 8eeaddd..fa7d7fa 100644 --- a/java/com/android/modules/utils/testing/ExtendedMockitoRule.java +++ b/java/com/android/modules/utils/testing/ExtendedMockitoRule.java @@ -18,6 +18,12 @@ package com.android.modules.utils.testing; import com.android.modules.utils.testing.AbstractExtendedMockitoRule.AbstractBuilder; import com.android.modules.utils.testing.ExtendedMockitoRule.Builder; +import java.lang.annotation.ElementType; +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + /** * Rule to make it easier to use Extended Mockito: * @@ -71,4 +77,30 @@ public final class ExtendedMockitoRule extends return new ExtendedMockitoRule(this); } } + + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.METHOD, ElementType.TYPE}) + @Repeatable(SpyStaticClasses.class) + public @interface SpyStatic { + Class<?> value(); + } + + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.METHOD, ElementType.TYPE}) + public @interface SpyStaticClasses { + SpyStatic[] value(); + } + + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.METHOD, ElementType.TYPE}) + @Repeatable(MockStaticClasses.class) + public @interface MockStatic { + Class<?> value(); + } + + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.METHOD, ElementType.TYPE}) + public @interface MockStaticClasses { + MockStatic[] value(); + } } diff --git a/javatests/com/android/modules/expresslog/Android.bp b/javatests/com/android/modules/expresslog/Android.bp index dd52750..9396e17 100644 --- a/javatests/com/android/modules/expresslog/Android.bp +++ b/javatests/com/android/modules/expresslog/Android.bp @@ -37,40 +37,7 @@ android_test { "android.test.runner", ], - jni_libs: [ - "libexpresslog_test_jni", - ], - test_suites: [ "general-tests", ], } - -cc_library_shared { - name: "libexpresslog_test_jni", - - sdk_version: "current", - min_sdk_version: "30", - - cflags: [ - "-Wall", - "-Werror", - "-Wextra", - - "-DNAMESPACE_FOR_HASH_FUNCTIONS=farmhash", - ], - srcs: [ - "jni/onload.cpp", - ], - header_libs: [ - "liblog_headers", - "libnativehelper_header_only", - ], - shared_libs: [ - "liblog", - ], - static_libs: [ - "libexpresslog_jni", - "libtextclassifier_hash_static", - ], -} diff --git a/javatests/com/android/modules/expresslog/ScaledRangeOptionsTest.java b/javatests/com/android/modules/expresslog/ScaledRangeOptionsTest.java index 8defce7..1c42788 100644 --- a/javatests/com/android/modules/expresslog/ScaledRangeOptionsTest.java +++ b/javatests/com/android/modules/expresslog/ScaledRangeOptionsTest.java @@ -27,10 +27,6 @@ import org.junit.runners.JUnit4; @RunWith(JUnit4.class) @SmallTest public class ScaledRangeOptionsTest { - static { - System.loadLibrary("expresslog_test_jni"); - } - private static final String TAG = ScaledRangeOptionsTest.class.getSimpleName(); @Test diff --git a/javatests/com/android/modules/expresslog/UniformOptionsTest.java b/javatests/com/android/modules/expresslog/UniformOptionsTest.java index 3cc03ec..cad4c3f 100644 --- a/javatests/com/android/modules/expresslog/UniformOptionsTest.java +++ b/javatests/com/android/modules/expresslog/UniformOptionsTest.java @@ -26,10 +26,6 @@ import org.junit.runners.JUnit4; @RunWith(JUnit4.class) @SmallTest public class UniformOptionsTest { - static { - System.loadLibrary("expresslog_test_jni"); - } - private static final String TAG = UniformOptionsTest.class.getSimpleName(); @Test diff --git a/javatests/com/android/modules/expresslog/jni/.clang-format b/javatests/com/android/modules/expresslog/jni/.clang-format deleted file mode 100644 index cead3a0..0000000 --- a/javatests/com/android/modules/expresslog/jni/.clang-format +++ /dev/null @@ -1,17 +0,0 @@ -BasedOnStyle: Google -AllowShortIfStatementsOnASingleLine: true -AllowShortFunctionsOnASingleLine: false -AllowShortLoopsOnASingleLine: true -BinPackArguments: true -BinPackParameters: true -ColumnLimit: 100 -CommentPragmas: NOLINT:.* -ContinuationIndentWidth: 8 -DerivePointerAlignment: false -IndentWidth: 4 -PointerAlignment: Left -TabWidth: 4 -AccessModifierOffset: -4 -IncludeCategories: - - Regex: '^"Log\.h"' - Priority: -1 diff --git a/javatests/com/android/modules/expresslog/jni/onload.cpp b/javatests/com/android/modules/expresslog/jni/onload.cpp deleted file mode 100644 index a112467..0000000 --- a/javatests/com/android/modules/expresslog/jni/onload.cpp +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (C) 2023 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. - */ - -#define LOG_TAG "TeX" - -#include <jni.h> -#include <log/log.h> - -namespace android { -extern int register_com_android_modules_expresslog_Utils(JNIEnv* env); -} // namespace android - -using namespace android; - -extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) { - JNIEnv* env = NULL; - jint result = -1; - - if (vm->GetEnv((void**)&env, JNI_VERSION_1_4) != JNI_OK) { - ALOGE("GetEnv failed!"); - return result; - } - ALOG_ASSERT(env, "Could not retrieve the env!"); - - register_com_android_modules_expresslog_Utils(env); - return JNI_VERSION_1_4; -} diff --git a/javatests/com/android/modules/utils/build/Android.bp b/javatests/com/android/modules/utils/build/Android.bp index 239530a..06ceb34 100644 --- a/javatests/com/android/modules/utils/build/Android.bp +++ b/javatests/com/android/modules/utils/build/Android.bp @@ -25,7 +25,7 @@ android_test { "androidx.test.ext.junit", "androidx.test.runner", "modules-utils-build", - "truth-prebuilt", + "truth", ], test_suites: ["general-tests"], } diff --git a/javatests/com/android/modules/utils/testing/Android.bp b/javatests/com/android/modules/utils/testing/Android.bp index 418239a..5da571b 100644 --- a/javatests/com/android/modules/utils/testing/Android.bp +++ b/javatests/com/android/modules/utils/testing/Android.bp @@ -34,7 +34,7 @@ android_test { "androidx.test.runner", "androidx.test.rules", "platform-test-annotations", - "truth-prebuilt", + "truth", ], libs: [ diff --git a/javatests/com/android/modules/utils/testing/ExtendedMockitoRuleTest.java b/javatests/com/android/modules/utils/testing/ExtendedMockitoRuleTest.java index cb54f9f..26d0cc3 100644 --- a/javatests/com/android/modules/utils/testing/ExtendedMockitoRuleTest.java +++ b/javatests/com/android/modules/utils/testing/ExtendedMockitoRuleTest.java @@ -31,6 +31,9 @@ import android.util.Log; import androidx.annotation.Nullable; +import com.android.modules.utils.testing.ExtendedMockitoRule.MockStatic; +import com.android.modules.utils.testing.ExtendedMockitoRule.SpyStatic; + import org.junit.Test; import org.junit.runner.Description; import org.junit.runner.RunWith; @@ -46,20 +49,27 @@ import org.mockito.listeners.MockitoListener; import org.mockito.plugins.MockitoPlugins; import org.mockito.quality.Strictness; +import java.lang.annotation.Annotation; +import java.util.Collection; +import java.util.Objects; +import java.util.Set; import java.util.function.Supplier; @RunWith(MockitoJUnitRunner.class) public final class ExtendedMockitoRuleTest { + public static final String TAG = ExtendedMockitoRuleTest.class.getSimpleName(); + // Not a real test (i.e., it doesn't exist on this class), but it's passed to Description + private static final String TEST_METHOD_BEING_EXECUTED = "testAmI..OrNot"; + private @Mock Statement mStatement; - private @Mock Description mDescription; private @Mock Runnable mRunnable; - private @Mock ExtendedMockitoRule.SessionBuilderVisitor mSessionBuilderVisitor; private @Mock StaticMockFixture mStaticMockFixture1; private @Mock StaticMockFixture mStaticMockFixture2; private @Mock StaticMockFixture mStaticMockFixture3; + private final Description mDescription = newTestMethod(); private final ClassUnderTest mClassUnderTest = new ClassUnderTest(); private final ExtendedMockitoRule.Builder mBuilder = new ExtendedMockitoRule.Builder(mClassUnderTest); @@ -89,12 +99,6 @@ public final class ExtendedMockitoRuleTest { } @Test - public void testBuilder_configureSessionBuilder_null() { - assertThrows(NullPointerException.class, - () -> mBuilder.configureSessionBuilder(null)); - } - - @Test public void testBuilder_mockStatic_null() { assertThrows(NullPointerException.class, () -> mBuilder.mockStatic(null)); } @@ -164,7 +168,8 @@ public final class ExtendedMockitoRuleTest { @Test public void testMocksStatic() throws Throwable { - mBuilder.mockStatic(StaticClass.class).build().apply(new Statement() { + ExtendedMockitoRule rule = mBuilder.mockStatic(StaticClass.class).build(); + rule.apply(new Statement() { @Override public void evaluate() throws Throwable { doReturn("mocko()").when(() -> StaticClass.marco()); @@ -175,6 +180,12 @@ public final class ExtendedMockitoRuleTest { .that(StaticClass.water()).isNull(); // not mocked } }, mDescription).evaluate(); + + Set<Class<?>> mockedClasses = rule.getMockedStaticClasses(mDescription); + assertWithMessage("rule.getMockedStaticClasses()").that(mockedClasses) + .containsExactly(StaticClass.class); + assertThrows(RuntimeException.class, + () -> mockedClasses.add(ExtendedMockitoRuleTest.class)); } @Test @@ -186,29 +197,79 @@ public final class ExtendedMockitoRuleTest { @Test public void testMocksStatic_multipleClasses() throws Throwable { - mBuilder.mockStatic(StaticClass.class).mockStatic(AnotherStaticClass.class).build().apply( - new Statement() { - @Override - public void evaluate() throws Throwable { - doReturn("mocko()").when(() -> StaticClass.marco()); - doReturn("MOCKO()").when(() -> AnotherStaticClass.marco()); + ExtendedMockitoRule rule = mBuilder.mockStatic(StaticClass.class) + .mockStatic(AnotherStaticClass.class).build(); + rule.apply(new Statement() { + @Override + public void evaluate() throws Throwable { + doReturn("mocko()").when(() -> StaticClass.marco()); + doReturn("MOCKO()").when(() -> AnotherStaticClass.marco()); - assertWithMessage("StaticClass.marco()") - .that(StaticClass.marco()).isEqualTo("mocko()"); - assertWithMessage("StaticClass.water()") - .that(StaticClass.water()).isNull(); // not mocked + assertWithMessage("StaticClass.marco()") + .that(StaticClass.marco()).isEqualTo("mocko()"); + assertWithMessage("StaticClass.water()") + .that(StaticClass.water()).isNull(); // not mocked - assertWithMessage("AnotherStaticClass.marco()") - .that(AnotherStaticClass.marco()).isEqualTo("MOCKO()"); - assertWithMessage("AnotherStaticClass.water()") - .that(AnotherStaticClass.water()).isNull(); // not mocked - } - }, mDescription).evaluate(); + assertWithMessage("AnotherStaticClass.marco()") + .that(AnotherStaticClass.marco()).isEqualTo("MOCKO()"); + assertWithMessage("AnotherStaticClass.water()") + .that(AnotherStaticClass.water()).isNull(); // not mocked + } + }, mDescription).evaluate(); + + Set<Class<?>> mockedClasses = rule.getMockedStaticClasses(mDescription); + assertWithMessage("rule.getMockedStaticClasses()").that(mockedClasses) + .containsExactly(StaticClass.class, AnotherStaticClass.class); + assertThrows(RuntimeException.class, + () -> mockedClasses.add(ExtendedMockitoRuleTest.class)); + } + + @Test + public void testMockStatic_ruleAndAnnotation() throws Throwable { + ExtendedMockitoRule rule = mBuilder.mockStatic(StaticClass.class).build(); + + rule.apply(new Statement() { + @Override + public void evaluate() throws Throwable { + doReturn("mocko()").when(() -> StaticClass.marco()); + doReturn("MOCKO()").when(() -> AnotherStaticClass.marco()); + + assertWithMessage("StaticClass.marco()") + .that(StaticClass.marco()).isEqualTo("mocko()"); + assertWithMessage("StaticClass.water()") + .that(StaticClass.water()).isNull(); // not mocked + + assertWithMessage("AnotherStaticClass.marco()") + .that(AnotherStaticClass.marco()).isEqualTo("MOCKO()"); + assertWithMessage("AnotherStaticClass.water()") + .that(AnotherStaticClass.water()).isNull(); // not mocked + } + }, newTestMethod(new MockStaticAnnotation(AnotherStaticClass.class))).evaluate(); + } + + // Ideally, we should test the annotations indirectly (i.e., by asserting their static classes + // are properly mocked, but pragmatically speaking, testing the getSpiedStatic() is enough - and + // much simpler + @Test + public void testMockStatic_fromEverywhere() throws Throwable { + ExtendedMockitoRule rule = mBuilder.mockStatic(StaticClass.class).build(); + + Set<Class<?>> mockedClasses = rule.getMockedStaticClasses(newTestMethod(SubClass.class, + new MockStaticAnnotation(AnotherStaticClass.class))); + + assertWithMessage("rule.getMockedStaticClasses()").that(mockedClasses).containsExactly( + StaticClass.class, AnotherStaticClass.class, StaticClassMockedBySuperClass.class, + AnotherStaticClassMockedBySuperClass.class, StaticClassMockedBySubClass.class, + AnotherStaticClassMockedBySubClass.class); + assertThrows(RuntimeException.class, + () -> mockedClasses.add(ExtendedMockitoRuleTest.class)); } @Test public void testSpyStatic() throws Throwable { - mBuilder.spyStatic(StaticClass.class).build().apply(new Statement() { + ExtendedMockitoRule rule = mBuilder.spyStatic(StaticClass.class).build(); + + rule.apply(new Statement() { @Override public void evaluate() throws Throwable { doReturn("mocko()").when(() -> StaticClass.marco()); @@ -219,6 +280,11 @@ public final class ExtendedMockitoRuleTest { .that(StaticClass.water()).isEqualTo("polo"); } }, mDescription).evaluate(); + + Set<Class<?>> spiedClasses = rule.getSpiedStaticClasses(mDescription); + assertWithMessage("rule.getSpiedStaticClasses()").that(spiedClasses) + .containsExactly(StaticClass.class); + assertThrows(RuntimeException.class, () -> spiedClasses.add(ExtendedMockitoRuleTest.class)); } @Test @@ -230,8 +296,10 @@ public final class ExtendedMockitoRuleTest { @Test public void testSpyStatic_multipleClasses() throws Throwable { - mBuilder.spyStatic(StaticClass.class).spyStatic(AnotherStaticClass.class).build() - .apply(new Statement() { + ExtendedMockitoRule rule = mBuilder.spyStatic(StaticClass.class) + .spyStatic(AnotherStaticClass.class).build(); + + rule.apply(new Statement() { @Override public void evaluate() throws Throwable { doReturn("mocko()").when(() -> StaticClass.marco()); @@ -248,12 +316,58 @@ public final class ExtendedMockitoRuleTest { .that(AnotherStaticClass.water()).isEqualTo("POLO"); } }, mDescription).evaluate(); + + Set<Class<?>> spiedClasses = rule.getSpiedStaticClasses(mDescription); + assertWithMessage("rule.getSpiedStaticClasses()").that(spiedClasses) + .containsExactly(StaticClass.class, AnotherStaticClass.class); + assertThrows(RuntimeException.class, () -> spiedClasses.add(ExtendedMockitoRuleTest.class)); + } + + @Test + public void testSpyStatic_ruleAndAnnotation() throws Throwable { + ExtendedMockitoRule rule = mBuilder.spyStatic(StaticClass.class).build(); + rule.apply(new Statement() { + @Override + public void evaluate() throws Throwable { + doReturn("mocko()").when(() -> StaticClass.marco()); + doReturn("MOCKO()").when(() -> AnotherStaticClass.marco()); + + assertWithMessage("StaticClass.marco()") + .that(StaticClass.marco()).isEqualTo("mocko()"); + assertWithMessage("StaticClass.water()") + .that(StaticClass.water()).isEqualTo("polo"); + + assertWithMessage("AnotherStaticClass.marco()") + .that(AnotherStaticClass.marco()).isEqualTo("MOCKO()"); + assertWithMessage("AnotherStaticClass.water()") + .that(AnotherStaticClass.water()).isEqualTo("POLO"); + } + }, newTestMethod(new SpyStaticAnnotation(AnotherStaticClass.class))).evaluate(); + } + + // Ideally, we should test the annotations indirectly (i.e., by asserting their static classes + // are properly spied, but pragmatically speaking, testing the getSpiedStatic() is enough - and + // much simpler + @Test + public void testSpyStatic_fromEverywhere() throws Throwable { + ExtendedMockitoRule rule = mBuilder.spyStatic(StaticClass.class).build(); + + Set<Class<?>> spiedClasses = rule.getSpiedStaticClasses(newTestMethod(SubClass.class, + new SpyStaticAnnotation(AnotherStaticClass.class))); + + assertWithMessage("rule.getSpiedStaticClasses()").that(spiedClasses).containsExactly( + StaticClass.class, AnotherStaticClass.class, StaticClassSpiedBySuperClass.class, + AnotherStaticClassSpiedBySuperClass.class, StaticClassSpiedBySubClass.class, + AnotherStaticClassSpiedBySubClass.class); + assertThrows(RuntimeException.class, () -> spiedClasses.add(ExtendedMockitoRuleTest.class)); } @Test public void testMockAndSpyStatic() throws Throwable { - mBuilder.mockStatic(StaticClass.class).spyStatic(AnotherStaticClass.class).build() - .apply(new Statement() { + ExtendedMockitoRule rule = mBuilder.mockStatic(StaticClass.class) + .spyStatic(AnotherStaticClass.class).build(); + + rule.apply(new Statement() { @Override public void evaluate() throws Throwable { doReturn("mocko()").when(() -> StaticClass.marco()); @@ -270,6 +384,18 @@ public final class ExtendedMockitoRuleTest { .that(AnotherStaticClass.water()).isEqualTo("POLO"); } }, mDescription).evaluate(); + + Set<Class<?>> spiedStaticClasses = rule.getSpiedStaticClasses(mDescription); + assertWithMessage("rule.getSpiedStaticClasses()").that(spiedStaticClasses) + .containsExactly(AnotherStaticClass.class); + assertThrows(RuntimeException.class, + () -> spiedStaticClasses.add(ExtendedMockitoRuleTest.class)); + + Set<Class<?>> mockedStaticClasses = rule.getMockedStaticClasses(mDescription); + assertWithMessage("rule.getMockedStaticClasses()").that(mockedStaticClasses) + .containsExactly(StaticClass.class); + assertThrows(RuntimeException.class, + () -> mockedStaticClasses.add(ExtendedMockitoRuleTest.class)); } @Test @@ -287,18 +413,6 @@ public final class ExtendedMockitoRuleTest { } @Test - public void testSpyStatic_afterConfigureSessionBuilder() throws Throwable { - assertThrows(IllegalStateException.class, () -> mBuilder - .configureSessionBuilder(mSessionBuilderVisitor).spyStatic(StaticClass.class)); - } - - @Test - public void testMockStatic_afterConfigureSessionBuilder() throws Throwable { - assertThrows(IllegalStateException.class, () -> mBuilder - .configureSessionBuilder(mSessionBuilderVisitor).mockStatic(StaticClass.class)); - } - - @Test public void testAddStaticMockFixtures_once() throws Throwable { InOrder inOrder = inOrder(mStaticMockFixture1, mStaticMockFixture2); @@ -382,26 +496,6 @@ public final class ExtendedMockitoRuleTest { } @Test - public void testConfigureSessionBuilder_afterMockStatic() throws Throwable { - assertThrows(IllegalStateException.class, () -> mBuilder.mockStatic(StaticClass.class) - .configureSessionBuilder(mSessionBuilderVisitor)); - } - - @Test - public void testConfigureSessionBuilder_afterSpyStatic() throws Throwable { - assertThrows(IllegalStateException.class, () -> mBuilder.spyStatic(StaticClass.class) - .configureSessionBuilder(mSessionBuilderVisitor)); - } - - @Test - public void testConfigureSessionBuilder() throws Throwable { - mUnsafeBuilder.configureSessionBuilder(mSessionBuilderVisitor) - .build().apply(mStatement, mDescription).evaluate(); - - verify(mSessionBuilderVisitor).visit(notNull()); - } - - @Test public void testAfterSessionFinished() throws Throwable { mUnsafeBuilder.afterSessionFinished(mRunnable).build().apply(mStatement, mDescription) .evaluate(); @@ -506,6 +600,16 @@ public final class ExtendedMockitoRuleTest { assertWithMessage("mockito framework cleared").that(mockitoFramework.called).isTrue(); } + @Test + public void testGetClearInlineMethodsAtTheEnd() throws Throwable { + assertWithMessage("getClearInlineMethodsAtTheEnd() by default") + .that(mBuilder.build().getClearInlineMethodsAtTheEnd(mDescription)).isTrue(); + assertWithMessage("getClearInlineMethodsAtTheEnd() when built with dontClearInlineMocks()") + .that(mBuilder.dontClearInlineMocks().build() + .getClearInlineMethodsAtTheEnd(mDescription)) + .isFalse(); + } + private void applyRuleOnTestThatDoesntUseExpectation(@Nullable Strictness strictness) throws Throwable { Log.d(TAG, "applyRuleOnTestThatDoesntUseExpectation(): strictness= " + strictness); @@ -520,6 +624,15 @@ public final class ExtendedMockitoRuleTest { }, mDescription).evaluate(); } + private static Description newTestMethod(Annotation... annotations) { + return newTestMethod(ClassUnderTest.class, annotations); + } + + private static Description newTestMethod(Class<?> testClass, Annotation... annotations) { + return Description.createTestDescription(testClass, TEST_METHOD_BEING_EXECUTED, + annotations); + } + private static final class ClassUnderTest { @Mock public DumbObject mMock; @@ -622,4 +735,103 @@ public final class ExtendedMockitoRuleTest { throw e; } } + + private abstract static class ClassAnnotation<A extends Annotation> implements Annotation { + private Class<A> mAnnotationType; + private Class<?> mClass; + + private ClassAnnotation(Class<A> annotationType, Class<?> clazz) { + mAnnotationType = annotationType; + mClass = clazz; + } + + @Override + public final Class<A> annotationType() { + return mAnnotationType; + } + + public final Class<?> value() { + return mClass; + } + + @Override + public int hashCode() { + return Objects.hash(mAnnotationType, mClass); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + ClassAnnotation other = (ClassAnnotation) obj; + return Objects.equals(mAnnotationType, other.mAnnotationType) + && Objects.equals(mClass, other.mClass); + } + + @Override + public String toString() { + return getClass().getSimpleName() + "[" + mClass.getSimpleName() + "]"; + } + } + + private static final class SpyStaticAnnotation extends ClassAnnotation<SpyStatic> + implements SpyStatic { + + private SpyStaticAnnotation(Class<?> clazz) { + super(SpyStatic.class, clazz); + } + } + + private static final class MockStaticAnnotation extends ClassAnnotation<MockStatic> + implements MockStatic { + + private MockStaticAnnotation(Class<?> clazz) { + super(MockStatic.class, clazz); + } + } + + private static final class StaticClassMockedBySuperClass { + } + + private static final class AnotherStaticClassMockedBySuperClass { + } + private static final class StaticClassSpiedBySuperClass { + } + + private static final class AnotherStaticClassSpiedBySuperClass { + } + + @SpyStatic(StaticClassSpiedBySuperClass.class) + @SpyStatic(AnotherStaticClassSpiedBySuperClass.class) + @MockStatic(StaticClassMockedBySuperClass.class) + @MockStatic(AnotherStaticClassMockedBySuperClass.class) + private static class SuperClass { + + } + + private static final class StaticClassMockedBySubClass { + } + + private static final class AnotherStaticClassMockedBySubClass { + } + + private static final class StaticClassSpiedBySubClass { + } + + private static final class AnotherStaticClassSpiedBySubClass { + } + + @SpyStatic(StaticClassSpiedBySubClass.class) + @SpyStatic(AnotherStaticClassSpiedBySubClass.class) + @MockStatic(StaticClassMockedBySubClass.class) + @MockStatic(AnotherStaticClassMockedBySubClass.class) + private static final class SubClass extends SuperClass{ + } }
\ No newline at end of file diff --git a/jni/expresslog/.clang-format b/jni/expresslog/.clang-format deleted file mode 100644 index cead3a0..0000000 --- a/jni/expresslog/.clang-format +++ /dev/null @@ -1,17 +0,0 @@ -BasedOnStyle: Google -AllowShortIfStatementsOnASingleLine: true -AllowShortFunctionsOnASingleLine: false -AllowShortLoopsOnASingleLine: true -BinPackArguments: true -BinPackParameters: true -ColumnLimit: 100 -CommentPragmas: NOLINT:.* -ContinuationIndentWidth: 8 -DerivePointerAlignment: false -IndentWidth: 4 -PointerAlignment: Left -TabWidth: 4 -AccessModifierOffset: -4 -IncludeCategories: - - Regex: '^"Log\.h"' - Priority: -1 diff --git a/jni/expresslog/Android.bp b/jni/expresslog/Android.bp deleted file mode 100644 index 7bef576..0000000 --- a/jni/expresslog/Android.bp +++ /dev/null @@ -1,53 +0,0 @@ -// -// Copyright (C) 2023 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. - -// JNI library for Utils.hashString -package { - default_applicable_licenses: ["Android-Apache-2.0"], -} - -cc_library_static { - name: "libexpresslog_jni", - - sdk_version: "current", - min_sdk_version: "30", - - cflags: [ - "-Wall", - "-Werror", - "-Wextra", - - "-DNAMESPACE_FOR_HASH_FUNCTIONS=farmhash", - ], - srcs: [ - "com_android_modules_expresslog_Utils.cpp", - ], - header_libs: [ - "liblog_headers", - "libnativehelper_header_only", - "libtextclassifier_hash_headers", - ], - shared_libs: [ - "liblog", - ], - static_libs: [ - "libtextclassifier_hash_static", - ], - visibility: ["//visibility:public"], - apex_available: [ - "//apex_available:anyapex", - "//apex_available:platform", - ], -} diff --git a/jni/expresslog/com_android_modules_expresslog_Utils.cpp b/jni/expresslog/com_android_modules_expresslog_Utils.cpp deleted file mode 100644 index 973d946..0000000 --- a/jni/expresslog/com_android_modules_expresslog_Utils.cpp +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (C) 2023 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. - */ - -#define LOG_NAMESPACE "TeX.tag." -#define LOG_TAG "TeX" - -#include <log/log.h> -#include <nativehelper/scoped_local_ref.h> -#include <nativehelper/scoped_utf_chars.h> -#include <utils/hash/farmhash.h> - -// ---------------------------------------------------------------------------- -// JNI Glue -// ---------------------------------------------------------------------------- - -static jclass gStringClass = nullptr; - -/** - * Class: com_android_modules_expresslog_Utils - * Method: hashString - * Signature: (Ljava/lang/String;)J - */ -static jlong hashString(JNIEnv* env, jclass /*class*/, jstring metricNameObj) { - ScopedUtfChars name(env, metricNameObj); - if (name.c_str() == nullptr) { - return 0; - } - - return static_cast<jlong>(farmhash::Fingerprint64(name.c_str(), name.size())); -} - -static const JNINativeMethod gMethods[] = { - {"hashString", "(Ljava/lang/String;)J", (void*)hashString}, -}; - -namespace android { - -int register_com_android_modules_expresslog_Utils(JNIEnv* env) { - static const char* const kUtilsClassName = "com/android/modules/expresslog/Utils"; - static const char* const kStringClassName = "java/lang/String"; - - ScopedLocalRef<jclass> utilsCls(env, env->FindClass(kUtilsClassName)); - if (utilsCls.get() == nullptr) { - ALOGE("jni expresslog registration failure, class not found '%s'", kUtilsClassName); - return JNI_ERR; - } - - jclass stringClass = env->FindClass(kStringClassName); - if (stringClass == nullptr) { - ALOGE("jni expresslog registration failure, class not found '%s'", kStringClassName); - return JNI_ERR; - } - gStringClass = static_cast<jclass>(env->NewGlobalRef(stringClass)); - if (gStringClass == nullptr) { - ALOGE("jni expresslog Unable to create global reference '%s'", kStringClassName); - return JNI_ERR; - } - - const jint count = sizeof(gMethods) / sizeof(gMethods[0]); - int status = env->RegisterNatives(utilsCls.get(), gMethods, count); - if (status < 0) { - ALOGE("jni expresslog registration failure, status: %d", status); - return JNI_ERR; - } - return JNI_VERSION_1_4; -} - -} // namespace android |