310 lines
12 KiB
C++
310 lines
12 KiB
C++
/*
|
|
* 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
|