/* * Copyright (C) 2020 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. */ // This needs to be on top of the file to work. #include "gmock-logging-compat.h" #include #include #include #include #include #include #include #include #include #include #include #include "parse_xml_for_test.h" #include "test_constants.h" namespace android::vintf { namespace { using ::testing::_; using ::testing::Eq; using ::testing::Invoke; using ::testing::MakeMatcher; using ::testing::Matcher; using ::testing::MatcherInterface; using ::testing::MatchResultListener; using ::testing::NiceMock; using ::testing::Return; using ::testing::StartsWith; using ::testing::Test; using ::testing::WithParamInterface; using std::string_literals::operator""s; static constexpr const char* gFakeRoot = "fake_root"; static constexpr const char* gFakeSystemArg = "/system:fake_root/system"; static constexpr const char* gFrameworkManifestPath = "fake_root/system/etc/vintf/manifest.xml"; static constexpr const char* gFrozenDir = "frozen"; static constexpr const char* gFrameworkManifest = R"( android.frameworks.no_level hwbinder @1.0::IHidl/default android.frameworks.level1 hwbinder @1.0::IHidl/default android.frameworks.level2 hwbinder @1.0::IHidl/default android.frameworks.no_level IAidl/default android.frameworks.level1 IAidl/default android.frameworks.level2 IAidl/default )"; // clang-format off static std::set gInstances1 = { "android.frameworks.level1@1.0::IHidl/default", "android.frameworks.level1.IAidl/default (@1)", "android.frameworks.level2@1.0::IHidl/default", "android.frameworks.level2.IAidl/default (@1)", "android.frameworks.no_level@1.0::IHidl/default", "android.frameworks.no_level.IAidl/default (@1)", }; static std::set gInstances2 = { "android.frameworks.level2@1.0::IHidl/default", "android.frameworks.level2.IAidl/default (@1)", "android.frameworks.no_level@1.0::IHidl/default", "android.frameworks.no_level.IAidl/default (@1)", }; static std::set gInstances3 = { "android.frameworks.no_level@1.0::IHidl/default", "android.frameworks.no_level.IAidl/default (@1)", }; // clang-format on class MockWritableFileSystem : public WritableFileSystem { public: MOCK_METHOD(status_t, fetch, (const std::string&, std::string*, std::string*), (const override)); MOCK_METHOD(status_t, listFiles, (const std::string&, std::vector*, std::string*), (const override)); MOCK_METHOD(status_t, write, (const std::string&, const std::string&, std::string*), (const override)); MOCK_METHOD(status_t, deleteFile, (const std::string&, std::string*), (const override)); }; // Helper to convert const char* array to char* array. class Args { public: Args(const std::initializer_list& list) { mStrings.reserve(list.size()); mBuf.reserve(list.size()); for (const char* item : list) { auto& str = mStrings.emplace_back(item); mBuf.push_back(str.data()); } } int size() { return static_cast(mBuf.size()); } char** get() { return mBuf.data(); } private: std::vector mStrings; std::vector mBuf; }; // Returns true if two paths are equivalent. Repeated '/' are omitted. MATCHER_P(PathEq, expected, "") { return std::filesystem::path(arg) == std::filesystem::path(expected); } // CHeck if arg contains only the listed instances. class MatrixInstanceMatcher : public MatcherInterface { public: MatrixInstanceMatcher(std::set expected) : mExpected(std::move(expected)) {} bool MatchAndExplain(const std::string& actual, MatchResultListener* listener) const override { CompatibilityMatrix actualMatrix; std::string error; if (!fromXml(&actualMatrix, actual, &error)) { *listener << "is not a valid compatibility matrix: " << error; return false; } std::set actualInstances; actualMatrix.forEachInstance([&](const auto& matrixInstance) { actualInstances.emplace( matrixInstance.description(matrixInstance.versionRange().minVer())); return true; }); if (actualInstances != mExpected) { *listener << "contains instances " << android::base::Join(actualInstances, ",\n"); return false; } return true; } void DescribeTo(std::ostream* os) const override { *os << "contains only the following instances " << android::base::Join(mExpected, ",\n"); } void DescribeNegationTo(std::ostream* os) const override { *os << "does not contain only the following instances " << android::base::Join(mExpected, ",\n"); } private: std::set mExpected; }; Matcher MatrixContains(std::set expected) { return MakeMatcher(new MatrixInstanceMatcher(expected)); } } // namespace class VintfFmTest : public Test { protected: void SetUp() override { auto unique_fs = std::make_unique>(); fs = unique_fs.get(); vintffm = std::make_unique(std::move(unique_fs)); ON_CALL(*fs, fetch(StartsWith(gFakeRoot), _, _)).WillByDefault(Return(NAME_NOT_FOUND)); ON_CALL(*fs, fetch(PathEq(gFrameworkManifestPath), _, _)) .WillByDefault(Invoke([](const auto&, auto* fetched, auto*) { *fetched = gFrameworkManifest; return OK; })); } void expectWriteMatrix(const std::string& path, std::set instances) { EXPECT_CALL(*fs, write(PathEq(path), MatrixContains(std::move(instances)), _)) .WillOnce(Return(OK)); } MockWritableFileSystem* fs; std::unique_ptr vintffm; }; TEST_F(VintfFmTest, Update1) { expectWriteMatrix(gFrozenDir + "/1.xml"s, gInstances1); Args args({"vintffm", "--update", "--dirmap", gFakeSystemArg, "--level=1", gFrozenDir}); EXPECT_EQ(EX_OK, vintffm->main(args.size(), args.get())); } TEST_F(VintfFmTest, Update2) { expectWriteMatrix(gFrozenDir + "/2.xml"s, gInstances2); Args args({"vintffm", "--update", "--dirmap", gFakeSystemArg, "--level=2", gFrozenDir}); EXPECT_EQ(EX_OK, vintffm->main(args.size(), args.get())); } TEST_F(VintfFmTest, Update3) { expectWriteMatrix(gFrozenDir + "/3.xml"s, gInstances3); Args args({"vintffm", "--update", "--dirmap", gFakeSystemArg, "--level=3", gFrozenDir}); EXPECT_EQ(EX_OK, vintffm->main(args.size(), args.get())); } std::string createMatrixHal(HalFormat format, const std::string& package) { std::vector versionRanges; if (format != HalFormat::AIDL) { versionRanges.emplace_back(1, 0); } auto interface = format == HalFormat::AIDL ? "IAidl" : "IHidl"; MatrixHal matrixHal{.format = format, .name = package, .versionRanges = versionRanges, .optional = false, .interfaces = {{interface, HalInterface{interface, {"default"}}}}}; return toXml(matrixHal); } class VintfFmCheckTest : public VintfFmTest, public WithParamInterface { protected: void SetUp() override { VintfFmTest::SetUp(); SetUpFiles(); ON_CALL(*fs, listFiles(gFrozenDir, _, _)) .WillByDefault(Invoke([this](const auto&, auto* out, auto*) { for (const auto& [path, content] : files) { out->push_back(path); } return OK; })); ON_CALL(*fs, fetch(StartsWith(gFrozenDir + "/"s), _, _)) .WillByDefault(Invoke([this](const auto& path, auto* fetched, auto*) { auto it = files.find(android::base::Basename(path)); if (it == files.end()) { return NAME_NOT_FOUND; } *fetched = it->second; return OK; })); } std::map files; private: // Set up the following files: // 1.xml -> gXml1 // ... // |current|.json -> gJson|current|. // |current| is the value of the variable |current|. // |level|.xml contains instances listed in gInstances|level|. void SetUpFiles() { auto current = GetParam(); auto head = android::base::StringPrintf(R"()", kMetaVersionStr.c_str()); auto tail = R"()"; auto xml3 = createMatrixHal(HalFormat::HIDL, "android.frameworks.no_level") + createMatrixHal(HalFormat::AIDL, "android.frameworks.no_level"); auto xml2 = xml3 + createMatrixHal(HalFormat::HIDL, "android.frameworks.level2") + createMatrixHal(HalFormat::AIDL, "android.frameworks.level2"); auto xml1 = xml2 + createMatrixHal(HalFormat::HIDL, "android.frameworks.level1") + createMatrixHal(HalFormat::AIDL, "android.frameworks.level1"); xml3 = head + xml3 + tail; xml2 = head + xml2 + tail; xml1 = head + xml1 + tail; std::map allFiles{ {static_cast(1), xml1}, {static_cast(2), xml2}, {static_cast(3), xml3}, }; for (Level level = static_cast(1); level <= current; level = static_cast(static_cast(level) + 1)) { files.emplace(to_string(level) + ".xml", allFiles[level]); } } }; TEST_P(VintfFmCheckTest, Check) { Args args({"vintffm", "--check", "--dirmap", gFakeSystemArg, gFrozenDir}); EXPECT_EQ(EX_OK, vintffm->main(args.size(), args.get())); } INSTANTIATE_TEST_SUITE_P(VintfFmTest, VintfFmCheckTest, ::testing::Values(static_cast(1), static_cast(2), static_cast(3))); } // namespace android::vintf int main(int argc, char** argv) { // Silence logs on host because they pollute the gtest output. VintfObject writes a lot // of INFO logs. android::base::SetMinimumLogSeverity(android::base::LogSeverity::WARNING); ::testing::InitGoogleMock(&argc, argv); return RUN_ALL_TESTS(); }