blob: 1790071da013f1a572bb2edb14369d41b4c8423a [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <chrono>
#include <cstdlib>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <re2/re2.h>
#include "src/lib/json_parser/json_parser.h"
#include "src/virtualization/tests/lib/guest_test.h"
namespace {
using testing::HasSubstr;
using namespace std::chrono_literals;
constexpr size_t kVirtioConsoleMessageCount = 100;
constexpr char kVirtioRngUtil[] = "virtio_rng_test_util";
constexpr uint64_t kOneKibibyte = 1ul << 10;
constexpr uint64_t kOneMebibyte = 1ul << 20;
constexpr uint64_t kOneGibibyte = 1ul << 30;
// Memory tests moderately increase the VM's guest memory above the default so that they can
// validate that the guest memory is configurable.
#if __aarch64__
constexpr uint64_t kGuestMemoryForMemoryTests = kOneGibibyte + 512 * kOneMebibyte;
#else
constexpr uint64_t kGuestMemoryForMemoryTests = 4 * kOneGibibyte + 512 * kOneMebibyte;
#endif
template <class T>
using CoreGuestTest = GuestTest<T>;
// This test suite contains all guest tests that don't require a specific configuration of devices.
// They are grouped together so that they share guests and reduce the number of times guests are
// started, which is time consuming. Note that this means that some tests need to dynamically check
// the guest type in order to skip under certain conditions.
TYPED_TEST_SUITE(CoreGuestTest, AllGuestTypes, GuestTestNameGenerator);
TYPED_TEST(CoreGuestTest, VirtioConsole) {
// Test many small packets.
std::string result;
for (size_t i = 0; i != kVirtioConsoleMessageCount; ++i) {
EXPECT_EQ(this->Execute({"echo", "test"}, &result), ZX_OK);
EXPECT_EQ(result, "test\n");
}
// Test large packets. Note that we must keep the total length below 4096,
// which is the maximum line length for dash.
std::string test_data;
for (size_t i = 0; i != kVirtioConsoleMessageCount; ++i) {
test_data.append("Lorem ipsum dolor sit amet consectetur");
}
EXPECT_EQ(this->Execute({"echo", test_data.c_str()}, &result), ZX_OK);
test_data.append("\n");
EXPECT_EQ(result, test_data);
}
TYPED_TEST(CoreGuestTest, VirtioRng) {
std::string result;
ASSERT_EQ(this->RunUtil(kVirtioRngUtil, {}, &result), ZX_OK);
EXPECT_THAT(result, HasSubstr("PASS"));
}
TYPED_TEST(CoreGuestTest, RealTimeClock) {
// Real time clock not functioning in Zircon guest at this time.
//
// TODO(https://fxbug.dev/42155234): Fix clock in Zircon guest.
if (this->GetGuestKernel() == GuestKernel::ZIRCON) {
return;
}
// Print seconds since Unix epoch (1970-01-01), and parse the result.
std::string result;
ASSERT_EQ(this->Execute({"/bin/date", "+%s"}, {}, &result), ZX_OK);
int64_t guest_timestamp = std::stol(result, /*pos=*/nullptr, /*base=*/10);
ASSERT_TRUE(guest_timestamp > 0) << "Could not parse guest time.";
// Get the system time.
std::chrono::time_point now = std::chrono::system_clock::now();
int64_t host_timestamp =
std::chrono::duration_cast<std::chrono::seconds>(now.time_since_epoch()).count();
// Ensure the clock matches the system time, within a few minutes.
std::cout << "Guest time is " << (host_timestamp - guest_timestamp)
<< " second(s) behind host time.\n";
EXPECT_LT(std::abs(host_timestamp - guest_timestamp),
std::chrono::duration_cast<std::chrono::seconds>(5min).count())
<< "Guest time (" << guest_timestamp << ") and host time (" << host_timestamp
<< ") differ by more than 5 minutes.";
}
template <typename T>
class CustomizableMemoryGuest : public T {
public:
CustomizableMemoryGuest(async_dispatcher_t* dispatcher,
typename T::RunLoopUntilFunc run_loop_until)
: T(dispatcher, std::move(run_loop_until)) {}
zx_status_t BuildLaunchInfo(GuestLaunchInfo* launch_info) override {
zx_status_t status = T::BuildLaunchInfo(launch_info);
if (status != ZX_OK) {
return status;
}
launch_info->config.set_guest_memory(kGuestMemoryForMemoryTests);
return ZX_OK;
}
};
using LinuxCustomizableMemoryGuestTypes =
::testing::Types<CustomizableMemoryGuest<DebianEnclosedGuest>>;
template <class T>
using CustomizableMemoryLinuxGuestTest = GuestTest<T>;
TYPED_TEST_SUITE(CustomizableMemoryLinuxGuestTest, LinuxCustomizableMemoryGuestTypes,
GuestTestNameGenerator);
TYPED_TEST(CustomizableMemoryLinuxGuestTest, LinuxSystemMemoryConfigurable) {
std::string result;
ASSERT_EQ(this->Execute({"cat", "/proc/meminfo"}, {}, &result), ZX_OK);
std::string sizestr;
RE2::PartialMatch(result, R"(MemTotal:\s+(\d+)\skB)", &sizestr);
uint64_t system_memory = std::stoull(sizestr) * kOneKibibyte;
// Linux doesn't report the actual amount of system memory via meminfo, and we don't
// currently emulate SMBIOS allowing us to use dmidecode. For now, we can just assume
// that the Linux kernel isn't taking up more than 300 mebibytes giving moderate
// confidence that this works.
uint64_t memory_leeway = 300 * kOneMebibyte;
EXPECT_THAT(system_memory + memory_leeway, ::testing::Gt(kGuestMemoryForMemoryTests));
}
using CustomizableMemoryZirconGuestTest = GuestTest<CustomizableMemoryGuest<ZirconEnclosedGuest>>;
TEST_F(CustomizableMemoryZirconGuestTest, ZirconSystemMemoryConfigurable) {
std::string result;
ASSERT_EQ(this->Execute({"memgraph"}, {}, &result), ZX_OK);
json_parser::JSONParser parser;
rapidjson::Document memgraph = parser.ParseFromString(result, "memgraph");
ASSERT_FALSE(parser.HasError()) << parser.error_str();
ASSERT_TRUE(memgraph.IsArray());
uint64_t system_memory = 0;
std::string physmem("physmem");
for (auto& entry : memgraph.GetArray()) {
if (entry.HasMember("name") && entry["name"].GetString() == physmem) {
system_memory = entry["size_bytes"].GetUint64();
}
}
ASSERT_NE(0ul, system_memory) << "Couldn't find physmem entry in memgraph output";
// Zircon may or may not allow the first MiB to be used for as guest memory, so expect that
// reported memory is within one MiB of expected memory.
uint64_t memory_leeway = kOneMebibyte;
EXPECT_THAT(system_memory + memory_leeway, ::testing::Gt(kGuestMemoryForMemoryTests));
}
} // namespace