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
 |