From af8a16adf096f86f4ebe9ad987c6aef4cb270649 Mon Sep 17 00:00:00 2001 From: Zhi Dou Date: Tue, 9 Jul 2024 18:19:09 +0000 Subject: [PATCH] Implement storage files in Java This change implements storage files in java to remove the dependencies on JNI. Bug: 352078117 Test: atest aconfig_storage_file.test.java Change-Id: I34438576d09d0aa31eabc60f472e71cebf1552a0 --- tools/aconfig/TEST_MAPPING | 4 + tools/aconfig/aconfig_storage_file/Android.bp | 9 + .../storage/AconfigStorageException.java | 27 +++ .../aconfig/storage/ByteBufferReader.java | 54 ++++++ .../android/aconfig/storage/FileType.java | 45 +++++ .../android/aconfig/storage/FlagTable.java | 174 ++++++++++++++++++ .../android/aconfig/storage/FlagType.java | 42 +++++ .../aconfig/storage/FlagValueList.java | 106 +++++++++++ .../android/aconfig/storage/PackageTable.java | 162 ++++++++++++++++ .../android/aconfig/storage/TableUtils.java | 61 ++++++ .../aconfig_storage_file/tests/Android.bp | 25 ++- .../tests/AndroidManifest.xml | 27 +++ .../tests/AndroidStorageJaveTest.xml | 33 ++++ .../tests/srcs/ByteBufferReaderTest.java | 79 ++++++++ .../tests/srcs/FlagTableTest.java | 103 +++++++++++ .../tests/srcs/FlagValueListTest.java | 77 ++++++++ .../tests/srcs/PackageTableTest.java | 70 +++++++ .../tests/srcs/TestDataUtils.java | 52 ++++++ 18 files changed, 1149 insertions(+), 1 deletion(-) create mode 100644 tools/aconfig/aconfig_storage_file/srcs/android/aconfig/storage/AconfigStorageException.java create mode 100644 tools/aconfig/aconfig_storage_file/srcs/android/aconfig/storage/ByteBufferReader.java create mode 100644 tools/aconfig/aconfig_storage_file/srcs/android/aconfig/storage/FileType.java create mode 100644 tools/aconfig/aconfig_storage_file/srcs/android/aconfig/storage/FlagTable.java create mode 100644 tools/aconfig/aconfig_storage_file/srcs/android/aconfig/storage/FlagType.java create mode 100644 tools/aconfig/aconfig_storage_file/srcs/android/aconfig/storage/FlagValueList.java create mode 100644 tools/aconfig/aconfig_storage_file/srcs/android/aconfig/storage/PackageTable.java create mode 100644 tools/aconfig/aconfig_storage_file/srcs/android/aconfig/storage/TableUtils.java create mode 100644 tools/aconfig/aconfig_storage_file/tests/AndroidManifest.xml create mode 100644 tools/aconfig/aconfig_storage_file/tests/AndroidStorageJaveTest.xml create mode 100644 tools/aconfig/aconfig_storage_file/tests/srcs/ByteBufferReaderTest.java create mode 100644 tools/aconfig/aconfig_storage_file/tests/srcs/FlagTableTest.java create mode 100644 tools/aconfig/aconfig_storage_file/tests/srcs/FlagValueListTest.java create mode 100644 tools/aconfig/aconfig_storage_file/tests/srcs/PackageTableTest.java create mode 100644 tools/aconfig/aconfig_storage_file/tests/srcs/TestDataUtils.java diff --git a/tools/aconfig/TEST_MAPPING b/tools/aconfig/TEST_MAPPING index 448d8cf8af..15e41876cf 100644 --- a/tools/aconfig/TEST_MAPPING +++ b/tools/aconfig/TEST_MAPPING @@ -98,6 +98,10 @@ { // aconfig_storage file cpp integration tests "name": "aconfig_storage_file.test.cpp" + }, + { + // aconfig_storage file java integration tests + "name": "aconfig_storage_file.test.java" } ], "postsubmit": [ diff --git a/tools/aconfig/aconfig_storage_file/Android.bp b/tools/aconfig/aconfig_storage_file/Android.bp index e066e31ac1..38591947c1 100644 --- a/tools/aconfig/aconfig_storage_file/Android.bp +++ b/tools/aconfig/aconfig_storage_file/Android.bp @@ -137,3 +137,12 @@ cc_library { min_sdk_version: "29", double_loadable: true, } + +// storage file parse api java cc_library +java_library { + name: "aconfig_storage_file_java", + srcs: [ + "srcs/**/*.java", + ], + sdk_version: "core_current", +} \ No newline at end of file diff --git a/tools/aconfig/aconfig_storage_file/srcs/android/aconfig/storage/AconfigStorageException.java b/tools/aconfig/aconfig_storage_file/srcs/android/aconfig/storage/AconfigStorageException.java new file mode 100644 index 0000000000..86a75f2f65 --- /dev/null +++ b/tools/aconfig/aconfig_storage_file/srcs/android/aconfig/storage/AconfigStorageException.java @@ -0,0 +1,27 @@ +/* + * 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.aconfig.storage; + +public class AconfigStorageException extends RuntimeException { + public AconfigStorageException(String msg) { + super(msg); + } + + public AconfigStorageException(String msg, Throwable cause) { + super(msg, cause); + } +} diff --git a/tools/aconfig/aconfig_storage_file/srcs/android/aconfig/storage/ByteBufferReader.java b/tools/aconfig/aconfig_storage_file/srcs/android/aconfig/storage/ByteBufferReader.java new file mode 100644 index 0000000000..1c72364e6b --- /dev/null +++ b/tools/aconfig/aconfig_storage_file/srcs/android/aconfig/storage/ByteBufferReader.java @@ -0,0 +1,54 @@ +/* + * 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.aconfig.storage; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.charset.StandardCharsets; + +public class ByteBufferReader { + + private ByteBuffer mByteBuffer; + + public ByteBufferReader(ByteBuffer byteBuffer) { + this.mByteBuffer = byteBuffer; + this.mByteBuffer.order(ByteOrder.LITTLE_ENDIAN); + } + + public int readByte() { + return Byte.toUnsignedInt(mByteBuffer.get()); + } + + public int readShort() { + return Short.toUnsignedInt(mByteBuffer.getShort()); + } + + public int readInt() { + return this.mByteBuffer.getInt(); + } + + public String readString() { + int length = readInt(); + byte[] bytes = new byte[length]; + mByteBuffer.get(bytes, 0, length); + return new String(bytes, StandardCharsets.UTF_8); + } + + public void position(int newPosition) { + mByteBuffer.position(newPosition); + } +} diff --git a/tools/aconfig/aconfig_storage_file/srcs/android/aconfig/storage/FileType.java b/tools/aconfig/aconfig_storage_file/srcs/android/aconfig/storage/FileType.java new file mode 100644 index 0000000000..b0b1b9b186 --- /dev/null +++ b/tools/aconfig/aconfig_storage_file/srcs/android/aconfig/storage/FileType.java @@ -0,0 +1,45 @@ +/* + * 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.aconfig.storage; + +public enum FileType { + PACKAGE_MAP(0), + FLAG_MAP(1), + FLAG_VAL(2), + FLAG_INFO(3); + + public final int type; + + FileType(int type) { + this.type = type; + } + + public static FileType fromInt(int index) { + switch (index) { + case 0: + return PACKAGE_MAP; + case 1: + return FLAG_MAP; + case 2: + return FLAG_VAL; + case 3: + return FLAG_INFO; + default: + return null; + } + } +} diff --git a/tools/aconfig/aconfig_storage_file/srcs/android/aconfig/storage/FlagTable.java b/tools/aconfig/aconfig_storage_file/srcs/android/aconfig/storage/FlagTable.java new file mode 100644 index 0000000000..e85fdee20f --- /dev/null +++ b/tools/aconfig/aconfig_storage_file/srcs/android/aconfig/storage/FlagTable.java @@ -0,0 +1,174 @@ +/* + * 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.aconfig.storage; + +import java.nio.ByteBuffer; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +public class FlagTable { + + private Header mHeader; + private Map mNodeMap; + + public static FlagTable fromBytes(ByteBuffer bytes) { + FlagTable flagTable = new FlagTable(); + ByteBufferReader reader = new ByteBufferReader(bytes); + Header header = Header.fromBytes(reader); + flagTable.mHeader = header; + flagTable.mNodeMap = new HashMap(TableUtils.getTableSize(header.mNumFlags)); + reader.position(header.mNodeOffset); + for (int i = 0; i < header.mNumFlags; i++) { + Node node = Node.fromBytes(reader); + flagTable.mNodeMap.put(makeKey(node.mPackageId, node.mFlagName), node); + } + return flagTable; + } + + public Node get(int packageId, String flagName) { + return mNodeMap.get(makeKey(packageId, flagName)); + } + + public Header getHeader() { + return mHeader; + } + + private static String makeKey(int packageId, String flagName) { + StringBuilder ret = new StringBuilder(); + return ret.append(packageId).append('/').append(flagName).toString(); + } + + public static class Header { + + private int mVersion; + private String mContainer; + private FileType mFileType; + private int mFileSize; + private int mNumFlags; + private int mBucketOffset; + private int mNodeOffset; + + public static Header fromBytes(ByteBufferReader reader) { + Header header = new Header(); + header.mVersion = reader.readInt(); + header.mContainer = reader.readString(); + header.mFileType = FileType.fromInt(reader.readByte()); + header.mFileSize = reader.readInt(); + header.mNumFlags = reader.readInt(); + header.mBucketOffset = reader.readInt(); + header.mNodeOffset = reader.readInt(); + + if (header.mFileType != FileType.FLAG_MAP) { + throw new AconfigStorageException("binary file is not a flag map"); + } + + return header; + } + + public int getVersion() { + return mVersion; + } + + public String getContainer() { + return mContainer; + } + + public FileType getFileType() { + return mFileType; + } + + public int getFileSize() { + return mFileSize; + } + + public int getNumFlags() { + return mNumFlags; + } + + public int getBucketOffset() { + return mBucketOffset; + } + + public int getNodeOffset() { + return mNodeOffset; + } + } + + public static class Node { + + private String mFlagName; + private FlagType mFlagType; + private int mPackageId; + private int mFlagIndex; + private int mNextOffset; + + public static Node fromBytes(ByteBufferReader reader) { + Node node = new Node(); + node.mPackageId = reader.readInt(); + node.mFlagName = reader.readString(); + node.mFlagType = FlagType.fromInt(reader.readShort()); + node.mFlagIndex = reader.readShort(); + node.mNextOffset = reader.readInt(); + node.mNextOffset = node.mNextOffset == 0 ? -1 : node.mNextOffset; + return node; + } + + @Override + public int hashCode() { + return Objects.hash(mFlagName, mFlagType, mPackageId, mFlagIndex, mNextOffset); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + + if (obj == null || !(obj instanceof Node)) { + return false; + } + + Node other = (Node) obj; + return Objects.equals(mFlagName, other.mFlagName) + && Objects.equals(mFlagType, other.mFlagType) + && mPackageId == other.mPackageId + && mFlagIndex == other.mFlagIndex + && mNextOffset == other.mNextOffset; + } + + public String getFlagName() { + return mFlagName; + } + + public FlagType getFlagType() { + return mFlagType; + } + + public int getPackageId() { + return mPackageId; + } + + public int getFlagIndex() { + return mFlagIndex; + } + + public int getNextOffset() { + return mNextOffset; + } + } +} diff --git a/tools/aconfig/aconfig_storage_file/srcs/android/aconfig/storage/FlagType.java b/tools/aconfig/aconfig_storage_file/srcs/android/aconfig/storage/FlagType.java new file mode 100644 index 0000000000..385e2d9db9 --- /dev/null +++ b/tools/aconfig/aconfig_storage_file/srcs/android/aconfig/storage/FlagType.java @@ -0,0 +1,42 @@ +/* + * 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.aconfig.storage; + +public enum FlagType { + ReadWriteBoolean (0), + ReadOnlyBoolean(1), + FixedReadOnlyBoolean(2); + + public final int type; + + FlagType(int type) { + this.type = type; + } + + public static FlagType fromInt(int index) { + switch (index) { + case 0: + return ReadWriteBoolean; + case 1: + return ReadOnlyBoolean; + case 2: + return FixedReadOnlyBoolean; + default: + return null; + } + } +} diff --git a/tools/aconfig/aconfig_storage_file/srcs/android/aconfig/storage/FlagValueList.java b/tools/aconfig/aconfig_storage_file/srcs/android/aconfig/storage/FlagValueList.java new file mode 100644 index 0000000000..0ddc147e82 --- /dev/null +++ b/tools/aconfig/aconfig_storage_file/srcs/android/aconfig/storage/FlagValueList.java @@ -0,0 +1,106 @@ +/* + * 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.aconfig.storage; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; + +public class FlagValueList { + + private Header mHeader; + private List mList; + + private int mSize; + + public static FlagValueList fromBytes(ByteBuffer bytes) { + FlagValueList flagValueList = new FlagValueList(); + ByteBufferReader reader = new ByteBufferReader(bytes); + Header header = Header.fromBytes(reader); + flagValueList.mHeader = header; + flagValueList.mList = new ArrayList(header.mNumFlags); + reader.position(header.mBooleanValueOffset); + for (int i = 0; i < header.mNumFlags; i++) { + boolean val = reader.readByte() == 1; + flagValueList.mList.add(val); + } + flagValueList.mSize = flagValueList.mList.size(); + return flagValueList; + } + + public boolean get(int index) { + return mList.get(index); + } + + public Header getHeader() { + return mHeader; + } + + public int size() { + return mSize; + } + + public static class Header { + + private int mVersion; + private String mContainer; + private FileType mFileType; + private int mFileSize; + private int mNumFlags; + private int mBooleanValueOffset; + + public static Header fromBytes(ByteBufferReader reader) { + Header header = new Header(); + header.mVersion = reader.readInt(); + header.mContainer = reader.readString(); + header.mFileType = FileType.fromInt(reader.readByte()); + header.mFileSize = reader.readInt(); + header.mNumFlags = reader.readInt(); + header.mBooleanValueOffset = reader.readInt(); + + if (header.mFileType != FileType.FLAG_VAL) { + throw new AconfigStorageException("binary file is not a flag value file"); + } + + return header; + } + + public int getVersion() { + return mVersion; + } + + public String getContainer() { + return mContainer; + } + + public FileType getFileType() { + return mFileType; + } + + public int getFileSize() { + return mFileSize; + } + + public int getNumFlags() { + return mNumFlags; + } + + public int getBooleanValueOffset() { + return mBooleanValueOffset; + } + } +} diff --git a/tools/aconfig/aconfig_storage_file/srcs/android/aconfig/storage/PackageTable.java b/tools/aconfig/aconfig_storage_file/srcs/android/aconfig/storage/PackageTable.java new file mode 100644 index 0000000000..d04e1ac391 --- /dev/null +++ b/tools/aconfig/aconfig_storage_file/srcs/android/aconfig/storage/PackageTable.java @@ -0,0 +1,162 @@ +/* + * 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.aconfig.storage; + +import java.nio.ByteBuffer; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +public class PackageTable { + + private Header mHeader; + private Map mNodeMap; + + public static PackageTable fromBytes(ByteBuffer bytes) { + PackageTable packageTable = new PackageTable(); + ByteBufferReader reader = new ByteBufferReader(bytes); + Header header = Header.fromBytes(reader); + packageTable.mHeader = header; + packageTable.mNodeMap = new HashMap(TableUtils.getTableSize(header.mNumPackages)); + reader.position(header.mNodeOffset); + for (int i = 0; i < header.mNumPackages; i++) { + Node node = Node.fromBytes(reader); + packageTable.mNodeMap.put(node.mPackageName, node); + } + return packageTable; + } + + public Node get(String packageName) { + return mNodeMap.get(packageName); + } + + public Header getHeader() { + return mHeader; + } + + public static class Header { + + private int mVersion; + private String mContainer; + private FileType mFileType; + private int mFileSize; + private int mNumPackages; + private int mBucketOffset; + private int mNodeOffset; + + public static Header fromBytes(ByteBufferReader reader) { + Header header = new Header(); + header.mVersion = reader.readInt(); + header.mContainer = reader.readString(); + header.mFileType = FileType.fromInt(reader.readByte()); + header.mFileSize = reader.readInt(); + header.mNumPackages = reader.readInt(); + header.mBucketOffset = reader.readInt(); + header.mNodeOffset = reader.readInt(); + + if (header.mFileType != FileType.PACKAGE_MAP) { + throw new AconfigStorageException("binary file is not a package map"); + } + + return header; + } + + public int getVersion() { + return mVersion; + } + + public String getContainer() { + return mContainer; + } + + public FileType getFileType() { + return mFileType; + } + + public int getFileSize() { + return mFileSize; + } + + public int getNumPackages() { + return mNumPackages; + } + + public int getBucketOffset() { + return mBucketOffset; + } + + public int getNodeOffset() { + return mNodeOffset; + } + } + + public static class Node { + + private String mPackageName; + private int mPackageId; + private int mBooleanStartIndex; + private int mNextOffset; + + public static Node fromBytes(ByteBufferReader reader) { + Node node = new Node(); + node.mPackageName = reader.readString(); + node.mPackageId = reader.readInt(); + node.mBooleanStartIndex = reader.readInt(); + node.mNextOffset = reader.readInt(); + node.mNextOffset = node.mNextOffset == 0 ? -1 : node.mNextOffset; + return node; + } + + @Override + public int hashCode() { + return Objects.hash(mPackageName, mPackageId, mBooleanStartIndex, mNextOffset); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + + if (obj == null || !(obj instanceof Node)) { + return false; + } + + Node other = (Node) obj; + return Objects.equals(mPackageName, other.mPackageName) + && mPackageId == other.mPackageId + && mBooleanStartIndex == other.mBooleanStartIndex + && mNextOffset == other.mNextOffset; + } + + public String getPackageName() { + return mPackageName; + } + + public int getPackageId() { + return mPackageId; + } + + public int getBooleanStartIndex() { + return mBooleanStartIndex; + } + + public int getNextOffset() { + return mNextOffset; + } + } +} diff --git a/tools/aconfig/aconfig_storage_file/srcs/android/aconfig/storage/TableUtils.java b/tools/aconfig/aconfig_storage_file/srcs/android/aconfig/storage/TableUtils.java new file mode 100644 index 0000000000..714b53bf31 --- /dev/null +++ b/tools/aconfig/aconfig_storage_file/srcs/android/aconfig/storage/TableUtils.java @@ -0,0 +1,61 @@ +/* + * 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.aconfig.storage; + +public class TableUtils { + + private static final int[] HASH_PRIMES = + new int[] { + 7, + 17, + 29, + 53, + 97, + 193, + 389, + 769, + 1543, + 3079, + 6151, + 12289, + 24593, + 49157, + 98317, + 196613, + 393241, + 786433, + 1572869, + 3145739, + 6291469, + 12582917, + 25165843, + 50331653, + 100663319, + 201326611, + 402653189, + 805306457, + 1610612741 + }; + + public static int getTableSize(int numEntries) { + for (int i : HASH_PRIMES) { + if (i < 2 * numEntries) continue; + return i; + } + throw new AconfigStorageException("Number of items in a hash table exceeds limit"); + } +} diff --git a/tools/aconfig/aconfig_storage_file/tests/Android.bp b/tools/aconfig/aconfig_storage_file/tests/Android.bp index 26b7800877..c33127fc86 100644 --- a/tools/aconfig/aconfig_storage_file/tests/Android.bp +++ b/tools/aconfig/aconfig_storage_file/tests/Android.bp @@ -1,4 +1,3 @@ - cc_test { name: "aconfig_storage_file.test.cpp", team: "trendy_team_android_core_experiments", @@ -21,3 +20,27 @@ cc_test { "general-tests", ], } + +android_test { + name: "aconfig_storage_file.test.java", + team: "trendy_team_android_core_experiments", + srcs: [ + "srcs/**/*.java", + ], + static_libs: [ + "aconfig_storage_file_java", + "androidx.test.runner", + "junit", + ], + sdk_version: "test_current", + test_config: "AndroidStorageJaveTest.xml", + data: [ + "package.map", + "flag.map", + "flag.val", + "flag.info", + ], + test_suites: [ + "general-tests", + ], +} diff --git a/tools/aconfig/aconfig_storage_file/tests/AndroidManifest.xml b/tools/aconfig/aconfig_storage_file/tests/AndroidManifest.xml new file mode 100644 index 0000000000..5e01879157 --- /dev/null +++ b/tools/aconfig/aconfig_storage_file/tests/AndroidManifest.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + diff --git a/tools/aconfig/aconfig_storage_file/tests/AndroidStorageJaveTest.xml b/tools/aconfig/aconfig_storage_file/tests/AndroidStorageJaveTest.xml new file mode 100644 index 0000000000..2d52d44c57 --- /dev/null +++ b/tools/aconfig/aconfig_storage_file/tests/AndroidStorageJaveTest.xml @@ -0,0 +1,33 @@ + + + + + + + + + + \ No newline at end of file diff --git a/tools/aconfig/aconfig_storage_file/tests/srcs/ByteBufferReaderTest.java b/tools/aconfig/aconfig_storage_file/tests/srcs/ByteBufferReaderTest.java new file mode 100644 index 0000000000..66a8166812 --- /dev/null +++ b/tools/aconfig/aconfig_storage_file/tests/srcs/ByteBufferReaderTest.java @@ -0,0 +1,79 @@ +/* + * 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.aconfig.storage.test; + +import static org.junit.Assert.assertEquals; + +import android.aconfig.storage.ByteBufferReader; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.charset.StandardCharsets; + +@RunWith(JUnit4.class) +public class ByteBufferReaderTest { + + @Test + public void testReadByte() { + ByteBuffer buffer = ByteBuffer.allocate(1); + byte expect = 10; + buffer.put(expect).rewind(); + + ByteBufferReader reader = new ByteBufferReader(buffer); + assertEquals(expect, reader.readByte()); + } + + @Test + public void testReadShort() { + ByteBuffer buffer = ByteBuffer.allocate(4); + buffer.order(ByteOrder.LITTLE_ENDIAN); + short expect = Short.MAX_VALUE; + buffer.putShort(expect).rewind(); + + ByteBufferReader reader = new ByteBufferReader(buffer); + assertEquals(expect, reader.readShort()); + } + + @Test + public void testReadInt() { + ByteBuffer buffer = ByteBuffer.allocate(4); + buffer.order(ByteOrder.LITTLE_ENDIAN); + int expect = 10000; + buffer.putInt(expect).rewind(); + + ByteBufferReader reader = new ByteBufferReader(buffer); + assertEquals(expect, reader.readInt()); + } + + @Test + public void testReadString() { + String expect = "test read string"; + byte[] bytes = expect.getBytes(StandardCharsets.UTF_8); + + ByteBuffer buffer = ByteBuffer.allocate(expect.length() * 2 + 4); + buffer.order(ByteOrder.LITTLE_ENDIAN); + buffer.putInt(expect.length()).put(bytes).rewind(); + + ByteBufferReader reader = new ByteBufferReader(buffer); + + assertEquals(expect, reader.readString()); + } +} diff --git a/tools/aconfig/aconfig_storage_file/tests/srcs/FlagTableTest.java b/tools/aconfig/aconfig_storage_file/tests/srcs/FlagTableTest.java new file mode 100644 index 0000000000..fd40d4c4ef --- /dev/null +++ b/tools/aconfig/aconfig_storage_file/tests/srcs/FlagTableTest.java @@ -0,0 +1,103 @@ +/* + * 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.aconfig.storage.test; + +import static org.junit.Assert.assertEquals; + +import android.aconfig.storage.FileType; +import android.aconfig.storage.FlagTable; +import android.aconfig.storage.FlagType; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class FlagTableTest { + + @Test + public void testFlagTable_rightHeader() throws Exception { + FlagTable flagTable = FlagTable.fromBytes(TestDataUtils.getTestFlagMapByteBuffer()); + FlagTable.Header header = flagTable.getHeader(); + assertEquals(1, header.getVersion()); + assertEquals("mockup", header.getContainer()); + assertEquals(FileType.FLAG_MAP, header.getFileType()); + assertEquals(321, header.getFileSize()); + assertEquals(8, header.getNumFlags()); + assertEquals(31, header.getBucketOffset()); + assertEquals(99, header.getNodeOffset()); + } + + @Test + public void testFlagTable_rightNode() throws Exception { + FlagTable flagTable = FlagTable.fromBytes(TestDataUtils.getTestFlagMapByteBuffer()); + + FlagTable.Node node1 = flagTable.get(0, "enabled_ro"); + FlagTable.Node node2 = flagTable.get(0, "enabled_rw"); + FlagTable.Node node3 = flagTable.get(2, "enabled_rw"); + FlagTable.Node node4 = flagTable.get(1, "disabled_rw"); + FlagTable.Node node5 = flagTable.get(1, "enabled_fixed_ro"); + FlagTable.Node node6 = flagTable.get(1, "enabled_ro"); + FlagTable.Node node7 = flagTable.get(2, "enabled_fixed_ro"); + FlagTable.Node node8 = flagTable.get(0, "disabled_rw"); + + assertEquals("enabled_ro", node1.getFlagName()); + assertEquals("enabled_rw", node2.getFlagName()); + assertEquals("enabled_rw", node3.getFlagName()); + assertEquals("disabled_rw", node4.getFlagName()); + assertEquals("enabled_fixed_ro", node5.getFlagName()); + assertEquals("enabled_ro", node6.getFlagName()); + assertEquals("enabled_fixed_ro", node7.getFlagName()); + assertEquals("disabled_rw", node8.getFlagName()); + + assertEquals(0, node1.getPackageId()); + assertEquals(0, node2.getPackageId()); + assertEquals(2, node3.getPackageId()); + assertEquals(1, node4.getPackageId()); + assertEquals(1, node5.getPackageId()); + assertEquals(1, node6.getPackageId()); + assertEquals(2, node7.getPackageId()); + assertEquals(0, node8.getPackageId()); + + assertEquals(FlagType.ReadOnlyBoolean, node1.getFlagType()); + assertEquals(FlagType.ReadWriteBoolean, node2.getFlagType()); + assertEquals(FlagType.ReadWriteBoolean, node3.getFlagType()); + assertEquals(FlagType.ReadWriteBoolean, node4.getFlagType()); + assertEquals(FlagType.FixedReadOnlyBoolean, node5.getFlagType()); + assertEquals(FlagType.ReadOnlyBoolean, node6.getFlagType()); + assertEquals(FlagType.FixedReadOnlyBoolean, node7.getFlagType()); + assertEquals(FlagType.ReadWriteBoolean, node8.getFlagType()); + + assertEquals(1, node1.getFlagIndex()); + assertEquals(2, node2.getFlagIndex()); + assertEquals(1, node3.getFlagIndex()); + assertEquals(0, node4.getFlagIndex()); + assertEquals(1, node5.getFlagIndex()); + assertEquals(2, node6.getFlagIndex()); + assertEquals(0, node7.getFlagIndex()); + assertEquals(0, node8.getFlagIndex()); + + assertEquals(-1, node1.getNextOffset()); + assertEquals(151, node2.getNextOffset()); + assertEquals(-1, node3.getNextOffset()); + assertEquals(-1, node4.getNextOffset()); + assertEquals(236, node5.getNextOffset()); + assertEquals(-1, node6.getNextOffset()); + assertEquals(-1, node7.getNextOffset()); + assertEquals(-1, node8.getNextOffset()); + } +} diff --git a/tools/aconfig/aconfig_storage_file/tests/srcs/FlagValueListTest.java b/tools/aconfig/aconfig_storage_file/tests/srcs/FlagValueListTest.java new file mode 100644 index 0000000000..c18590accc --- /dev/null +++ b/tools/aconfig/aconfig_storage_file/tests/srcs/FlagValueListTest.java @@ -0,0 +1,77 @@ +/* + * 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.aconfig.storage.test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import android.aconfig.storage.FileType; +import android.aconfig.storage.FlagTable; +import android.aconfig.storage.FlagValueList; +import android.aconfig.storage.PackageTable; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class FlagValueListTest { + + @Test + public void testFlagValueList_rightHeader() throws Exception { + FlagValueList flagValueList = + FlagValueList.fromBytes(TestDataUtils.getTestFlagValByteBuffer()); + FlagValueList.Header header = flagValueList.getHeader(); + assertEquals(1, header.getVersion()); + assertEquals("mockup", header.getContainer()); + assertEquals(FileType.FLAG_VAL, header.getFileType()); + assertEquals(35, header.getFileSize()); + assertEquals(8, header.getNumFlags()); + assertEquals(27, header.getBooleanValueOffset()); + } + + @Test + public void testFlagValueList_rightNode() throws Exception { + FlagValueList flagValueList = + FlagValueList.fromBytes(TestDataUtils.getTestFlagValByteBuffer()); + + boolean[] expected = new boolean[] {false, true, true, false, true, true, true, true}; + assertEquals(expected.length, flagValueList.size()); + + for (int i = 0; i < flagValueList.size(); i++) { + assertEquals(expected[i], flagValueList.get(i)); + } + } + + @Test + public void testFlagValueList_getValue() throws Exception { + PackageTable packageTable = + PackageTable.fromBytes(TestDataUtils.getTestPackageMapByteBuffer()); + FlagTable flagTable = FlagTable.fromBytes(TestDataUtils.getTestFlagMapByteBuffer()); + + FlagValueList flagValueList = + FlagValueList.fromBytes(TestDataUtils.getTestFlagValByteBuffer()); + + PackageTable.Node pNode = packageTable.get("com.android.aconfig.storage.test_1"); + FlagTable.Node fNode = flagTable.get(pNode.getPackageId(), "enabled_rw"); + assertTrue(flagValueList.get(pNode.getBooleanStartIndex() + fNode.getFlagIndex())); + + pNode = packageTable.get("com.android.aconfig.storage.test_4"); + fNode = flagTable.get(pNode.getPackageId(), "enabled_fixed_ro"); + assertTrue(flagValueList.get(pNode.getBooleanStartIndex() + fNode.getFlagIndex())); + } +} diff --git a/tools/aconfig/aconfig_storage_file/tests/srcs/PackageTableTest.java b/tools/aconfig/aconfig_storage_file/tests/srcs/PackageTableTest.java new file mode 100644 index 0000000000..e7e19d8d51 --- /dev/null +++ b/tools/aconfig/aconfig_storage_file/tests/srcs/PackageTableTest.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.aconfig.storage.test; + +import static org.junit.Assert.assertEquals; + +import android.aconfig.storage.FileType; +import android.aconfig.storage.PackageTable; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class PackageTableTest { + + @Test + public void testPackageTable_rightHeader() throws Exception { + PackageTable packageTable = + PackageTable.fromBytes(TestDataUtils.getTestPackageMapByteBuffer()); + PackageTable.Header header = packageTable.getHeader(); + assertEquals(1, header.getVersion()); + assertEquals("mockup", header.getContainer()); + assertEquals(FileType.PACKAGE_MAP, header.getFileType()); + assertEquals(209, header.getFileSize()); + assertEquals(3, header.getNumPackages()); + assertEquals(31, header.getBucketOffset()); + assertEquals(59, header.getNodeOffset()); + } + + @Test + public void testPackageTable_rightNode() throws Exception { + PackageTable packageTable = + PackageTable.fromBytes(TestDataUtils.getTestPackageMapByteBuffer()); + + PackageTable.Node node1 = packageTable.get("com.android.aconfig.storage.test_1"); + PackageTable.Node node2 = packageTable.get("com.android.aconfig.storage.test_2"); + PackageTable.Node node4 = packageTable.get("com.android.aconfig.storage.test_4"); + + assertEquals("com.android.aconfig.storage.test_1", node1.getPackageName()); + assertEquals("com.android.aconfig.storage.test_2", node2.getPackageName()); + assertEquals("com.android.aconfig.storage.test_4", node4.getPackageName()); + + assertEquals(0, node1.getPackageId()); + assertEquals(1, node2.getPackageId()); + assertEquals(2, node4.getPackageId()); + + assertEquals(0, node1.getBooleanStartIndex()); + assertEquals(3, node2.getBooleanStartIndex()); + assertEquals(6, node4.getBooleanStartIndex()); + + assertEquals(159, node1.getNextOffset()); + assertEquals(-1, node2.getNextOffset()); + assertEquals(-1, node4.getNextOffset()); + } +} diff --git a/tools/aconfig/aconfig_storage_file/tests/srcs/TestDataUtils.java b/tools/aconfig/aconfig_storage_file/tests/srcs/TestDataUtils.java new file mode 100644 index 0000000000..f35952d392 --- /dev/null +++ b/tools/aconfig/aconfig_storage_file/tests/srcs/TestDataUtils.java @@ -0,0 +1,52 @@ +/* + * 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.aconfig.storage.test; + +import java.io.FileInputStream; +import java.io.InputStream; +import java.nio.ByteBuffer; + +public final class TestDataUtils { + private static final String TEST_PACKAGE_MAP_PATH = "package.map"; + private static final String TEST_FLAG_MAP_PATH = "flag.map"; + private static final String TEST_FLAG_VAL_PATH = "flag.val"; + private static final String TEST_FLAG_INFO_PATH = "flag.info"; + + private static final String TESTDATA_PATH = + "/data/local/tmp/aconfig_storage_file_test_java/testdata/"; + + public static ByteBuffer getTestPackageMapByteBuffer() throws Exception { + return readFile(TESTDATA_PATH + TEST_PACKAGE_MAP_PATH); + } + + public static ByteBuffer getTestFlagMapByteBuffer() throws Exception { + return readFile(TESTDATA_PATH + TEST_FLAG_MAP_PATH); + } + + public static ByteBuffer getTestFlagValByteBuffer() throws Exception { + return readFile(TESTDATA_PATH + TEST_FLAG_VAL_PATH); + } + + public static ByteBuffer getTestFlagInfoByteBuffer() throws Exception { + return readFile(TESTDATA_PATH + TEST_FLAG_INFO_PATH); + } + + private static ByteBuffer readFile(String fileName) throws Exception { + InputStream input = new FileInputStream(fileName); + return ByteBuffer.wrap(input.readAllBytes()); + } +}