diff options
author | android-build-team Robot <android-build-team-robot@google.com> | 2019-11-11 21:18:09 +0000 |
---|---|---|
committer | android-build-team Robot <android-build-team-robot@google.com> | 2019-11-11 21:18:09 +0000 |
commit | 8a1a29ec3eca70431c9d2889c1c6be2ad4762ca2 (patch) | |
tree | d84b28d350017c324faceae3d10ccecfcbaa8ec8 | |
parent | 6a9029542ce465b65f12221bdb6c9b74cec3252f (diff) | |
parent | bde7153521922a2494889f496d2bd394e5708d81 (diff) | |
download | extras-android10-mainline-resolv-release.tar.gz |
Snap for 6001391 from bde7153521922a2494889f496d2bd394e5708d81 to qt-aml-resolv-releaseandroid-mainline-10.0.0_r8android10-mainline-resolv-release
Change-Id: I638cbefe9e344376c4cb155dda39f8459f5ab040
30 files changed, 674 insertions, 297 deletions
diff --git a/libfscrypt/.clang-format b/libfscrypt/.clang-format new file mode 120000 index 00000000..973b2fab --- /dev/null +++ b/libfscrypt/.clang-format @@ -0,0 +1 @@ +../../../build/soong/scripts/system-clang-format
\ No newline at end of file diff --git a/libfscrypt/fscrypt.cpp b/libfscrypt/fscrypt.cpp index 66a43200..b76f0b17 100644 --- a/libfscrypt/fscrypt.cpp +++ b/libfscrypt/fscrypt.cpp @@ -18,6 +18,7 @@ #include <android-base/file.h> #include <android-base/logging.h> +#include <android-base/strings.h> #include <android-base/unique_fd.h> #include <asm/ioctl.h> #include <cutils/properties.h> @@ -32,6 +33,10 @@ #include <utils/misc.h> #include <array> +#include <string> +#include <vector> + +using namespace std::string_literals; // TODO: switch to <linux/fscrypt.h> once it's in Bionic #ifndef FSCRYPT_POLICY_V1 @@ -67,7 +72,45 @@ struct fscrypt_policy_v2 { #define HEX_LOOKUP "0123456789abcdef" -#define MAX_KEY_REF_SIZE_HEX (2 * FSCRYPT_KEY_IDENTIFIER_SIZE + 1) +struct ModeLookupEntry { + std::string name; + int id; +}; + +static const auto contents_modes = std::vector<ModeLookupEntry>{ + {"aes-256-xts"s, FS_ENCRYPTION_MODE_AES_256_XTS}, + {"software"s, FS_ENCRYPTION_MODE_AES_256_XTS}, + {"adiantum"s, FS_ENCRYPTION_MODE_ADIANTUM}, + {"ice"s, FS_ENCRYPTION_MODE_PRIVATE}, +}; + +static const auto filenames_modes = std::vector<ModeLookupEntry>{ + {"aes-256-cts"s, FS_ENCRYPTION_MODE_AES_256_CTS}, + {"aes-256-heh"s, FS_ENCRYPTION_MODE_AES_256_HEH}, + {"adiantum"s, FS_ENCRYPTION_MODE_ADIANTUM}, +}; + +static bool LookupModeByName(const std::vector<struct ModeLookupEntry>& modes, + const std::string& name, int* result) { + for (const auto& e : modes) { + if (e.name == name) { + *result = e.id; + return true; + } + } + return false; +} + +static bool LookupModeById(const std::vector<struct ModeLookupEntry>& modes, int id, + std::string* result) { + for (const auto& e : modes) { + if (e.id == id) { + *result = e.name; + return true; + } + } + return false; +} bool fscrypt_is_native() { char value[PROPERTY_VALUE_MAX]; @@ -75,6 +118,9 @@ bool fscrypt_is_native() { return !strcmp(value, "file"); } +namespace android { +namespace fscrypt { + static void log_ls(const char* dirname) { std::array<const char*, 3> argv = {"ls", "-laZ", dirname}; int status = 0; @@ -96,17 +142,85 @@ static void log_ls(const char* dirname) { } } -static void keyrefstring(const char* key_raw_ref, size_t key_raw_ref_length, char* hex) { - size_t j = 0; - for (size_t i = 0; i < key_raw_ref_length; i++) { - hex[j++] = HEX_LOOKUP[(key_raw_ref[i] & 0xF0) >> 4]; - hex[j++] = HEX_LOOKUP[key_raw_ref[i] & 0x0F]; +void BytesToHex(const std::string& bytes, std::string* hex) { + hex->clear(); + for (char c : bytes) { + *hex += HEX_LOOKUP[(c & 0xF0) >> 4]; + *hex += HEX_LOOKUP[c & 0x0F]; } - hex[j] = '\0'; } -static uint8_t fscrypt_get_policy_flags(int filenames_encryption_mode, int policy_version) { - uint8_t flags = 0; +static bool fscrypt_is_encrypted(int fd) { + fscrypt_policy_v1 policy; + + // success => encrypted with v1 policy + // EINVAL => encrypted with v2 policy + // ENODATA => not encrypted + return ioctl(fd, FS_IOC_GET_ENCRYPTION_POLICY, &policy) == 0 || errno == EINVAL; +} + +bool OptionsToString(const EncryptionOptions& options, std::string* options_string) { + std::string contents_mode, filenames_mode; + if (!LookupModeById(contents_modes, options.contents_mode, &contents_mode)) { + return false; + } + if (!LookupModeById(filenames_modes, options.filenames_mode, &filenames_mode)) { + return false; + } + *options_string = contents_mode + ":" + filenames_mode + ":v" + std::to_string(options.version); + if ((options.flags & FSCRYPT_POLICY_FLAG_IV_INO_LBLK_64)) { + *options_string += "+inlinecrypt_optimized"; + } + EncryptionOptions options_check; + if (!ParseOptions(*options_string, &options_check)) { + LOG(ERROR) << "Internal error serializing options as string: " << *options_string; + return false; + } + if (memcmp(&options, &options_check, sizeof(options_check)) != 0) { + LOG(ERROR) << "Internal error serializing options as string, round trip failed: " + << *options_string; + return false; + } + return true; +} + +bool ParseOptions(const std::string& options_string, EncryptionOptions* options) { + memset(options, '\0', sizeof(*options)); + auto parts = android::base::Split(options_string, ":"); + if (parts.size() < 1 || parts.size() > 3) { + return false; + } + if (!LookupModeByName(contents_modes, parts[0], &options->contents_mode)) { + LOG(ERROR) << "Invalid file contents encryption mode: " << parts[0]; + return false; + } + if (parts.size() >= 2) { + if (!LookupModeByName(filenames_modes, parts[1], &options->filenames_mode)) { + LOG(ERROR) << "Invalid file names encryption mode: " << parts[1]; + return false; + } + } else if (options->contents_mode == FS_ENCRYPTION_MODE_ADIANTUM) { + options->filenames_mode = FS_ENCRYPTION_MODE_ADIANTUM; + } else { + options->filenames_mode = FS_ENCRYPTION_MODE_AES_256_CTS; + } + options->version = 1; + options->flags = 0; + if (parts.size() >= 3) { + auto flags = android::base::Split(parts[2], "+"); + for (const auto& flag : flags) { + if (flag == "v1") { + options->version = 1; + } else if (flag == "v2") { + options->version = 2; + } else if (flag == "inlinecrypt_optimized") { + options->flags |= FSCRYPT_POLICY_FLAG_IV_INO_LBLK_64; + } else { + LOG(ERROR) << "Unknown flag: " << flag; + return false; + } + } + } // In the original setting of v1 policies and AES-256-CTS we used 4-byte // padding of filenames, so we have to retain that for compatibility. @@ -114,105 +228,77 @@ static uint8_t fscrypt_get_policy_flags(int filenames_encryption_mode, int polic // For everything else, use 16-byte padding. This is more secure (it helps // hide the length of filenames), and it makes the inputs evenly divisible // into cipher blocks which is more efficient for encryption and decryption. - if (policy_version == 1 && filenames_encryption_mode == FS_ENCRYPTION_MODE_AES_256_CTS) { - flags |= FS_POLICY_FLAGS_PAD_4; + if (options->version == 1 && options->filenames_mode == FS_ENCRYPTION_MODE_AES_256_CTS) { + options->flags |= FS_POLICY_FLAGS_PAD_4; } else { - flags |= FS_POLICY_FLAGS_PAD_16; + options->flags |= FS_POLICY_FLAGS_PAD_16; } // Use DIRECT_KEY for Adiantum, since it's much more efficient but just as // secure since Android doesn't reuse the same master key for multiple // encryption modes. - if (filenames_encryption_mode == FS_ENCRYPTION_MODE_ADIANTUM) { - flags |= FS_POLICY_FLAG_DIRECT_KEY; + if (options->filenames_mode == FS_ENCRYPTION_MODE_ADIANTUM) { + options->flags |= FS_POLICY_FLAG_DIRECT_KEY; } - - return flags; + return true; } -static bool fscrypt_is_encrypted(int fd) { - fscrypt_policy_v1 policy; - - // success => encrypted with v1 policy - // EINVAL => encrypted with v2 policy - // ENODATA => not encrypted - return ioctl(fd, FS_IOC_GET_ENCRYPTION_POLICY, &policy) == 0 || errno == EINVAL; +static std::string PolicyDebugString(const EncryptionPolicy& policy) { + std::stringstream ss; + std::string ref_hex; + BytesToHex(policy.key_raw_ref, &ref_hex); + ss << ref_hex; + ss << " v" << policy.options.version; + ss << " modes " << policy.options.contents_mode << "/" << policy.options.filenames_mode; + ss << std::hex << " flags 0x" << policy.options.flags; + return ss.str(); } -int fscrypt_policy_ensure(const char* directory, const char* key_raw_ref, size_t key_raw_ref_length, - const char* contents_encryption_mode, - const char* filenames_encryption_mode, int policy_version) { - int contents_mode = 0; - int filenames_mode = 0; - - if (!strcmp(contents_encryption_mode, "software") || - !strcmp(contents_encryption_mode, "aes-256-xts")) { - contents_mode = FS_ENCRYPTION_MODE_AES_256_XTS; - } else if (!strcmp(contents_encryption_mode, "adiantum")) { - contents_mode = FS_ENCRYPTION_MODE_ADIANTUM; - } else if (!strcmp(contents_encryption_mode, "ice")) { - contents_mode = FS_ENCRYPTION_MODE_PRIVATE; - } else { - LOG(ERROR) << "Invalid file contents encryption mode: " - << contents_encryption_mode; - return -1; - } - - if (!strcmp(filenames_encryption_mode, "aes-256-cts")) { - filenames_mode = FS_ENCRYPTION_MODE_AES_256_CTS; - } else if (!strcmp(filenames_encryption_mode, "aes-256-heh")) { - filenames_mode = FS_ENCRYPTION_MODE_AES_256_HEH; - } else if (!strcmp(filenames_encryption_mode, "adiantum")) { - filenames_mode = FS_ENCRYPTION_MODE_ADIANTUM; - } else { - LOG(ERROR) << "Invalid file names encryption mode: " - << filenames_encryption_mode; - return -1; - } - +bool EnsurePolicy(const EncryptionPolicy& policy, const std::string& directory) { union { fscrypt_policy_v1 v1; fscrypt_policy_v2 v2; - } policy; - memset(&policy, 0, sizeof(policy)); + } kern_policy; + memset(&kern_policy, 0, sizeof(kern_policy)); - switch (policy_version) { + switch (policy.options.version) { case 1: - if (key_raw_ref_length != FSCRYPT_KEY_DESCRIPTOR_SIZE) { - LOG(ERROR) << "Invalid key ref length for v1 policy: " << key_raw_ref_length; - return -1; + if (policy.key_raw_ref.size() != FSCRYPT_KEY_DESCRIPTOR_SIZE) { + LOG(ERROR) << "Invalid key descriptor length for v1 policy: " + << policy.key_raw_ref.size(); + return false; } // Careful: FSCRYPT_POLICY_V1 is actually 0 in the API, so make sure // to use it here instead of a literal 1. - policy.v1.version = FSCRYPT_POLICY_V1; - policy.v1.contents_encryption_mode = contents_mode; - policy.v1.filenames_encryption_mode = filenames_mode; - policy.v1.flags = fscrypt_get_policy_flags(filenames_mode, policy_version); - memcpy(policy.v1.master_key_descriptor, key_raw_ref, FSCRYPT_KEY_DESCRIPTOR_SIZE); + kern_policy.v1.version = FSCRYPT_POLICY_V1; + kern_policy.v1.contents_encryption_mode = policy.options.contents_mode; + kern_policy.v1.filenames_encryption_mode = policy.options.filenames_mode; + kern_policy.v1.flags = policy.options.flags; + policy.key_raw_ref.copy(reinterpret_cast<char*>(kern_policy.v1.master_key_descriptor), + FSCRYPT_KEY_DESCRIPTOR_SIZE); break; case 2: - if (key_raw_ref_length != FSCRYPT_KEY_IDENTIFIER_SIZE) { - LOG(ERROR) << "Invalid key ref length for v2 policy: " << key_raw_ref_length; - return -1; + if (policy.key_raw_ref.size() != FSCRYPT_KEY_IDENTIFIER_SIZE) { + LOG(ERROR) << "Invalid key identifier length for v2 policy: " + << policy.key_raw_ref.size(); + return false; } - policy.v2.version = FSCRYPT_POLICY_V2; - policy.v2.contents_encryption_mode = contents_mode; - policy.v2.filenames_encryption_mode = filenames_mode; - policy.v2.flags = fscrypt_get_policy_flags(filenames_mode, policy_version); - memcpy(policy.v2.master_key_identifier, key_raw_ref, FSCRYPT_KEY_IDENTIFIER_SIZE); + kern_policy.v2.version = FSCRYPT_POLICY_V2; + kern_policy.v2.contents_encryption_mode = policy.options.contents_mode; + kern_policy.v2.filenames_encryption_mode = policy.options.filenames_mode; + kern_policy.v2.flags = policy.options.flags; + policy.key_raw_ref.copy(reinterpret_cast<char*>(kern_policy.v2.master_key_identifier), + FSCRYPT_KEY_IDENTIFIER_SIZE); break; default: - LOG(ERROR) << "Invalid encryption policy version: " << policy_version; - return -1; + LOG(ERROR) << "Invalid encryption policy version: " << policy.options.version; + return false; } - char ref[MAX_KEY_REF_SIZE_HEX]; - keyrefstring(key_raw_ref, key_raw_ref_length, ref); - - android::base::unique_fd fd(open(directory, O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC)); + android::base::unique_fd fd(open(directory.c_str(), O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC)); if (fd == -1) { PLOG(ERROR) << "Failed to open directory " << directory; - return -1; + return false; } bool already_encrypted = fscrypt_is_encrypted(fd); @@ -220,7 +306,7 @@ int fscrypt_policy_ensure(const char* directory, const char* key_raw_ref, size_t // FS_IOC_SET_ENCRYPTION_POLICY will set the policy if the directory is // unencrypted; otherwise it will verify that the existing policy matches. // Setting the policy will fail if the directory is already nonempty. - if (ioctl(fd, FS_IOC_SET_ENCRYPTION_POLICY, &policy) != 0) { + if (ioctl(fd, FS_IOC_SET_ENCRYPTION_POLICY, &kern_policy) != 0) { std::string reason; switch (errno) { case EEXIST: @@ -230,20 +316,23 @@ int fscrypt_policy_ensure(const char* directory, const char* key_raw_ref, size_t reason = strerror(errno); break; } - LOG(ERROR) << "Failed to set encryption policy of " << directory << " to " << ref - << " modes " << contents_mode << "/" << filenames_mode << ": " << reason; + LOG(ERROR) << "Failed to set encryption policy of " << directory << " to " + << PolicyDebugString(policy) << ": " << reason; if (errno == ENOTEMPTY) { - log_ls(directory); + log_ls(directory.c_str()); } - return -1; + return false; } if (already_encrypted) { - LOG(INFO) << "Verified that " << directory << " has the encryption policy " << ref - << " modes " << contents_mode << "/" << filenames_mode; + LOG(INFO) << "Verified that " << directory << " has the encryption policy " + << PolicyDebugString(policy); } else { - LOG(INFO) << "Encryption policy of " << directory << " set to " << ref << " modes " - << contents_mode << "/" << filenames_mode; + LOG(INFO) << "Encryption policy of " << directory << " set to " + << PolicyDebugString(policy); } - return 0; + return true; } + +} // namespace fscrypt +} // namespace android diff --git a/libfscrypt/include/fscrypt/fscrypt.h b/libfscrypt/include/fscrypt/fscrypt.h index 13358bb7..2b809866 100644 --- a/libfscrypt/include/fscrypt/fscrypt.h +++ b/libfscrypt/include/fscrypt/fscrypt.h @@ -17,23 +17,45 @@ #ifndef _FSCRYPT_H_ #define _FSCRYPT_H_ -#include <sys/cdefs.h> -#include <stdbool.h> -#include <cutils/multiuser.h> +#include <string> -__BEGIN_DECLS +// TODO: switch to <linux/fscrypt.h> once it's in Bionic +#define FSCRYPT_POLICY_FLAG_IV_INO_LBLK_64 0x08 bool fscrypt_is_native(); -int fscrypt_policy_ensure(const char* directory, const char* key_raw_ref, size_t key_raw_ref_length, - const char* contents_encryption_mode, - const char* filenames_encryption_mode, int policy_version); - static const char* fscrypt_unencrypted_folder = "/unencrypted"; static const char* fscrypt_key_ref = "/unencrypted/ref"; static const char* fscrypt_key_per_boot_ref = "/unencrypted/per_boot_ref"; static const char* fscrypt_key_mode = "/unencrypted/mode"; -__END_DECLS +namespace android { +namespace fscrypt { + +struct EncryptionOptions { + int version; + int contents_mode; + int filenames_mode; + int flags; + + // Ensure that "version" is not valid on creation and so must be explicitly set + EncryptionOptions() : version(0) {} +}; + +struct EncryptionPolicy { + EncryptionOptions options; + std::string key_raw_ref; +}; + +void BytesToHex(const std::string& bytes, std::string* hex); + +bool OptionsToString(const EncryptionOptions& options, std::string* options_string); + +bool ParseOptions(const std::string& options_string, EncryptionOptions* options); + +bool EnsurePolicy(const EncryptionPolicy& policy, const std::string& directory); + +} // namespace fscrypt +} // namespace android #endif // _FSCRYPT_H_ diff --git a/libfscrypt/tests/Android.bp b/libfscrypt/tests/Android.bp new file mode 100644 index 00000000..985b425f --- /dev/null +++ b/libfscrypt/tests/Android.bp @@ -0,0 +1,33 @@ +// Copyright (C) 2019 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +cc_test { + name: "libfscrypt_unit_test", + + shared_libs: [ + "libbase", + ], + static_libs: [ + "libfscrypt", + ], + srcs: [ + "fscrypt_test.cpp", + ], + + cflags: [ + "-Wall", + "-Wextra", + "-Werror", + ], +} diff --git a/libfscrypt/tests/fscrypt_test.cpp b/libfscrypt/tests/fscrypt_test.cpp new file mode 100644 index 00000000..677f0f22 --- /dev/null +++ b/libfscrypt/tests/fscrypt_test.cpp @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <linux/fs.h> + +#include <fscrypt/fscrypt.h> + +#include <gtest/gtest.h> + +using namespace android::fscrypt; + +/* modes not supported by upstream kernel, so not in <linux/fs.h> */ +#define FS_ENCRYPTION_MODE_AES_256_HEH 126 +#define FS_ENCRYPTION_MODE_PRIVATE 127 + +TEST(fscrypt, ParseOptions) { + EncryptionOptions options; + std::string options_string; + + EXPECT_FALSE(ParseOptions("", &options)); + EXPECT_FALSE(ParseOptions("blah", &options)); + + EXPECT_TRUE(ParseOptions("software", &options)); + EXPECT_EQ(1, options.version); + EXPECT_EQ(FS_ENCRYPTION_MODE_AES_256_XTS, options.contents_mode); + EXPECT_EQ(FS_ENCRYPTION_MODE_AES_256_CTS, options.filenames_mode); + EXPECT_EQ(FS_POLICY_FLAGS_PAD_4, options.flags); + EXPECT_TRUE(OptionsToString(options, &options_string)); + EXPECT_EQ("aes-256-xts:aes-256-cts:v1", options_string); + + EXPECT_TRUE(ParseOptions("aes-256-xts", &options)); + EXPECT_EQ(1, options.version); + EXPECT_EQ(FS_ENCRYPTION_MODE_AES_256_XTS, options.contents_mode); + EXPECT_EQ(FS_ENCRYPTION_MODE_AES_256_CTS, options.filenames_mode); + EXPECT_EQ(FS_POLICY_FLAGS_PAD_4, options.flags); + EXPECT_TRUE(OptionsToString(options, &options_string)); + EXPECT_EQ("aes-256-xts:aes-256-cts:v1", options_string); + + EXPECT_TRUE(ParseOptions("adiantum", &options)); + EXPECT_EQ(1, options.version); + EXPECT_EQ(FS_ENCRYPTION_MODE_ADIANTUM, options.contents_mode); + EXPECT_EQ(FS_ENCRYPTION_MODE_ADIANTUM, options.filenames_mode); + EXPECT_EQ(FS_POLICY_FLAGS_PAD_16 | FS_POLICY_FLAG_DIRECT_KEY, options.flags); + EXPECT_TRUE(OptionsToString(options, &options_string)); + EXPECT_EQ("adiantum:adiantum:v1", options_string); + + EXPECT_TRUE(ParseOptions("adiantum:aes-256-heh", &options)); + EXPECT_EQ(1, options.version); + EXPECT_EQ(FS_ENCRYPTION_MODE_ADIANTUM, options.contents_mode); + EXPECT_EQ(FS_ENCRYPTION_MODE_AES_256_HEH, options.filenames_mode); + EXPECT_EQ(FS_POLICY_FLAGS_PAD_16, options.flags); + EXPECT_TRUE(OptionsToString(options, &options_string)); + EXPECT_EQ("adiantum:aes-256-heh:v1", options_string); + + EXPECT_TRUE(ParseOptions("ice", &options)); + EXPECT_EQ(1, options.version); + EXPECT_EQ(FS_ENCRYPTION_MODE_PRIVATE, options.contents_mode); + EXPECT_EQ(FS_ENCRYPTION_MODE_AES_256_CTS, options.filenames_mode); + EXPECT_EQ(FS_POLICY_FLAGS_PAD_4, options.flags); + EXPECT_TRUE(OptionsToString(options, &options_string)); + EXPECT_EQ("ice:aes-256-cts:v1", options_string); + + EXPECT_FALSE(ParseOptions("ice:blah", &options)); + + EXPECT_TRUE(ParseOptions("ice:aes-256-cts", &options)); + EXPECT_EQ(1, options.version); + EXPECT_EQ(FS_ENCRYPTION_MODE_PRIVATE, options.contents_mode); + EXPECT_EQ(FS_ENCRYPTION_MODE_AES_256_CTS, options.filenames_mode); + EXPECT_EQ(FS_POLICY_FLAGS_PAD_4, options.flags); + EXPECT_TRUE(OptionsToString(options, &options_string)); + EXPECT_EQ("ice:aes-256-cts:v1", options_string); + + EXPECT_TRUE(ParseOptions("ice:aes-256-heh", &options)); + EXPECT_EQ(1, options.version); + EXPECT_EQ(FS_ENCRYPTION_MODE_PRIVATE, options.contents_mode); + EXPECT_EQ(FS_ENCRYPTION_MODE_AES_256_HEH, options.filenames_mode); + EXPECT_EQ(FS_POLICY_FLAGS_PAD_16, options.flags); + EXPECT_TRUE(OptionsToString(options, &options_string)); + EXPECT_EQ("ice:aes-256-heh:v1", options_string); + + EXPECT_TRUE(ParseOptions("ice:adiantum", &options)); + EXPECT_EQ(1, options.version); + EXPECT_EQ(FS_ENCRYPTION_MODE_PRIVATE, options.contents_mode); + EXPECT_EQ(FS_ENCRYPTION_MODE_ADIANTUM, options.filenames_mode); + EXPECT_EQ(FS_POLICY_FLAGS_PAD_16 | FS_POLICY_FLAG_DIRECT_KEY, options.flags); + EXPECT_TRUE(OptionsToString(options, &options_string)); + EXPECT_EQ("ice:adiantum:v1", options_string); + + EXPECT_TRUE(ParseOptions("aes-256-xts:aes-256-cts", &options)); + EXPECT_EQ(1, options.version); + EXPECT_EQ(FS_ENCRYPTION_MODE_AES_256_XTS, options.contents_mode); + EXPECT_EQ(FS_ENCRYPTION_MODE_AES_256_CTS, options.filenames_mode); + EXPECT_EQ(FS_POLICY_FLAGS_PAD_4, options.flags); + EXPECT_TRUE(OptionsToString(options, &options_string)); + EXPECT_EQ("aes-256-xts:aes-256-cts:v1", options_string); + + EXPECT_TRUE(ParseOptions("aes-256-xts:aes-256-cts:v1", &options)); + EXPECT_EQ(1, options.version); + EXPECT_EQ(FS_ENCRYPTION_MODE_AES_256_XTS, options.contents_mode); + EXPECT_EQ(FS_ENCRYPTION_MODE_AES_256_CTS, options.filenames_mode); + EXPECT_EQ(FS_POLICY_FLAGS_PAD_4, options.flags); + EXPECT_TRUE(OptionsToString(options, &options_string)); + EXPECT_EQ("aes-256-xts:aes-256-cts:v1", options_string); + + EXPECT_TRUE(ParseOptions("aes-256-xts:aes-256-cts:v2", &options)); + EXPECT_EQ(2, options.version); + EXPECT_EQ(FS_ENCRYPTION_MODE_AES_256_XTS, options.contents_mode); + EXPECT_EQ(FS_ENCRYPTION_MODE_AES_256_CTS, options.filenames_mode); + EXPECT_EQ(FS_POLICY_FLAGS_PAD_16, options.flags); + EXPECT_TRUE(OptionsToString(options, &options_string)); + EXPECT_EQ("aes-256-xts:aes-256-cts:v2", options_string); + + EXPECT_TRUE(ParseOptions("aes-256-xts:aes-256-cts:v2+inlinecrypt_optimized", &options)); + EXPECT_EQ(2, options.version); + EXPECT_EQ(FS_ENCRYPTION_MODE_AES_256_XTS, options.contents_mode); + EXPECT_EQ(FS_ENCRYPTION_MODE_AES_256_CTS, options.filenames_mode); + EXPECT_EQ(FS_POLICY_FLAGS_PAD_16 | FSCRYPT_POLICY_FLAG_IV_INO_LBLK_64, options.flags); + EXPECT_TRUE(OptionsToString(options, &options_string)); + EXPECT_EQ("aes-256-xts:aes-256-cts:v2+inlinecrypt_optimized", options_string); + + EXPECT_FALSE(ParseOptions("aes-256-xts:aes-256-cts:v2:", &options)); + EXPECT_FALSE(ParseOptions("aes-256-xts:aes-256-cts:v2:foo", &options)); + EXPECT_FALSE(ParseOptions("aes-256-xts:aes-256-cts:blah", &options)); + EXPECT_FALSE(ParseOptions("aes-256-xts:aes-256-cts:vblah", &options)); +} diff --git a/libjsonpb/verify/test.cpp b/libjsonpb/verify/test.cpp index 31a734ad..cb98f47f 100644 --- a/libjsonpb/verify/test.cpp +++ b/libjsonpb/verify/test.cpp @@ -287,7 +287,7 @@ static const std::vector<ScalarTestErrorParam> gScalarTestErrorParams = { {"{\"e\": 1}", "Should not allow integers for enums"}, }; -INSTANTIATE_TEST_SUITE_P(, ScalarTestError, +INSTANTIATE_TEST_SUITE_P(Jsonpb, ScalarTestError, ::testing::ValuesIn(gScalarTestErrorParams)); int main(int argc, char** argv) { diff --git a/libperfmgr/tools/ConfigVerifier.cc b/libperfmgr/tools/ConfigVerifier.cc index a08fd006..2e25e950 100644 --- a/libperfmgr/tools/ConfigVerifier.cc +++ b/libperfmgr/tools/ConfigVerifier.cc @@ -44,23 +44,6 @@ class NodeVerifier : public HintManager { return false; } - for (const auto& node : nodes) { - std::vector<std::string> values = node->GetValues(); - std::string default_value = values[node->GetDefaultIndex()]; - // Always set to default first - values.insert(values.begin(), default_value); - // And reset to default after test - values.push_back(default_value); - for (const auto& value : values) { - if (!android::base::WriteStringToFile(value, node->GetPath())) { - LOG(ERROR) << "Failed to write to node: " << node->GetPath() - << " with value: " << value; - return false; - } - LOG(VERBOSE) << "Wrote to node: " << node->GetPath() - << " with value: " << value; - } - } return true; } diff --git a/simpleperf/JITDebugReader.cpp b/simpleperf/JITDebugReader.cpp index 6de16aac..7ef3f25f 100644 --- a/simpleperf/JITDebugReader.cpp +++ b/simpleperf/JITDebugReader.cpp @@ -531,10 +531,10 @@ bool JITDebugReader::ReadNewCodeEntries(Process& process, const Descriptor& desc } if (descriptor.version == 2) { if (process.is_64bit) { - return ReadNewCodeEntriesImpl<JITCodeEntry64V2>( + return ReadNewCodeEntriesImplV2<JITCodeEntry64V2>( process, descriptor, last_action_timestamp, read_entry_limit, new_code_entries); } - return ReadNewCodeEntriesImpl<JITCodeEntry32V2>( + return ReadNewCodeEntriesImplV2<JITCodeEntry32V2>( process, descriptor, last_action_timestamp, read_entry_limit, new_code_entries); } return false; @@ -548,6 +548,7 @@ bool JITDebugReader::ReadNewCodeEntriesImpl(Process& process, const Descriptor& uint64_t current_entry_addr = descriptor.first_entry_addr; uint64_t prev_entry_addr = 0u; std::unordered_set<uint64_t> entry_addr_set; + for (size_t i = 0u; i < read_entry_limit && current_entry_addr != 0u; ++i) { if (entry_addr_set.find(current_entry_addr) != entry_addr_set.end()) { // We enter a loop, which means a broken linked list. @@ -566,15 +567,54 @@ bool JITDebugReader::ReadNewCodeEntriesImpl(Process& process, const Descriptor& // once we hit an entry with timestamp <= last_action_timestmap. break; } - if (entry.symfile_size == 0) { - continue; + if (entry.symfile_size > 0) { + CodeEntry code_entry; + code_entry.addr = current_entry_addr; + code_entry.symfile_addr = entry.symfile_addr; + code_entry.symfile_size = entry.symfile_size; + code_entry.timestamp = entry.register_timestamp; + new_code_entries->push_back(code_entry); + } + entry_addr_set.insert(current_entry_addr); + prev_entry_addr = current_entry_addr; + current_entry_addr = entry.next_addr; + } + return true; +} + +// Temporary work around for patch "JIT mini-debug-info: Append packed entries towards end.", which +// adds new entries at the end of the list and forces simpleperf to read the whole list. +template <typename CodeEntryT> +bool JITDebugReader::ReadNewCodeEntriesImplV2(Process& process, const Descriptor& descriptor, + uint64_t last_action_timestamp, + uint32_t /* read_entry_limit */, + std::vector<CodeEntry>* new_code_entries) { + uint64_t current_entry_addr = descriptor.first_entry_addr; + uint64_t prev_entry_addr = 0u; + std::unordered_set<uint64_t> entry_addr_set; + const size_t READ_ENTRY_LIMIT = 10000; // to avoid endless loop + + for (size_t i = 0u; i < READ_ENTRY_LIMIT && current_entry_addr != 0u; ++i) { + if (entry_addr_set.find(current_entry_addr) != entry_addr_set.end()) { + // We enter a loop, which means a broken linked list. + return false; + } + CodeEntryT entry; + if (!ReadRemoteMem(process, current_entry_addr, sizeof(entry), &entry)) { + return false; + } + if (entry.prev_addr != prev_entry_addr || !entry.Valid()) { + // A broken linked list + return false; + } + if (entry.symfile_size > 0 && entry.register_timestamp > last_action_timestamp) { + CodeEntry code_entry; + code_entry.addr = current_entry_addr; + code_entry.symfile_addr = entry.symfile_addr; + code_entry.symfile_size = entry.symfile_size; + code_entry.timestamp = entry.register_timestamp; + new_code_entries->push_back(code_entry); } - CodeEntry code_entry; - code_entry.addr = current_entry_addr; - code_entry.symfile_addr = entry.symfile_addr; - code_entry.symfile_size = entry.symfile_size; - code_entry.timestamp = entry.register_timestamp; - new_code_entries->push_back(code_entry); entry_addr_set.insert(current_entry_addr); prev_entry_addr = current_entry_addr; current_entry_addr = entry.next_addr; @@ -608,6 +648,9 @@ void JITDebugReader::ReadJITCodeDebugInfo(Process& process, tmp_file->DoNotRemove(); } auto callback = [&](const ElfFileSymbol& symbol) { + if (symbol.len == 0) { // Some arm labels can have zero length. + return; + } LOG(VERBOSE) << "JITSymbol " << symbol.name << " at [" << std::hex << symbol.vaddr << " - " << (symbol.vaddr + symbol.len) << " with size " << symbol.len; debug_info->emplace_back(process.pid, jit_entry.timestamp, symbol.vaddr, symbol.len, diff --git a/simpleperf/JITDebugReader.h b/simpleperf/JITDebugReader.h index 3be124e6..1a3b5e69 100644 --- a/simpleperf/JITDebugReader.h +++ b/simpleperf/JITDebugReader.h @@ -169,6 +169,10 @@ class JITDebugReader { bool ReadNewCodeEntriesImpl(Process& process, const Descriptor& descriptor, uint64_t last_action_timestamp, uint32_t read_entry_limit, std::vector<CodeEntry>* new_code_entries); + template <typename CodeEntryT> + bool ReadNewCodeEntriesImplV2(Process& process, const Descriptor& descriptor, + uint64_t last_action_timestamp, uint32_t read_entry_limit, + std::vector<CodeEntry>* new_code_entries); void ReadJITCodeDebugInfo(Process& process, const std::vector<CodeEntry>& jit_entries, std::vector<JITDebugInfo>* debug_info); diff --git a/simpleperf/cmd_record_test.cpp b/simpleperf/cmd_record_test.cpp index 82f494f8..184f9359 100644 --- a/simpleperf/cmd_record_test.cpp +++ b/simpleperf/cmd_record_test.cpp @@ -747,7 +747,7 @@ TEST(record_cmd, cpu_percent_option) { class RecordingAppHelper { public: bool InstallApk(const std::string& apk_path, const std::string& package_name) { - if (Workload::RunCmd({"pm", "install", "-t", apk_path})) { + if (Workload::RunCmd({"pm", "install", "-t", "--abi", GetABI(), apk_path})) { installed_packages_.emplace_back(package_name); return true; } @@ -785,6 +785,20 @@ class RecordingAppHelper { } private: + const char* GetABI() { +#if defined(__i386__) + return "x86"; +#elif defined(__x86_64__) + return "x86_64"; +#elif defined(__aarch64__) + return "arm64-v8a"; +#elif defined(__arm__) + return "armeabi-v7a"; +#else + #error "unrecognized ABI" +#endif + } + std::vector<std::string> installed_packages_; std::unique_ptr<Workload> app_start_proc_; TemporaryFile perf_data_file_; @@ -894,6 +908,13 @@ TEST(record_cmd, cs_etm_event) { ASSERT_TRUE(RunRecordCmd({"-e", "cs-etm"}, tmpfile.path)); std::unique_ptr<RecordFileReader> reader = RecordFileReader::CreateInstance(tmpfile.path); ASSERT_TRUE(reader); + + // cs-etm uses sample period instead of sample freq. + ASSERT_EQ(reader->AttrSection().size(), 1u); + const perf_event_attr* attr = reader->AttrSection()[0].attr; + ASSERT_EQ(attr->freq, 0); + ASSERT_EQ(attr->sample_period, 1); + bool has_auxtrace_info = false; bool has_auxtrace = false; bool has_aux = false; diff --git a/simpleperf/command.cpp b/simpleperf/command.cpp index d751d610..e937e19b 100644 --- a/simpleperf/command.cpp +++ b/simpleperf/command.cpp @@ -24,7 +24,6 @@ #include <android-base/logging.h> #include <android-base/parsedouble.h> #include <android-base/parseint.h> -#include <android-base/quick_exit.h> #include "utils.h" @@ -177,9 +176,9 @@ bool RunSimpleperfCmd(int argc, char** argv) { bool result = command->Run(args); LOG(DEBUG) << "command '" << command_name << "' " << (result ? "finished successfully" : "failed"); - // Quick exit to avoid cost freeing memory and closing files. + // Quick exit to avoid the cost of freeing memory and closing files. fflush(stdout); fflush(stderr); - android::base::quick_exit(result ? 0 : 1); + _Exit(result ? 0 : 1); return result; } diff --git a/simpleperf/event_selection_set.cpp b/simpleperf/event_selection_set.cpp index e1cba8cc..c7c7a38a 100644 --- a/simpleperf/event_selection_set.cpp +++ b/simpleperf/event_selection_set.cpp @@ -174,6 +174,11 @@ bool EventSelectionSet::BuildAndCheckEventSelection(const std::string& event_nam if (event_type->event_type.type == PERF_TYPE_TRACEPOINT) { selection->event_attr.freq = 0; selection->event_attr.sample_period = DEFAULT_SAMPLE_PERIOD_FOR_TRACEPOINT_EVENT; + } else if (IsEtmEventType(event_type->event_type.type)) { + // ETM recording has no sample frequency to adjust. Using sample frequency only wastes time + // enabling/disabling etm devices. So don't adjust frequency by default. + selection->event_attr.freq = 0; + selection->event_attr.sample_period = 1; } else { selection->event_attr.freq = 1; // Set default sample freq here may print msg "Adjust sample freq to max allowed sample diff --git a/simpleperf/scripts/bin/android/arm/simpleperf b/simpleperf/scripts/bin/android/arm/simpleperf Binary files differindex 81aa45c8..0bbb47fc 100755 --- a/simpleperf/scripts/bin/android/arm/simpleperf +++ b/simpleperf/scripts/bin/android/arm/simpleperf diff --git a/simpleperf/scripts/bin/android/arm64/simpleperf b/simpleperf/scripts/bin/android/arm64/simpleperf Binary files differindex 21fab083..72d505ce 100755 --- a/simpleperf/scripts/bin/android/arm64/simpleperf +++ b/simpleperf/scripts/bin/android/arm64/simpleperf diff --git a/simpleperf/scripts/bin/android/x86/simpleperf b/simpleperf/scripts/bin/android/x86/simpleperf Binary files differindex afc8ae5d..8de146d3 100755 --- a/simpleperf/scripts/bin/android/x86/simpleperf +++ b/simpleperf/scripts/bin/android/x86/simpleperf diff --git a/simpleperf/scripts/bin/android/x86_64/simpleperf b/simpleperf/scripts/bin/android/x86_64/simpleperf Binary files differindex bb32c9f8..5cd23524 100755 --- a/simpleperf/scripts/bin/android/x86_64/simpleperf +++ b/simpleperf/scripts/bin/android/x86_64/simpleperf diff --git a/simpleperf/scripts/bin/darwin/x86_64/libsimpleperf_report.dylib b/simpleperf/scripts/bin/darwin/x86_64/libsimpleperf_report.dylib Binary files differindex cb0121c1..207eefe6 100755 --- a/simpleperf/scripts/bin/darwin/x86_64/libsimpleperf_report.dylib +++ b/simpleperf/scripts/bin/darwin/x86_64/libsimpleperf_report.dylib diff --git a/simpleperf/scripts/bin/darwin/x86_64/simpleperf b/simpleperf/scripts/bin/darwin/x86_64/simpleperf Binary files differindex 552cf259..c3f85c03 100755 --- a/simpleperf/scripts/bin/darwin/x86_64/simpleperf +++ b/simpleperf/scripts/bin/darwin/x86_64/simpleperf diff --git a/simpleperf/scripts/bin/linux/x86_64/libsimpleperf_report.so b/simpleperf/scripts/bin/linux/x86_64/libsimpleperf_report.so Binary files differindex 751a9ce1..fc48d068 100755 --- a/simpleperf/scripts/bin/linux/x86_64/libsimpleperf_report.so +++ b/simpleperf/scripts/bin/linux/x86_64/libsimpleperf_report.so diff --git a/simpleperf/scripts/bin/linux/x86_64/simpleperf b/simpleperf/scripts/bin/linux/x86_64/simpleperf Binary files differindex 6a7b3a34..8ae69262 100755 --- a/simpleperf/scripts/bin/linux/x86_64/simpleperf +++ b/simpleperf/scripts/bin/linux/x86_64/simpleperf diff --git a/simpleperf/scripts/bin/windows/x86/libsimpleperf_report.dll b/simpleperf/scripts/bin/windows/x86/libsimpleperf_report.dll Binary files differindex 5ef46de0..24e3129c 100755 --- a/simpleperf/scripts/bin/windows/x86/libsimpleperf_report.dll +++ b/simpleperf/scripts/bin/windows/x86/libsimpleperf_report.dll diff --git a/simpleperf/scripts/bin/windows/x86/libwinpthread-1.dll b/simpleperf/scripts/bin/windows/x86/libwinpthread-1.dll Binary files differindex 86de5d6e..a41127ab 100755 --- a/simpleperf/scripts/bin/windows/x86/libwinpthread-1.dll +++ b/simpleperf/scripts/bin/windows/x86/libwinpthread-1.dll diff --git a/simpleperf/scripts/bin/windows/x86/simpleperf.exe b/simpleperf/scripts/bin/windows/x86/simpleperf.exe Binary files differindex 2b26c7a9..c8e4e14a 100755 --- a/simpleperf/scripts/bin/windows/x86/simpleperf.exe +++ b/simpleperf/scripts/bin/windows/x86/simpleperf.exe diff --git a/simpleperf/scripts/bin/windows/x86_64/libsimpleperf_report.dll b/simpleperf/scripts/bin/windows/x86_64/libsimpleperf_report.dll Binary files differindex 7ba20159..a55350b3 100755 --- a/simpleperf/scripts/bin/windows/x86_64/libsimpleperf_report.dll +++ b/simpleperf/scripts/bin/windows/x86_64/libsimpleperf_report.dll diff --git a/simpleperf/scripts/bin/windows/x86_64/libwinpthread-1.dll b/simpleperf/scripts/bin/windows/x86_64/libwinpthread-1.dll Binary files differindex ee5d7a3c..5a12ce3c 100755 --- a/simpleperf/scripts/bin/windows/x86_64/libwinpthread-1.dll +++ b/simpleperf/scripts/bin/windows/x86_64/libwinpthread-1.dll diff --git a/simpleperf/scripts/bin/windows/x86_64/simpleperf.exe b/simpleperf/scripts/bin/windows/x86_64/simpleperf.exe Binary files differindex f5d10a4e..d221976a 100755 --- a/simpleperf/scripts/bin/windows/x86_64/simpleperf.exe +++ b/simpleperf/scripts/bin/windows/x86_64/simpleperf.exe diff --git a/simpleperf/scripts/script_testdata/cpp_api-profile_Q.apk b/simpleperf/scripts/script_testdata/cpp_api-profile_Q.apk Binary files differindex b3f4e7d8..746b86cc 100644 --- a/simpleperf/scripts/script_testdata/cpp_api-profile_Q.apk +++ b/simpleperf/scripts/script_testdata/cpp_api-profile_Q.apk diff --git a/simpleperf/scripts/script_testdata/cpp_api-profile_prev_Q.apk b/simpleperf/scripts/script_testdata/cpp_api-profile_prev_Q.apk Binary files differindex 33d78806..9718824e 100644 --- a/simpleperf/scripts/script_testdata/cpp_api-profile_prev_Q.apk +++ b/simpleperf/scripts/script_testdata/cpp_api-profile_prev_Q.apk diff --git a/simpleperf/scripts/test.py b/simpleperf/scripts/test.py index 2adee0e0..b60d54d6 100755 --- a/simpleperf/scripts/test.py +++ b/simpleperf/scripts/test.py @@ -102,51 +102,97 @@ class TestLogger(object): TEST_LOGGER = TestLogger() -def get_device_features(): - adb = AdbHelper() - adb.check_run_and_return_output(['push', - 'bin/android/%s/simpleperf' % adb.get_device_arch(), - '/data/local/tmp']) - adb.check_run_and_return_output(['shell', 'chmod', 'a+x', '/data/local/tmp/simpleperf']) - return adb.check_run_and_return_output(['shell', '/data/local/tmp/simpleperf', 'list', - '--show-features']) - -def is_trace_offcpu_supported(): - if not hasattr(is_trace_offcpu_supported, 'value'): - is_trace_offcpu_supported.value = 'trace-offcpu' in get_device_features() - return is_trace_offcpu_supported.value - - -def android_version(): - """ Get Android version on device, like 7 is for Android N, 8 is for Android O.""" - if not hasattr(android_version, 'value'): - android_version.value = AdbHelper().get_android_version() - return android_version.value - - -def build_testdata(): - """ Collect testdata from ../testdata and ../demo. """ - from_testdata_path = os.path.join('..', 'testdata') - from_demo_path = os.path.join('..', 'demo') - from_script_testdata_path = 'script_testdata' - if (not os.path.isdir(from_testdata_path) or not os.path.isdir(from_demo_path) or - not from_script_testdata_path): - return - copy_demo_list = ['SimpleperfExamplePureJava', 'SimpleperfExampleWithNative', - 'SimpleperfExampleOfKotlin'] - - testdata_path = "testdata" - remove(testdata_path) - shutil.copytree(from_testdata_path, testdata_path) - for demo in copy_demo_list: - shutil.copytree(os.path.join(from_demo_path, demo), os.path.join(testdata_path, demo)) - for f in os.listdir(from_script_testdata_path): - shutil.copy(os.path.join(from_script_testdata_path, f), testdata_path) +class TestHelper(object): + """ Keep global test info. """ + + def __init__(self): + self.python_version = 3 if is_python3() else 2 + self.repeat_count = 0 + self.script_dir = os.path.abspath(get_script_dir()) + self.cur_dir = os.getcwd() + self.testdata_dir = os.path.join(self.cur_dir, 'testdata') + self.test_base_dir = self.get_test_base_dir(self.python_version) + self.adb = AdbHelper(enable_switch_to_root=True) + self.android_version = self.adb.get_android_version() + self.device_features = None + self.browser_option = [] + + def get_test_base_dir(self, python_version): + """ Return the dir of generated data for a python version. """ + return os.path.join(self.cur_dir, 'test_python_%d' % python_version) + + def testdata_path(self, testdata_name): + """ Return the path of a test data. """ + return os.path.join(self.testdata_dir, testdata_name.replace('/', os.sep)) + + def test_dir(self, test_name): + """ Return the dir to run a test. """ + return os.path.join( + self.test_base_dir, 'repeat_%d' % TEST_HELPER.repeat_count, test_name) + + def script_path(self, script_name): + """ Return the dir of python scripts. """ + return os.path.join(self.script_dir, script_name) + + def get_device_features(self): + if self.device_features is None: + args = [sys.executable, self.script_path( + 'run_simpleperf_on_device.py'), 'list', '--show-features'] + output = subprocess.check_output(args, stderr=TEST_LOGGER.log_fh) + output = bytes_to_str(output) + self.device_features = output.split() + return self.device_features + + def is_trace_offcpu_supported(self): + return 'trace-offcpu' in self.get_device_features() + + def build_testdata(self): + """ Collect testdata in self.testdata_dir. + In system/extras/simpleperf/scripts, testdata comes from: + <script_dir>/../testdata, <script_dir>/script_testdata, <script_dir>/../demo + In prebuilts/simpleperf, testdata comes from: + <script_dir>/testdata + """ + if os.path.isdir(self.testdata_dir): + return # already built + os.makedirs(self.testdata_dir) + + source_dirs = [os.path.join('..', 'testdata'), 'script_testdata', + os.path.join('..', 'demo'), 'testdata'] + source_dirs = [os.path.join(self.script_dir, x) for x in source_dirs] + source_dirs = [x for x in source_dirs if os.path.isdir(x)] + + for source_dir in source_dirs: + for name in os.listdir(source_dir): + source = os.path.join(source_dir, name) + target = os.path.join(self.testdata_dir, name) + if os.path.exists(target): + continue + if os.path.isfile(source): + shutil.copyfile(source, target) + elif os.path.isdir(source): + shutil.copytree(source, target) + + def get_32bit_abi(self): + return self.adb.get_property('ro.product.cpu.abilist32').strip().split(',')[0] + + +TEST_HELPER = TestHelper() + class TestBase(unittest.TestCase): + def setUp(self): + """ Run each test in a separate dir. """ + self.test_dir = TEST_HELPER.test_dir('%s.%s' % ( + self.__class__.__name__, self._testMethodName)) + os.makedirs(self.test_dir) + os.chdir(self.test_dir) + def run_cmd(self, args, return_output=False): + if args[0] == 'report_html.py' or args[0] == INFERNO_SCRIPT: + args += TEST_HELPER.browser_option if args[0].endswith('.py'): - args = [sys.executable] + args + args = [sys.executable, TEST_HELPER.script_path(args[0])] + args[1:] use_shell = args[0].endswith('.bat') try: if not return_output: @@ -184,7 +230,7 @@ class TestExampleBase(TestBase): @classmethod def prepare(cls, example_name, package_name, activity_name, abi=None, adb_root=False): cls.adb = AdbHelper(enable_switch_to_root=adb_root) - cls.example_path = os.path.join("testdata", example_name) + cls.example_path = TEST_HELPER.testdata_path(example_name) if not os.path.isdir(cls.example_path): log_fatal("can't find " + cls.example_path) for root, _, files in os.walk(cls.example_path): @@ -195,64 +241,49 @@ class TestExampleBase(TestBase): log_fatal("can't find app-profiling.apk under " + cls.example_path) cls.package_name = package_name cls.activity_name = activity_name - cls.abi = "arm64" - if abi and abi != "arm64" and abi.find("arm") != -1: - cls.abi = "arm" args = ["install", "-r"] if abi: args += ["--abi", abi] args.append(cls.apk_path) cls.adb.check_run(args) cls.adb_root = adb_root - cls.compiled = False cls.has_perf_data_for_report = False # On Android >= P (version 9), we can profile JITed and interpreted Java code. # So only compile Java code on Android <= O (version 8). - cls.use_compiled_java_code = android_version() <= 8 - - def setUp(self): - if self.id().find('TraceOffCpu') != -1 and not is_trace_offcpu_supported(): - self.skipTest('trace-offcpu is not supported on device') - cls = self.__class__ - if not cls.has_perf_data_for_report: - cls.has_perf_data_for_report = True - self.run_app_profiler() - shutil.copy('perf.data', 'perf.data_for_report') - remove('binary_cache_for_report') - shutil.copytree('binary_cache', 'binary_cache_for_report') - else: - shutil.copy('perf.data_for_report', 'perf.data') - remove('binary_cache') - shutil.copytree('binary_cache_for_report', 'binary_cache') + cls.use_compiled_java_code = TEST_HELPER.android_version <= 8 + cls.testcase_dir = TEST_HELPER.test_dir(cls.__name__) @classmethod def tearDownClass(cls): - if hasattr(cls, 'test_result') and cls.test_result and not cls.test_result.wasSuccessful(): - return + remove(cls.testcase_dir) if hasattr(cls, 'package_name'): cls.adb.check_run(["uninstall", cls.package_name]) - remove("binary_cache") - remove("annotated_files") - remove("perf.data") - remove("report.txt") - remove("pprof.profile") - if cls.has_perf_data_for_report: - cls.has_perf_data_for_report = False - remove('perf.data_for_report') - remove('binary_cache_for_report') + + def setUp(self): + super(TestExampleBase, self).setUp() + if 'TraceOffCpu' in self.id() and not TEST_HELPER.is_trace_offcpu_supported(): + self.skipTest('trace-offcpu is not supported on device') + # Use testcase_dir to share a common perf.data for reporting. So we don't need to + # generate it for each test. + if not os.path.isdir(self.testcase_dir): + os.makedirs(self.testcase_dir) + os.chdir(self.testcase_dir) + self.run_app_profiler(compile_java_code=self.use_compiled_java_code) + remove(self.test_dir) + shutil.copytree(self.testcase_dir, self.test_dir) + os.chdir(self.test_dir) def run(self, result=None): self.__class__.test_result = result super(TestExampleBase, self).run(result) def run_app_profiler(self, record_arg="-g --duration 10", build_binary_cache=True, - start_activity=True): + start_activity=True, compile_java_code=False): args = ['app_profiler.py', '--app', self.package_name, '-r', record_arg, '-o', 'perf.data'] if not build_binary_cache: args.append("-nb") - if self.use_compiled_java_code and not self.__class__.compiled: + if compile_java_code: args.append('--compile_java_code') - self.__class__.compiled = True if start_activity: args += ["-a", self.activity_name] args += ["-lib", self.example_path] @@ -384,7 +415,6 @@ class TestExampleBase(TestBase): shutil.move('perf.data', 'perf2.data') self.run_app_profiler(record_arg='-g -f 1000 --duration 3 -e task-clock:u') self.run_cmd(['report_html.py', '-i', 'perf.data', 'perf2.data']) - remove('perf2.data') class TestExamplePureJava(TestExampleBase): @@ -419,8 +449,8 @@ class TestExamplePureJava(TestExampleBase): return self.adb.check_run(['shell', 'am', 'start', '-n', self.package_name + '/.MainActivity']) time.sleep(1) - args = [sys.executable, "app_profiler.py", "--app", self.package_name, - "-r", "--duration 10000", "--disable_adb_root"] + args = [sys.executable, TEST_HELPER.script_path("app_profiler.py"), + "--app", self.package_name, "-r", "--duration 10000", "--disable_adb_root"] subproc = subprocess.Popen(args) time.sleep(3) @@ -432,8 +462,9 @@ class TestExamplePureJava(TestExampleBase): def test_app_profiler_stop_after_app_exit(self): self.adb.check_run(['shell', 'am', 'start', '-n', self.package_name + '/.MainActivity']) time.sleep(1) - subproc = subprocess.Popen([sys.executable, 'app_profiler.py', '--app', self.package_name, - '-r', '--duration 10000', '--disable_adb_root']) + subproc = subprocess.Popen( + [sys.executable, TEST_HELPER.script_path('app_profiler.py'), + '--app', self.package_name, '-r', '--duration 10000', '--disable_adb_root']) time.sleep(3) self.adb.check_run(['shell', 'am', 'force-stop', self.package_name]) subproc.wait() @@ -502,20 +533,15 @@ class TestExamplePureJava(TestExampleBase): self.check_inferno_report_html( [('com.example.simpleperf.simpleperfexamplepurejava.MainActivity$1.run', 80)], "report2.html") - remove("report2.html") def test_inferno_in_another_dir(self): test_dir = 'inferno_testdir' - saved_dir = os.getcwd() - remove(test_dir) os.mkdir(test_dir) os.chdir(test_dir) - self.run_cmd(['python', os.path.join(saved_dir, 'app_profiler.py'), - '--app', self.package_name, '-r', '-e task-clock:u -g --duration 3']) + self.run_cmd(['app_profiler.py', '--app', self.package_name, + '-r', '-e task-clock:u -g --duration 3']) self.check_exist(filename="perf.data") self.run_cmd([INFERNO_SCRIPT, "-sc"]) - os.chdir(saved_dir) - remove(test_dir) def test_report_html(self): self.common_test_report_html() @@ -711,32 +737,32 @@ class TestExampleWithNativeJniCall(TestExampleBase): self.run_cmd([INFERNO_SCRIPT, "-sc"]) -class TestExampleWithNativeForceArm(TestExampleWithNative): +class TestExampleWithNativeForce32Bit(TestExampleWithNative): @classmethod def setUpClass(cls): cls.prepare("SimpleperfExampleWithNative", "com.example.simpleperf.simpleperfexamplewithnative", ".MainActivity", - abi="armeabi-v7a") + abi=TEST_HELPER.get_32bit_abi()) -class TestExampleWithNativeForceArmRoot(TestExampleWithNativeRoot): +class TestExampleWithNativeRootForce32Bit(TestExampleWithNativeRoot): @classmethod def setUpClass(cls): cls.prepare("SimpleperfExampleWithNative", "com.example.simpleperf.simpleperfexamplewithnative", ".MainActivity", - abi="armeabi-v7a", + abi=TEST_HELPER.get_32bit_abi(), adb_root=False) -class TestExampleWithNativeTraceOffCpuForceArm(TestExampleWithNativeTraceOffCpu): +class TestExampleWithNativeTraceOffCpuForce32Bit(TestExampleWithNativeTraceOffCpu): @classmethod def setUpClass(cls): cls.prepare("SimpleperfExampleWithNative", "com.example.simpleperf.simpleperfexamplewithnative", ".SleepActivity", - abi="armeabi-v7a") + abi=TEST_HELPER.get_32bit_abi()) class TestExampleOfKotlin(TestExampleBase): @@ -855,8 +881,8 @@ class TestExampleOfKotlinTraceOffCpu(TestExampleBase): class TestNativeProfiling(TestBase): def setUp(self): - self.adb = AdbHelper() - self.is_rooted_device = self.adb.switch_to_root() + super(TestNativeProfiling, self).setUp() + self.is_rooted_device = TEST_HELPER.adb.switch_to_root() def test_profile_cmd(self): self.run_cmd(["app_profiler.py", "-cmd", "pm -l", "--disable_adb_root"]) @@ -873,7 +899,7 @@ class TestNativeProfiling(TestBase): def test_profile_pids(self): if not self.is_rooted_device: return - pid = int(self.adb.check_run_and_return_output(['shell', 'pidof', 'system_server'])) + pid = int(TEST_HELPER.adb.check_run_and_return_output(['shell', 'pidof', 'system_server'])) self.run_cmd(['app_profiler.py', '--pid', str(pid), '-r', '--duration 1']) self.run_cmd(['app_profiler.py', '--pid', str(pid), str(pid), '-r', '--duration 1']) self.run_cmd(['app_profiler.py', '--tid', str(pid), '-r', '--duration 1']) @@ -886,13 +912,15 @@ class TestNativeProfiling(TestBase): self.run_cmd(['app_profiler.py', '--system_wide', '-r', '--duration 1']) -class TestReportLib(unittest.TestCase): +class TestReportLib(TestBase): def setUp(self): + super(TestReportLib, self).setUp() self.report_lib = ReportLib() - self.report_lib.SetRecordFile(os.path.join('testdata', 'perf_with_symbols.data')) + self.report_lib.SetRecordFile(TEST_HELPER.testdata_path('perf_with_symbols.data')) def tearDown(self): self.report_lib.Close() + super(TestReportLib, self).tearDown() def test_build_id(self): build_id = self.report_lib.GetBuildIdForPath('/data/t2') @@ -926,7 +954,7 @@ class TestReportLib(unittest.TestCase): self.assertTrue(found_sample) def test_meta_info(self): - self.report_lib.SetRecordFile(os.path.join('testdata', 'perf_with_trace_offcpu.data')) + self.report_lib.SetRecordFile(TEST_HELPER.testdata_path('perf_with_trace_offcpu.data')) meta_info = self.report_lib.MetaInfo() self.assertTrue("simpleperf_version" in meta_info) self.assertEqual(meta_info["system_wide_collection"], "false") @@ -935,7 +963,7 @@ class TestReportLib(unittest.TestCase): self.assertTrue("product_props" in meta_info) def test_event_name_from_meta_info(self): - self.report_lib.SetRecordFile(os.path.join('testdata', 'perf_with_tracepoint_event.data')) + self.report_lib.SetRecordFile(TEST_HELPER.testdata_path('perf_with_tracepoint_event.data')) event_names = set() while self.report_lib.GetNextSample(): event_names.add(self.report_lib.GetEventOfCurrentSample().name) @@ -943,13 +971,13 @@ class TestReportLib(unittest.TestCase): self.assertTrue('cpu-cycles' in event_names) def test_record_cmd(self): - self.report_lib.SetRecordFile(os.path.join('testdata', 'perf_with_trace_offcpu.data')) + self.report_lib.SetRecordFile(TEST_HELPER.testdata_path('perf_with_trace_offcpu.data')) self.assertEqual(self.report_lib.GetRecordCmd(), "/data/local/tmp/simpleperf record --trace-offcpu --duration 2 -g " + "./simpleperf_runtest_run_and_sleep64") def test_offcpu(self): - self.report_lib.SetRecordFile(os.path.join('testdata', 'perf_with_trace_offcpu.data')) + self.report_lib.SetRecordFile(TEST_HELPER.testdata_path('perf_with_trace_offcpu.data')) total_period = 0 sleep_function_period = 0 sleep_function_name = "SleepFunction(unsigned long long)" @@ -970,7 +998,7 @@ class TestReportLib(unittest.TestCase): def test_show_art_frames(self): def has_art_frame(report_lib): - report_lib.SetRecordFile(os.path.join('testdata', 'perf_with_interpreter_frames.data')) + report_lib.SetRecordFile(TEST_HELPER.testdata_path('perf_with_interpreter_frames.data')) result = False while report_lib.GetNextSample(): callchain = report_lib.GetCallChainOfCurrentSample() @@ -993,7 +1021,7 @@ class TestReportLib(unittest.TestCase): def test_merge_java_methods(self): def parse_dso_names(report_lib): dso_names = set() - report_lib.SetRecordFile(os.path.join('testdata', 'perf_with_interpreter_frames.data')) + report_lib.SetRecordFile(TEST_HELPER.testdata_path('perf_with_interpreter_frames.data')) while report_lib.GetNextSample(): dso_names.add(report_lib.GetSymbolOfCurrentSample().dso_name) callchain = report_lib.GetCallChainOfCurrentSample() @@ -1016,7 +1044,7 @@ class TestReportLib(unittest.TestCase): self.assertEqual(parse_dso_names(report_lib), (True, False)) def test_tracing_data(self): - self.report_lib.SetRecordFile(os.path.join('testdata', 'perf_with_tracepoint_event.data')) + self.report_lib.SetRecordFile(TEST_HELPER.testdata_path('perf_with_tracepoint_event.data')) has_tracing_data = False while self.report_lib.GetNextSample(): event = self.report_lib.GetEventOfCurrentSample() @@ -1037,13 +1065,13 @@ class TestRunSimpleperfOnDevice(TestBase): self.run_cmd(['run_simpleperf_on_device.py', 'list', '--show-features']) -class TestTools(unittest.TestCase): +class TestTools(TestBase): def test_addr2nearestline(self): self.run_addr2nearestline_test(True) self.run_addr2nearestline_test(False) def run_addr2nearestline_test(self, with_function_name): - binary_cache_path = 'testdata' + binary_cache_path = TEST_HELPER.testdata_dir test_map = { '/simpleperf_runtest_two_functions_arm64': [ { @@ -1083,7 +1111,7 @@ class TestTools(unittest.TestCase): { 'func_addr': 0x840, 'addr': 0x840, - 'source': 'system/extras/simpleperf/runtest/two_functions.cpp:6', + 'source': 'system/extras/simpleperf/runtest/two_functions.cpp:7', 'function': 'Function1()', }, { @@ -1145,7 +1173,7 @@ class TestTools(unittest.TestCase): self.assertEqual(source[2], expected_functions[i]) def test_objdump(self): - binary_cache_path = 'testdata' + binary_cache_path = TEST_HELPER.testdata_dir test_map = { '/simpleperf_runtest_two_functions_arm64': { 'start_addr': 0x668, @@ -1196,7 +1224,7 @@ class TestTools(unittest.TestCase): def test_readelf(self): test_map = { - '/simpleperf_runtest_two_functions_arm64': { + 'simpleperf_runtest_two_functions_arm64': { 'arch': 'arm64', 'build_id': '0xe8ecb3916d989dbdc068345c30f0c24300000000', 'sections': ['.interp', '.note.android.ident', '.note.gnu.build-id', '.dynsym', @@ -1208,21 +1236,21 @@ class TestTools(unittest.TestCase): '.debug_pubnames', '.debug_pubtypes', '.debug_line', '.note.gnu.gold-version', '.symtab', '.strtab', '.shstrtab'], }, - '/simpleperf_runtest_two_functions_arm': { + 'simpleperf_runtest_two_functions_arm': { 'arch': 'arm', 'build_id': '0x718f5b36c4148ee1bd3f51af89ed2be600000000', }, - '/simpleperf_runtest_two_functions_x86_64': { + 'simpleperf_runtest_two_functions_x86_64': { 'arch': 'x86_64', }, - '/simpleperf_runtest_two_functions_x86': { + 'simpleperf_runtest_two_functions_x86': { 'arch': 'x86', } } readelf = ReadElf(None) for dso_path in test_map: dso_info = test_map[dso_path] - path = 'testdata' + dso_path + path = os.path.join(TEST_HELPER.testdata_dir, dso_path) self.assertEqual(dso_info['arch'], readelf.get_arch(path)) if 'build_id' in dso_info: self.assertEqual(dso_info['build_id'], readelf.get_build_id(path)) @@ -1233,31 +1261,33 @@ class TestTools(unittest.TestCase): self.assertEqual(readelf.get_sections('not_exist_file'), []) def test_source_file_searcher(self): - searcher = SourceFileSearcher(['testdata']) + searcher = SourceFileSearcher( + [TEST_HELPER.testdata_path('SimpleperfExampleWithNative'), + TEST_HELPER.testdata_path('SimpleperfExampleOfKotlin')]) def format_path(path): - return path.replace('/', os.sep) + return os.path.join(TEST_HELPER.testdata_dir, path.replace('/', os.sep)) # Find a C++ file with pure file name. self.assertEqual( - format_path('testdata/SimpleperfExampleWithNative/app/src/main/cpp/native-lib.cpp'), + format_path('SimpleperfExampleWithNative/app/src/main/cpp/native-lib.cpp'), searcher.get_real_path('native-lib.cpp')) # Find a C++ file with an absolute file path. self.assertEqual( - format_path('testdata/SimpleperfExampleWithNative/app/src/main/cpp/native-lib.cpp'), + format_path('SimpleperfExampleWithNative/app/src/main/cpp/native-lib.cpp'), searcher.get_real_path('/data/native-lib.cpp')) # Find a Java file. self.assertEqual( - format_path('testdata/SimpleperfExampleWithNative/app/src/main/java/com/example/' + + format_path('SimpleperfExampleWithNative/app/src/main/java/com/example/' + 'simpleperf/simpleperfexamplewithnative/MainActivity.java'), searcher.get_real_path('simpleperfexamplewithnative/MainActivity.java')) # Find a Kotlin file. self.assertEqual( - format_path('testdata/SimpleperfExampleOfKotlin/app/src/main/java/com/example/' + + format_path('SimpleperfExampleOfKotlin/app/src/main/java/com/example/' + 'simpleperf/simpleperfexampleofkotlin/MainActivity.kt'), searcher.get_real_path('MainActivity.kt')) def test_is_elf_file(self): - self.assertTrue(is_elf_file(os.path.join( - 'testdata', 'simpleperf_runtest_two_functions_arm'))) + self.assertTrue(is_elf_file(TEST_HELPER.testdata_path( + 'simpleperf_runtest_two_functions_arm'))) with open('not_elf', 'wb') as fh: fh.write(b'\x90123') try: @@ -1266,13 +1296,15 @@ class TestTools(unittest.TestCase): remove('not_elf') -class TestNativeLibDownloader(unittest.TestCase): +class TestNativeLibDownloader(TestBase): def setUp(self): - self.adb = AdbHelper() + super(TestNativeLibDownloader, self).setUp() + self.adb = TEST_HELPER.adb self.adb.check_run(['shell', 'rm', '-rf', '/data/local/tmp/native_libs']) def tearDown(self): self.adb.check_run(['shell', 'rm', '-rf', '/data/local/tmp/native_libs']) + super(TestNativeLibDownloader, self).tearDown() def test_smoke(self): def is_lib_on_device(path): @@ -1280,9 +1312,8 @@ class TestNativeLibDownloader(unittest.TestCase): # Sync all native libs on device. downloader = NativeLibDownloader(None, 'arm64', self.adb) - downloader.collect_native_libs_on_host(os.path.join( - 'testdata', 'SimpleperfExampleWithNative', 'app', 'build', 'intermediates', 'cmake', - 'profiling')) + downloader.collect_native_libs_on_host(TEST_HELPER.testdata_path( + 'SimpleperfExampleWithNative/app/build/intermediates/cmake/profiling')) self.assertEqual(len(downloader.host_build_id_map), 2) for entry in downloader.host_build_id_map.values(): self.assertEqual(entry.score, 3) @@ -1330,15 +1361,15 @@ class TestNativeLibDownloader(unittest.TestCase): class TestReportHtml(TestBase): def test_long_callchain(self): self.run_cmd(['report_html.py', '-i', - os.path.join('testdata', 'perf_with_long_callchain.data')]) + TEST_HELPER.testdata_path('perf_with_long_callchain.data')]) def test_aggregated_by_thread_name(self): # Calculate event_count for each thread name before aggregation. event_count_for_thread_name = collections.defaultdict(lambda: 0) # use "--min_func_percent 0" to avoid cutting any thread. self.run_cmd(['report_html.py', '--min_func_percent', '0', '-i', - os.path.join('testdata', 'aggregatable_perf1.data'), - os.path.join('testdata', 'aggregatable_perf2.data')]) + TEST_HELPER.testdata_path('aggregatable_perf1.data'), + TEST_HELPER.testdata_path('aggregatable_perf2.data')]) record_data = self._load_record_data_in_html('report.html') event = record_data['sampleInfo'][0] for process in event['processes']: @@ -1349,8 +1380,8 @@ class TestReportHtml(TestBase): # Check event count for each thread after aggregation. self.run_cmd(['report_html.py', '--aggregate-by-thread-name', '--min_func_percent', '0', '-i', - os.path.join('testdata', 'aggregatable_perf1.data'), - os.path.join('testdata', 'aggregatable_perf2.data')]) + TEST_HELPER.testdata_path('aggregatable_perf1.data'), + TEST_HELPER.testdata_path('aggregatable_perf2.data')]) record_data = self._load_record_data_in_html('report.html') event = record_data['sampleInfo'][0] hit_count = 0 @@ -1364,7 +1395,7 @@ class TestReportHtml(TestBase): def test_no_empty_process(self): """ Test not showing a process having no threads. """ - perf_data = os.path.join('testdata', 'two_process_perf.data') + perf_data = TEST_HELPER.testdata_path('two_process_perf.data') self.run_cmd(['report_html.py', '-i', perf_data]) record_data = self._load_record_data_in_html('report.html') processes = record_data['sampleInfo'][0]['processes'] @@ -1398,11 +1429,11 @@ class TestBinaryCacheBuilder(TestBase): readelf = ReadElf(None) strip = find_tool_path('strip', arch='arm') self.assertIsNotNone(strip) - symfs_dir = os.path.join('testdata', 'symfs_dir') + symfs_dir = os.path.join(self.test_dir, 'symfs_dir') remove(symfs_dir) os.mkdir(symfs_dir) filename = 'simpleperf_runtest_two_functions_arm' - origin_file = os.path.join('testdata', filename) + origin_file = TEST_HELPER.testdata_path(filename) source_file = os.path.join(symfs_dir, filename) target_file = os.path.join('binary_cache', filename) expected_build_id = readelf.get_build_id(origin_file) @@ -1428,14 +1459,14 @@ class TestBinaryCacheBuilder(TestBase): class TestApiProfiler(TestBase): def run_api_test(self, package_name, apk_name, expected_reports, min_android_version): - adb = AdbHelper() - if android_version() < ord(min_android_version) - ord('L') + 5: + adb = TEST_HELPER.adb + if TEST_HELPER.android_version < ord(min_android_version) - ord('L') + 5: log_info('skip this test on Android < %s.' % min_android_version) return # step 1: Prepare profiling. self.run_cmd(['api_profiler.py', 'prepare']) # step 2: Install and run the app. - apk_path = os.path.join('testdata', apk_name) + apk_path = TEST_HELPER.testdata_path(apk_name) adb.run(['uninstall', package_name]) adb.check_run(['install', '-t', apk_path]) adb.check_run(['shell', 'am', 'start', '-n', package_name + '/.MainActivity']) @@ -1458,8 +1489,6 @@ class TestApiProfiler(TestBase): self.run_cmd(['report.py', '-g', '-o', 'report.txt', '-i', path]) self.check_strings_in_file('report.txt', expected_reports) # step 6: Clean up. - remove('report.txt') - remove('simpleperf_data') adb.check_run(['uninstall', package_name]) def run_cpp_api_test(self, apk_name, min_android_version): @@ -1504,12 +1533,13 @@ class TestApiProfiler(TestBase): class TestPprofProtoGenerator(TestBase): def setUp(self): + super(TestPprofProtoGenerator, self).setUp() if not HAS_GOOGLE_PROTOBUF: raise unittest.SkipTest( 'Skip test for pprof_proto_generator because google.protobuf is missing') def run_generator(self, options=None, testdata_file='perf_with_interpreter_frames.data'): - testdata_path = os.path.join('testdata', testdata_file) + testdata_path = TEST_HELPER.testdata_path(testdata_file) options = options or [] self.run_cmd(['pprof_proto_generator.py', '-i', testdata_path] + options) return self.run_cmd(['pprof_proto_generator.py', '--show'], return_output=True) @@ -1565,7 +1595,7 @@ class TestPprofProtoGenerator(TestBase): mapping. """ self.run_cmd(['pprof_proto_generator.py', '-i', - os.path.join('testdata', 'perf_with_interpreter_frames.data')]) + TEST_HELPER.testdata_path('perf_with_interpreter_frames.data')]) profile = load_pprof_profile('pprof.profile') # pylint: disable=no-member @@ -1577,12 +1607,14 @@ class TestPprofProtoGenerator(TestBase): class TestRecordingRealApps(TestBase): def setUp(self): - self.adb = AdbHelper(False) + super(TestRecordingRealApps, self).setUp() + self.adb = TEST_HELPER.adb self.installed_packages = [] def tearDown(self): for package in self.installed_packages: self.adb.run(['shell', 'pm', 'uninstall', package]) + super(TestRecordingRealApps, self).tearDown() def install_apk(self, apk_path, package_name): self.adb.run(['install', '-t', apk_path]) @@ -1600,20 +1632,21 @@ class TestRecordingRealApps(TestBase): self.check_strings_in_file('report.txt', [symbol_name]) def test_recording_displaybitmaps(self): - self.install_apk(os.path.join('testdata', 'DisplayBitmaps.apk'), + self.install_apk(TEST_HELPER.testdata_path('DisplayBitmaps.apk'), 'com.example.android.displayingbitmaps') - self.install_apk(os.path.join('testdata', 'DisplayBitmapsTest.apk'), + self.install_apk(TEST_HELPER.testdata_path('DisplayBitmapsTest.apk'), 'com.example.android.displayingbitmaps.test') self.start_app('shell am instrument -w -r -e debug false -e class ' + 'com.example.android.displayingbitmaps.tests.GridViewTest ' + 'com.example.android.displayingbitmaps.test/' + 'androidx.test.runner.AndroidJUnitRunner') self.record_data('com.example.android.displayingbitmaps', '-e cpu-clock -g --duration 10') - if android_version() >= 9: + if TEST_HELPER.android_version >= 9: self.check_symbol_in_record_file('androidx.test.espresso') def test_recording_endless_tunnel(self): - self.install_apk(os.path.join('testdata', 'EndlessTunnel.apk'), 'com.google.sample.tunnel') + self.install_apk(TEST_HELPER.testdata_path( + 'EndlessTunnel.apk'), 'com.google.sample.tunnel') self.start_app('shell am start -n com.google.sample.tunnel/android.app.NativeActivity -a ' + 'android.intent.action.MAIN -c android.intent.category.LAUNCHER') self.record_data('com.google.sample.tunnel', '-e cpu-clock -g --duration 10') @@ -1631,18 +1664,19 @@ def get_all_tests(): return sorted(tests) -def run_tests(tests, repeats, python_version): - os.chdir(get_script_dir()) - build_testdata() +def run_tests(tests, repeats): + TEST_HELPER.build_testdata() argv = [sys.argv[0]] + tests test_runner = unittest.TextTestRunner(stream=TEST_LOGGER, verbosity=2) - for repeat in range(repeats): + success = True + for repeat in range(1, repeats + 1): print('Run tests with python %d for %dth time\n%s' % ( - python_version, repeat + 1, '\n'.join(tests)), file=TEST_LOGGER) + TEST_HELPER.python_version, repeat, '\n'.join(tests)), file=TEST_LOGGER) + TEST_HELPER.repeat_count = repeat test_program = unittest.main(argv=argv, testRunner=test_runner, exit=False) if not test_program.result.wasSuccessful(): - return False - return True + success = False + return success def main(): @@ -1654,6 +1688,7 @@ def main(): parser.add_argument('--repeat', type=int, nargs=1, default=[1], help='run test multiple times') parser.add_argument('--no-test-result', dest='report_test_result', action='store_false', help="Don't report test result.") + parser.add_argument('--browser', action='store_true', help='pop report html file in browser.') parser.add_argument('pattern', nargs='*', help='Run tests matching the selected pattern.') args = parser.parse_args() tests = get_all_tests() @@ -1668,16 +1703,12 @@ def main(): log_exit("Can't find test %s" % args.test_from[0]) tests = tests[start_pos:] if args.pattern: - pattern = re.compile(fnmatch.translate(args.pattern[0])) - new_tests = [] - for test in tests: - if pattern.match(test): - new_tests.append(test) - tests = new_tests + patterns = [re.compile(fnmatch.translate(x)) for x in args.pattern] + tests = [t for t in tests if any(pattern.match(t) for pattern in patterns)] if not tests: log_exit('No tests are matched.') - if android_version() < 7: + if TEST_HELPER.android_version < 7: print("Skip tests on Android version < N.", file=TEST_LOGGER) return False @@ -1685,14 +1716,21 @@ def main(): python_versions = [2, 3] else: python_versions = [int(args.python_version)] + + for python_version in python_versions: + remove(TEST_HELPER.get_test_base_dir(python_version)) + + if not args.browser: + TEST_HELPER.browser_option = ['--no_browser'] + test_results = [] - current_version = 3 if is_python3() else 2 for version in python_versions: - if version == current_version: - test_result = run_tests(tests, args.repeat[0], version) + os.chdir(TEST_HELPER.cur_dir) + if version == TEST_HELPER.python_version: + test_result = run_tests(tests, args.repeat[0]) else: argv = ['python3' if version == 3 else 'python'] - argv.append(os.path.join(get_script_dir(), 'test.py')) + argv.append(TEST_HELPER.script_path('test.py')) argv += sys.argv[1:] argv += ['--python-version', str(version), '--no-test-result'] test_result = subprocess.call(argv) == 0 diff --git a/simpleperf/scripts/utils.py b/simpleperf/scripts/utils.py index e0d40b24..5754f383 100644 --- a/simpleperf/scripts/utils.py +++ b/simpleperf/scripts/utils.py @@ -326,6 +326,7 @@ class AdbHelper(object): def get_android_version(self): + """ Get Android version on device, like 7 is for Android N, 8 is for Android O.""" build_version = self.get_property('ro.build.version.release') android_version = 0 if build_version: |