blob: f28d8495751bcd021c5c04e312574d4d9f1562cd [file] [log] [blame]
/*
* Copyright (C) 2021 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 <odr_compilation_log.h>
#include <time.h>
#include <cstdint>
#include <ctime>
#include <iosfwd>
#include <istream>
#include <limits>
#include <ostream>
#include <sstream>
#include <string>
#include <vector>
#include "android-base/file.h"
#include "base/common_art_test.h"
#include "odrefresh/odrefresh.h"
#include "odr_metrics.h"
namespace art {
namespace odrefresh {
const time_t kSecondsPerDay = 86'400;
class OdrCompilationLogTest : public CommonArtTest {};
TEST(OdrCompilationLogEntry, Equality) {
OdrCompilationLogEntry a{1, 2, 3, 4, 5};
ASSERT_EQ(a, (OdrCompilationLogEntry{1, 2, 3, 4, 5}));
ASSERT_NE(a, (OdrCompilationLogEntry{9, 2, 3, 4, 5}));
ASSERT_NE(a, (OdrCompilationLogEntry{1, 9, 3, 4, 5}));
ASSERT_NE(a, (OdrCompilationLogEntry{1, 2, 9, 4, 5}));
ASSERT_NE(a, (OdrCompilationLogEntry{2, 2, 3, 9, 5}));
ASSERT_NE(a, (OdrCompilationLogEntry{2, 2, 3, 5, 9}));
}
TEST(OdrCompilationLogEntry, InputOutput) {
const OdrCompilationLogEntry entries[] = {
{1, 2, 3, 4, 5},
{std::numeric_limits<int64_t>::min(),
std::numeric_limits<int64_t>::min(),
std::numeric_limits<int32_t>::min(),
std::numeric_limits<time_t>::min(),
std::numeric_limits<int32_t>::min()},
{std::numeric_limits<int64_t>::max(),
std::numeric_limits<int64_t>::max(),
std::numeric_limits<int32_t>::max(),
std::numeric_limits<time_t>::max(),
std::numeric_limits<int32_t>::max()},
{0, 0, 0, 0, 0},
{0x7fedcba9'87654321, 0x5a5a5a5a'5a5a5a5a, 0x12345678, 0x2346789, 0x76543210}
};
for (const auto& entry : entries) {
std::stringstream ss;
ss << entry;
OdrCompilationLogEntry actual;
ss >> actual;
ASSERT_EQ(entry, actual);
}
}
TEST(OdrCompilationLogEntry, TruncatedInput) {
std::stringstream ss;
ss << "1 2";
OdrCompilationLogEntry entry;
ss >> entry;
ASSERT_TRUE(ss.fail());
ASSERT_FALSE(ss.bad());
}
TEST(OdrCompilationLogEntry, ReadMultiple) {
std::stringstream ss;
ss << "0 1 2 3 4\n5 6 7 8 9\n";
OdrCompilationLogEntry entry0, entry1;
ss >> entry0 >> entry1;
ASSERT_EQ(entry0, (OdrCompilationLogEntry{0, 1, 2, 3, 4}));
ASSERT_EQ(entry1, (OdrCompilationLogEntry{5, 6, 7, 8, 9}));
ASSERT_FALSE(ss.fail());
ASSERT_FALSE(ss.bad());
}
TEST(OdrCompilationLog, ShouldAttemptCompile) {
OdrCompilationLog ocl(/*compilation_log_path=*/nullptr);
ASSERT_TRUE(ocl.ShouldAttemptCompile(OdrMetrics::Trigger::kUnknown, 0));
ocl.Log(
/*apex_version=*/1,
/*last_update_millis=*/762,
OdrMetrics::Trigger::kApexVersionMismatch,
ExitCode::kCompilationFailed);
ASSERT_TRUE(ocl.ShouldAttemptCompile(OdrMetrics::Trigger::kApexVersionMismatch));
ASSERT_TRUE(ocl.ShouldAttemptCompile(OdrMetrics::Trigger::kDexFilesChanged));
ASSERT_FALSE(ocl.ShouldAttemptCompile(OdrMetrics::Trigger::kUnknown));
}
TEST(OdrCompilationLog, BackOffNoHistory) {
time_t start_time;
time(&start_time);
OdrCompilationLog ocl(/*compilation_log_path=*/nullptr);
ASSERT_TRUE(ocl.ShouldAttemptCompile(OdrMetrics::Trigger::kUnknown, start_time));
// Start log
ocl.Log(/*apex_version=*/1,
/*last_update_millis=*/0,
OdrMetrics::Trigger::kApexVersionMismatch,
start_time,
ExitCode::kCompilationFailed);
ASSERT_FALSE(ocl.ShouldAttemptCompile(OdrMetrics::Trigger::kUnknown, start_time));
ASSERT_FALSE(
ocl.ShouldAttemptCompile(OdrMetrics::Trigger::kUnknown, start_time + kSecondsPerDay / 2));
ASSERT_TRUE(ocl.ShouldAttemptCompile(OdrMetrics::Trigger::kUnknown, start_time + kSecondsPerDay));
// Add one more log entry
ocl.Log(/*apex_version=*/1,
/*last_update_millis=*/0,
OdrMetrics::Trigger::kApexVersionMismatch,
start_time,
ExitCode::kCompilationFailed);
ASSERT_FALSE(
ocl.ShouldAttemptCompile(OdrMetrics::Trigger::kUnknown, start_time + kSecondsPerDay));
ASSERT_TRUE(
ocl.ShouldAttemptCompile(OdrMetrics::Trigger::kUnknown, start_time + 2 * kSecondsPerDay));
// One more.
ocl.Log(/*apex_version=*/1,
/*last_update_millis=*/0,
OdrMetrics::Trigger::kApexVersionMismatch,
start_time,
ExitCode::kCompilationFailed);
ASSERT_FALSE(
ocl.ShouldAttemptCompile(OdrMetrics::Trigger::kUnknown, start_time + 3 * kSecondsPerDay));
ASSERT_TRUE(
ocl.ShouldAttemptCompile(OdrMetrics::Trigger::kUnknown, start_time + 4 * kSecondsPerDay));
// And one for the road.
ocl.Log(/*apex_version=*/1,
/*last_update_millis=*/0,
OdrMetrics::Trigger::kApexVersionMismatch,
start_time,
ExitCode::kCompilationFailed);
ASSERT_FALSE(
ocl.ShouldAttemptCompile(OdrMetrics::Trigger::kUnknown, start_time + 7 * kSecondsPerDay));
ASSERT_TRUE(
ocl.ShouldAttemptCompile(OdrMetrics::Trigger::kUnknown, start_time + 8 * kSecondsPerDay));
}
TEST(OdrCompilationLog, BackOffHappyHistory) {
time_t start_time;
time(&start_time);
OdrCompilationLog ocl(/*compilation_log_path=*/nullptr);
// Start log with a successful entry.
ocl.Log(/*apex_version=*/1,
/*last_update_millis=*/0,
OdrMetrics::Trigger::kApexVersionMismatch,
start_time,
ExitCode::kCompilationSuccess);
ASSERT_TRUE(ocl.ShouldAttemptCompile(OdrMetrics::Trigger::kUnknown, start_time));
ASSERT_TRUE(
ocl.ShouldAttemptCompile(OdrMetrics::Trigger::kUnknown, start_time + kSecondsPerDay / 4));
ASSERT_TRUE(
ocl.ShouldAttemptCompile(OdrMetrics::Trigger::kUnknown, start_time + kSecondsPerDay / 2));
// Add a log entry for a failed compilation.
ocl.Log(/*apex_version=*/1,
/*last_update_millis=*/0,
OdrMetrics::Trigger::kApexVersionMismatch,
start_time,
ExitCode::kCompilationFailed);
ASSERT_FALSE(
ocl.ShouldAttemptCompile(OdrMetrics::Trigger::kUnknown, start_time + kSecondsPerDay / 2));
ASSERT_TRUE(ocl.ShouldAttemptCompile(OdrMetrics::Trigger::kUnknown, start_time + kSecondsPerDay));
}
TEST_F(OdrCompilationLogTest, LogNumberOfEntriesAndPeek) {
OdrCompilationLog ocl(/*compilation_log_path=*/nullptr);
std::vector<OdrCompilationLogEntry> entries = {
{ 0, 1, 2, 3, 4 },
{ 1, 2, 3, 4, 5 },
{ 2, 3, 4, 5, 6 },
{ 3, 4, 5, 6, 7 },
{ 4, 5, 6, 7, 8 },
{ 5, 6, 7, 8, 9 },
{ 6, 7, 8, 9, 10 }
};
for (size_t i = 0; i < entries.size(); ++i) {
OdrCompilationLogEntry& e = entries[i];
ocl.Log(e.apex_version,
e.last_update_millis,
static_cast<OdrMetrics::Trigger>(e.trigger),
e.when,
static_cast<ExitCode>(e.exit_code));
if (i < OdrCompilationLog::kMaxLoggedEntries) {
ASSERT_EQ(i + 1, ocl.NumberOfEntries());
} else {
ASSERT_EQ(OdrCompilationLog::kMaxLoggedEntries, ocl.NumberOfEntries());
}
for (size_t j = 0; j < ocl.NumberOfEntries(); ++j) {
const OdrCompilationLogEntry* logged = ocl.Peek(j);
ASSERT_TRUE(logged != nullptr);
const OdrCompilationLogEntry& expected = entries[i + 1 - ocl.NumberOfEntries() + j];
ASSERT_EQ(expected, *logged);
}
}
}
TEST_F(OdrCompilationLogTest, LogReadWrite) {
std::vector<OdrCompilationLogEntry> entries = {
{ 0, 1, 2, 3, 4 },
{ 1, 2, 3, 4, 5 },
{ 2, 3, 4, 5, 6 },
{ 3, 4, 5, 6, 7 },
{ 4, 5, 6, 7, 8 },
{ 5, 6, 7, 8, 9 },
{ 6, 7, 8, 9, 10 }
};
ScratchFile scratch_file;
scratch_file.Close();
for (size_t i = 0; i < entries.size(); ++i) {
{
OdrCompilationLog ocl(scratch_file.GetFilename().c_str());
OdrCompilationLogEntry& e = entries[i];
ocl.Log(e.apex_version,
e.last_update_millis,
static_cast<OdrMetrics::Trigger>(e.trigger),
e.when,
static_cast<ExitCode>(e.exit_code));
}
{
OdrCompilationLog ocl(scratch_file.GetFilename().c_str());
if (i < OdrCompilationLog::kMaxLoggedEntries) {
ASSERT_EQ(i + 1, ocl.NumberOfEntries());
} else {
ASSERT_EQ(OdrCompilationLog::kMaxLoggedEntries, ocl.NumberOfEntries());
}
for (size_t j = 0; j < ocl.NumberOfEntries(); ++j) {
const OdrCompilationLogEntry* logged = ocl.Peek(j);
ASSERT_TRUE(logged != nullptr);
const OdrCompilationLogEntry& expected = entries[i + 1 - ocl.NumberOfEntries() + j];
ASSERT_EQ(expected, *logged);
}
}
}
}
TEST_F(OdrCompilationLogTest, BackoffBasedOnLog) {
time_t start_time;
time(&start_time);
ScratchFile scratch_file;
scratch_file.Close();
const char* log_path = scratch_file.GetFilename().c_str();
{
OdrCompilationLog ocl(log_path);
ASSERT_TRUE(ocl.ShouldAttemptCompile(OdrMetrics::Trigger::kUnknown, start_time));
}
{
OdrCompilationLog ocl(log_path);
// Start log
ocl.Log(/*apex_version=*/1,
/*last_update_millis=*/0,
OdrMetrics::Trigger::kApexVersionMismatch,
start_time,
ExitCode::kCompilationFailed);
}
{
OdrCompilationLog ocl(log_path);
ASSERT_FALSE(ocl.ShouldAttemptCompile(OdrMetrics::Trigger::kUnknown, start_time));
ASSERT_FALSE(
ocl.ShouldAttemptCompile(OdrMetrics::Trigger::kUnknown, start_time + kSecondsPerDay / 2));
ASSERT_TRUE(
ocl.ShouldAttemptCompile(OdrMetrics::Trigger::kUnknown, start_time + kSecondsPerDay));
}
{
// Add one more log entry
OdrCompilationLog ocl(log_path);
ocl.Log(/*apex_version=*/1,
/*last_update_millis=*/0,
OdrMetrics::Trigger::kApexVersionMismatch,
start_time,
ExitCode::kCompilationFailed);
}
{
OdrCompilationLog ocl(log_path);
ASSERT_FALSE(
ocl.ShouldAttemptCompile(OdrMetrics::Trigger::kUnknown, start_time + kSecondsPerDay));
ASSERT_TRUE(
ocl.ShouldAttemptCompile(OdrMetrics::Trigger::kUnknown, start_time + 2 * kSecondsPerDay));
}
{
// One more log entry.
OdrCompilationLog ocl(log_path);
ocl.Log(/*apex_version=*/1,
/*last_update_millis=*/0,
OdrMetrics::Trigger::kApexVersionMismatch,
start_time,
ExitCode::kCompilationFailed);
}
{
OdrCompilationLog ocl(log_path);
ASSERT_FALSE(
ocl.ShouldAttemptCompile(OdrMetrics::Trigger::kUnknown, start_time + 3 * kSecondsPerDay));
ASSERT_TRUE(
ocl.ShouldAttemptCompile(OdrMetrics::Trigger::kUnknown, start_time + 4 * kSecondsPerDay));
}
{
// And one for the road.
OdrCompilationLog ocl(log_path);
ocl.Log(/*apex_version=*/1,
/*last_update_millis=*/0,
OdrMetrics::Trigger::kApexVersionMismatch,
start_time,
ExitCode::kCompilationFailed);
}
{
OdrCompilationLog ocl(log_path);
ASSERT_FALSE(
ocl.ShouldAttemptCompile(OdrMetrics::Trigger::kUnknown, start_time + 7 * kSecondsPerDay));
ASSERT_TRUE(
ocl.ShouldAttemptCompile(OdrMetrics::Trigger::kUnknown, start_time + 8 * kSecondsPerDay));
}
}
TEST_F(OdrCompilationLogTest, NewLogVersionTriggersCompilation) {
static const int64_t kApexVersion = 1066;
static const int64_t kLastUpdateMillis = 777;
time_t start_time;
time(&start_time);
ScratchFile scratch_file;
scratch_file.Close();
// Generate a compilation log.
{
OdrCompilationLog ocl(scratch_file.GetFilename().c_str());
for (size_t i = 0; i < OdrCompilationLog::kMaxLoggedEntries; ++i) {
ocl.Log(kApexVersion,
kLastUpdateMillis,
OdrMetrics::Trigger::kApexVersionMismatch,
start_time,
ExitCode::kCompilationFailed);
ASSERT_FALSE(ocl.ShouldAttemptCompile(OdrMetrics::Trigger::kUnknown, start_time));
}
}
// Replace version string in the compilation log.
std::string log_text;
ASSERT_TRUE(android::base::ReadFileToString(scratch_file.GetFilename(), &log_text));
std::string new_log_version = std::string(OdrCompilationLog::kLogVersion) + "a";
log_text.replace(0, new_log_version.size() - 1, new_log_version);
ASSERT_TRUE(android::base::WriteStringToFile(log_text, scratch_file.GetFilename()));
// Read log with updated version entry, check it is treated as out-of-date.
{
OdrCompilationLog ocl(scratch_file.GetFilename().c_str());
ASSERT_TRUE(ocl.ShouldAttemptCompile(OdrMetrics::Trigger::kUnknown, start_time));
ASSERT_EQ(0u, ocl.NumberOfEntries());
}
}
} // namespace odrefresh
} // namespace art