blob: 381d8057a4a57f43ac5a2de4560d7c6bbe8fbd8e [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 <android-base/stringprintf.h>
#include <android-base/strings.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <vintf/VintfObjectRecovery.h>
#include <vintf/parse_string.h>
#include "constants-private.h"
#include "test_constants.h"
#include "utils-fake.h"
using android::base::ConsumePrefix;
using android::base::StringPrintf;
using testing::_;
using testing::Combine;
using testing::Invoke;
using testing::IsEmpty;
using testing::Mock;
using testing::NiceMock;
using testing::StartsWith;
using testing::StrEq;
using testing::TestParamInfo;
using testing::UnorderedElementsAre;
using testing::ValuesIn;
namespace android::vintf::testing {
using details::kSystemManifest;
using details::kSystemManifestFragmentDir;
using details::MockFileSystemWithError;
using details::MockPropertyFetcher;
using details::MockRuntimeInfo;
using details::MockRuntimeInfoFactory;
template <typename T>
using StatusOr = std::variant<status_t, T>;
using DirectoryContent = std::map<std::string, StatusOr<std::string>>;
using OptionalType = std::optional<SchemaType>;
std::vector<OptionalType> OptionalTypes() {
return {std::nullopt, SchemaType::DEVICE, SchemaType::FRAMEWORK};
}
std::string OptionalTypeToString(const OptionalType& optionalType) {
if (!optionalType.has_value()) return "broken";
return to_string(*optionalType);
}
constexpr const char* kMainFmt = R"(<manifest %s type="%s">
<hal format="aidl">
<name>android.hardware.main</name>
<fqname>IMain/default</fqname>
</hal>
</manifest>
)";
constexpr const char* kFragment1Fmt = R"(<manifest %s type="%s">
<hal format="aidl">
<name>android.hardware.fragment1</name>
<fqname>IFragment/default</fqname>
</hal>
</manifest>
)";
constexpr const char* kFragment2Fmt = R"(<manifest %s type="%s">
<hal format="aidl">
<name>android.hardware.fragment2</name>
<fqname>IFragment/default</fqname>
</hal>
</manifest>
)";
std::string formatManifest(const char* fmt, const OptionalType& optionalType) {
if (!optionalType.has_value()) {
return "(broken manifest)";
}
return StringPrintf(fmt, kMetaVersionStr.c_str(), to_string(*optionalType).c_str());
}
using VintfObjectRecoveryTestParam = std::tuple<OptionalType, OptionalType, OptionalType>;
class VintfObjectRecoveryTest : public ::testing::TestWithParam<VintfObjectRecoveryTestParam> {
public:
virtual void SetUp() {
vintfObject = VintfObjectRecovery::Builder()
.setFileSystem(std::make_unique<NiceMock<MockFileSystemWithError>>())
.setRuntimeInfoFactory(std::make_unique<NiceMock<MockRuntimeInfoFactory>>(
std::make_shared<NiceMock<MockRuntimeInfo>>()))
.setPropertyFetcher(std::make_unique<NiceMock<MockPropertyFetcher>>())
.build<VintfObjectRecovery>();
auto [mainType, fragType1, fragType2] = GetParam();
main = formatManifest(kMainFmt, mainType);
frag1 = formatManifest(kFragment1Fmt, fragType1);
frag2 = formatManifest(kFragment2Fmt, fragType2);
}
virtual void TearDown() { Mock::VerifyAndClear(&fs()); }
MockFileSystemWithError& fs() {
return static_cast<MockFileSystemWithError&>(*vintfObject->getFileSystem());
}
void setUpManifests(const StatusOr<std::string>& mainContent,
const StatusOr<DirectoryContent>& frags) {
// By default, no files exist in the file system.
ON_CALL(fs(), listFiles(_, _, _)).WillByDefault(Return(NAME_NOT_FOUND));
ON_CALL(fs(), fetch(_, _, _))
.WillByDefault(Invoke([](const auto& path, auto*, auto* error) {
if (error != nullptr) {
*error = "fetch " + path + ": cannot be found on empty filesystem: " +
statusToString(NAME_NOT_FOUND);
}
return NAME_NOT_FOUND;
}));
ON_CALL(fs(), fetch(StrEq(kSystemManifest), _, _))
.WillByDefault(Invoke([=](const auto& path, auto* content, auto* error) -> status_t {
if (std::holds_alternative<status_t>(mainContent)) {
if (error != nullptr) {
*error = "fetch " + path + ": set to return " +
statusToString(std::get<status_t>(mainContent));
}
return std::get<status_t>(mainContent);
}
*content = std::get<std::string>(mainContent);
return OK;
}));
ON_CALL(fs(), listFiles(StrEq(kSystemManifestFragmentDir), _, _))
.WillByDefault(Invoke([=](const std::string& path, std::vector<std::string>* out,
auto* error) -> status_t {
if (std::holds_alternative<status_t>(frags)) {
if (error != nullptr) {
*error = "list " + path + ": set to return " +
statusToString(std::get<status_t>(frags));
}
return std::get<status_t>(frags);
}
for (const auto& [name, statusOrFile] : std::get<DirectoryContent>(frags)) {
out->push_back(name);
}
return OK;
}));
ON_CALL(fs(), fetch(StartsWith(kSystemManifestFragmentDir), _, _))
.WillByDefault(Invoke([=](const auto& path, auto* content, auto* error) -> status_t {
if (std::holds_alternative<status_t>(frags)) {
if (error != nullptr) {
*error = "fetch " + path + ": for dir, set to return " +
statusToString(std::get<status_t>(frags));
}
return std::get<status_t>(frags);
}
const auto& directoryContent = std::get<DirectoryContent>(frags);
std::string_view subpath = path;
bool consumed = ConsumePrefix(&subpath, kSystemManifestFragmentDir);
EXPECT_TRUE(consumed)
<< path << " does not start with " << kSystemManifestFragmentDir;
auto it = directoryContent.find(std::string(subpath));
if (it == directoryContent.end()) {
if (error != nullptr) {
*error = "fetch " + path +
": not in DirectoryContent: " + statusToString(NAME_NOT_FOUND);
}
return NAME_NOT_FOUND;
}
const auto& [name, statusOrFile] = *it;
if (std::holds_alternative<status_t>(statusOrFile)) {
*error = "fetch " + path + ": for file, set to return " +
statusToString(std::get<status_t>(statusOrFile));
return std::get<status_t>(statusOrFile);
}
*content = std::get<std::string>(statusOrFile);
return OK;
}));
}
static std::string ParamToString(const TestParamInfo<ParamType>& info) {
auto [mainType, fragType1, fragType2] = info.param;
auto s = "main_" + OptionalTypeToString(mainType);
s += "_frag1_" + OptionalTypeToString(fragType1);
s += "_frag2_" + OptionalTypeToString(fragType2);
return s;
}
std::unique_ptr<VintfObjectRecovery> vintfObject;
std::string main;
std::string frag1;
std::string frag2;
};
TEST_P(VintfObjectRecoveryTest, Empty) {
setUpManifests(NAME_NOT_FOUND, NAME_NOT_FOUND);
auto manifest = vintfObject->getRecoveryHalManifest();
ASSERT_NE(nullptr, manifest);
auto hals = manifest->getHalNames();
EXPECT_THAT(hals, IsEmpty());
}
TEST_P(VintfObjectRecoveryTest, InaccessibleMainManifest) {
setUpManifests(UNKNOWN_ERROR, NAME_NOT_FOUND);
auto manifest = vintfObject->getRecoveryHalManifest();
EXPECT_EQ(nullptr, manifest);
}
TEST_P(VintfObjectRecoveryTest, MainManifestOnly) {
auto [mainType, fragType1, fragType2] = GetParam();
setUpManifests(main, NAME_NOT_FOUND);
auto manifest = vintfObject->getRecoveryHalManifest();
if (!mainType.has_value()) { // main manifest is broken
EXPECT_EQ(nullptr, manifest);
return;
}
ASSERT_NE(nullptr, manifest);
EXPECT_THAT(manifest->getHalNames(), UnorderedElementsAre("android.hardware.main"));
}
TEST_P(VintfObjectRecoveryTest, MainManifestAndDirectoryOnly) {
auto [mainType, fragType1, fragType2] = GetParam();
setUpManifests(main, {});
auto manifest = vintfObject->getRecoveryHalManifest();
if (!mainType.has_value()) { // main manifest is broken
EXPECT_EQ(nullptr, manifest);
return;
}
ASSERT_NE(nullptr, manifest);
EXPECT_THAT(manifest->getHalNames(), UnorderedElementsAre("android.hardware.main"));
}
TEST_P(VintfObjectRecoveryTest, MainManifestAndInaccessibleFragment) {
setUpManifests(main, DirectoryContent{{"frag1.xml", UNKNOWN_ERROR}});
auto manifest = vintfObject->getRecoveryHalManifest();
EXPECT_EQ(nullptr, manifest);
}
TEST_P(VintfObjectRecoveryTest, MainManifestAndFragments) {
auto [mainType, fragType1, fragType2] = GetParam();
setUpManifests(main, DirectoryContent{{"frag1.xml", frag1}, {"frag2.xml", frag2}});
auto manifest = vintfObject->getRecoveryHalManifest();
if (!mainType.has_value() || !fragType1.has_value() || !fragType2.has_value()) {
// some manifest(s) are broken
EXPECT_EQ(nullptr, manifest);
return;
}
ASSERT_NE(nullptr, manifest);
EXPECT_THAT(manifest->getHalNames(),
UnorderedElementsAre("android.hardware.main", "android.hardware.fragment1",
"android.hardware.fragment2"));
}
TEST_P(VintfObjectRecoveryTest, InaccessibleDirectory) {
setUpManifests(NAME_NOT_FOUND, UNKNOWN_ERROR);
auto manifest = vintfObject->getRecoveryHalManifest();
EXPECT_EQ(nullptr, manifest);
}
TEST_P(VintfObjectRecoveryTest, InaccessibleFragment) {
setUpManifests(NAME_NOT_FOUND, DirectoryContent{{"frag1.xml", UNKNOWN_ERROR}});
auto manifest = vintfObject->getRecoveryHalManifest();
EXPECT_EQ(nullptr, manifest);
}
TEST_P(VintfObjectRecoveryTest, SomeInaccessibleFragment) {
setUpManifests(NAME_NOT_FOUND,
DirectoryContent{{"frag1.xml", UNKNOWN_ERROR}, {"frag2.xml", frag2}});
auto manifest = vintfObject->getRecoveryHalManifest();
EXPECT_EQ(nullptr, manifest);
}
TEST_P(VintfObjectRecoveryTest, DirectoryOnly) {
setUpManifests(NAME_NOT_FOUND, {});
auto manifest = vintfObject->getRecoveryHalManifest();
ASSERT_NE(nullptr, manifest);
EXPECT_THAT(manifest->getHalNames(), IsEmpty());
}
TEST_P(VintfObjectRecoveryTest, FragmentsOnly) {
auto [mainType, fragType1, fragType2] = GetParam();
setUpManifests(NAME_NOT_FOUND, DirectoryContent{{"frag1.xml", frag1}, {"frag2.xml", frag2}});
auto manifest = vintfObject->getRecoveryHalManifest();
if (!fragType1.has_value() || !fragType2.has_value()) {
// some manifest(s) are broken
EXPECT_EQ(nullptr, manifest);
return;
}
ASSERT_NE(nullptr, manifest);
EXPECT_THAT(manifest->getHalNames(),
UnorderedElementsAre("android.hardware.fragment1", "android.hardware.fragment2"));
}
INSTANTIATE_TEST_CASE_P(VintfObjectRecoveryTest, VintfObjectRecoveryTest,
Combine(ValuesIn(OptionalTypes()), ValuesIn(OptionalTypes()),
ValuesIn(OptionalTypes())),
VintfObjectRecoveryTest::ParamToString);
} // namespace android::vintf::testing