/* * 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 #include #include #include #include #include #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 using StatusOr = std::variant; using DirectoryContent = std::map>; using OptionalType = std::optional; std::vector 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"( android.hardware.main IMain/default )"; constexpr const char* kFragment1Fmt = R"( android.hardware.fragment1 IFragment/default )"; constexpr const char* kFragment2Fmt = R"( android.hardware.fragment2 IFragment/default )"; 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; class VintfObjectRecoveryTest : public ::testing::TestWithParam { public: virtual void SetUp() { vintfObject = VintfObjectRecovery::Builder() .setFileSystem(std::make_unique>()) .setRuntimeInfoFactory(std::make_unique>( std::make_shared>())) .setPropertyFetcher(std::make_unique>()) .build(); 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(*vintfObject->getFileSystem()); } void setUpManifests(const StatusOr& mainContent, const StatusOr& 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(mainContent)) { if (error != nullptr) { *error = "fetch " + path + ": set to return " + statusToString(std::get(mainContent)); } return std::get(mainContent); } *content = std::get(mainContent); return OK; })); ON_CALL(fs(), listFiles(StrEq(kSystemManifestFragmentDir), _, _)) .WillByDefault(Invoke([=](const std::string& path, std::vector* out, auto* error) -> status_t { if (std::holds_alternative(frags)) { if (error != nullptr) { *error = "list " + path + ": set to return " + statusToString(std::get(frags)); } return std::get(frags); } for (const auto& [name, statusOrFile] : std::get(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(frags)) { if (error != nullptr) { *error = "fetch " + path + ": for dir, set to return " + statusToString(std::get(frags)); } return std::get(frags); } const auto& directoryContent = std::get(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(statusOrFile)) { *error = "fetch " + path + ": for file, set to return " + statusToString(std::get(statusOrFile)); return std::get(statusOrFile); } *content = std::get(statusOrFile); return OK; })); } static std::string ParamToString(const TestParamInfo& 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 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