You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
327 lines
12 KiB
327 lines
12 KiB
/*
|
|
* 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 <sysexits.h>
|
|
|
|
#include <filesystem>
|
|
|
|
#include <android-base/file.h>
|
|
#include <android-base/logging.h>
|
|
#include <android-base/macros.h>
|
|
#include <android-base/parseint.h>
|
|
#include <android-base/stringprintf.h>
|
|
#include <android-base/strings.h>
|
|
#include <gtest/gtest.h>
|
|
#include <vintf/VintfFm.h>
|
|
#include <vintf/parse_xml.h>
|
|
|
|
#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"(
|
|
<manifest version="2.0" type="framework">
|
|
<hal>
|
|
<name>android.frameworks.no_level</name>
|
|
<transport>hwbinder</transport>
|
|
<fqname>@1.0::IHidl/default</fqname>
|
|
</hal>
|
|
<hal max-level="1">
|
|
<name>android.frameworks.level1</name>
|
|
<transport>hwbinder</transport>
|
|
<fqname>@1.0::IHidl/default</fqname>
|
|
</hal>
|
|
<hal max-level="2">
|
|
<name>android.frameworks.level2</name>
|
|
<transport>hwbinder</transport>
|
|
<fqname>@1.0::IHidl/default</fqname>
|
|
</hal>
|
|
<hal format="aidl">
|
|
<name>android.frameworks.no_level</name>
|
|
<fqname>IAidl/default</fqname>
|
|
</hal>
|
|
<hal format="aidl" max-level="1">
|
|
<name>android.frameworks.level1</name>
|
|
<fqname>IAidl/default</fqname>
|
|
</hal>
|
|
<hal format="aidl" max-level="2">
|
|
<name>android.frameworks.level2</name>
|
|
<fqname>IAidl/default</fqname>
|
|
</hal>
|
|
</manifest>)";
|
|
|
|
// clang-format off
|
|
static std::set<std::string> 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<std::string> 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<std::string> 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>*, 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<const char*>& 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<int>(mBuf.size()); }
|
|
char** get() { return mBuf.data(); }
|
|
|
|
private:
|
|
std::vector<std::string> mStrings;
|
|
std::vector<char*> 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<const std::string&> {
|
|
public:
|
|
MatrixInstanceMatcher(std::set<std::string> 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<std::string> 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<std::string> mExpected;
|
|
};
|
|
|
|
Matcher<const std::string&> MatrixContains(std::set<std::string> expected) {
|
|
return MakeMatcher(new MatrixInstanceMatcher(expected));
|
|
}
|
|
|
|
} // namespace
|
|
|
|
class VintfFmTest : public Test {
|
|
protected:
|
|
void SetUp() override {
|
|
auto unique_fs = std::make_unique<NiceMock<MockWritableFileSystem>>();
|
|
fs = unique_fs.get();
|
|
vintffm = std::make_unique<VintfFm>(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<std::string> instances) {
|
|
EXPECT_CALL(*fs, write(PathEq(path), MatrixContains(std::move(instances)), _))
|
|
.WillOnce(Return(OK));
|
|
}
|
|
|
|
MockWritableFileSystem* fs;
|
|
std::unique_ptr<VintfFm> 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<VersionRange> 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<Level> {
|
|
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<std::string, std::string> 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"(<compatibility-matrix %s type="device">)",
|
|
kMetaVersionStr.c_str());
|
|
auto tail = R"(</compatibility-matrix>)";
|
|
|
|
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<Level, std::string> allFiles{
|
|
{static_cast<Level>(1), xml1},
|
|
{static_cast<Level>(2), xml2},
|
|
{static_cast<Level>(3), xml3},
|
|
};
|
|
|
|
for (Level level = static_cast<Level>(1); level <= current;
|
|
level = static_cast<Level>(static_cast<size_t>(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<Level>(1), static_cast<Level>(2),
|
|
static_cast<Level>(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();
|
|
}
|