CompatibilityMatrix::combine add <kernel> from new matrices
Just as <hal> and <xmlfile> are added from new matrices
to old matrices as optional, so are <kernel> tags as well.
This avoids the hack that new <kernel> versions are added
retroactively. Specifically, we don't want to add 4.14 to
every matrix, but rather let libvintf do this automagically.
Bug: 78576469
Test: vintf_object_test
Test: libvintf_test
Test: manually inspect built matrices
Change-Id: Ia779c5e25c7c3db218007d23e4c887e8cd44e730
diff --git a/CompatibilityMatrix.cpp b/CompatibilityMatrix.cpp
index 451ca60..338e5be 100644
--- a/CompatibilityMatrix.cpp
+++ b/CompatibilityMatrix.cpp
@@ -194,6 +194,36 @@
return true;
}
+bool CompatibilityMatrix::addAllKernelsAsOptional(CompatibilityMatrix* other, std::string* error) {
+ if (other == nullptr || other->level() <= level()) {
+ return true;
+ }
+
+ for (MatrixKernel& kernelToAdd : other->framework.mKernels) {
+ bool exists =
+ std::any_of(this->framework.mKernels.begin(), this->framework.mKernels.end(),
+ [&kernelToAdd](const MatrixKernel& existing) {
+ return kernelToAdd.minLts().version == existing.minLts().version &&
+ kernelToAdd.minLts().majorRev == existing.minLts().majorRev;
+ });
+
+ if (exists) {
+ // Shouldn't retroactively add requirements to minLts(), so ignore this.
+ // This happens even when kernelToAdd.conditions() != existing.conditions().
+ continue;
+ }
+
+ KernelVersion minLts = kernelToAdd.minLts();
+ if (!add(std::move(kernelToAdd))) {
+ if (error) {
+ *error = "Cannot add " + to_string(minLts) + " for unknown reason.";
+ }
+ return false;
+ }
+ }
+ return true;
+}
+
bool operator==(const CompatibilityMatrix &lft, const CompatibilityMatrix &rgt) {
return lft.mType == rgt.mType && lft.mLevel == rgt.mLevel && lft.mHals == rgt.mHals &&
lft.mXmlFiles == rgt.mXmlFiles &&
@@ -258,9 +288,32 @@
return matrix;
}
+// Check if there are two files declaring level="1", for example, because
+// combine() use this assumption to simplify a lot of logic.
+static bool checkDuplicateLevels(const std::vector<Named<CompatibilityMatrix>>& matrices,
+ std::string* error) {
+ std::map<Level, const std::string*> existingLevels;
+ for (const auto& e : matrices) {
+ if (e.object.level() == Level::UNSPECIFIED &&
+ existingLevels.find(e.object.level()) != existingLevels.end()) {
+ if (error) {
+ *error = "Conflict of levels: file \"" +
+ *existingLevels.find(e.object.level())->second + "\" and \"" + e.name +
+ " both have level " + to_string(e.object.level());
+ }
+ return false;
+ }
+ existingLevels.emplace(e.object.level(), &e.name);
+ }
+ return true;
+}
+
CompatibilityMatrix* CompatibilityMatrix::combine(Level deviceLevel,
std::vector<Named<CompatibilityMatrix>>* matrices,
std::string* error) {
+ if (!checkDuplicateLevels(*matrices, error)) {
+ return nullptr;
+ }
CompatibilityMatrix* matrix = findOrInsertBaseMatrix(matrices, error);
if (matrix == nullptr) {
@@ -312,6 +365,13 @@
}
}
+ // Add <kernel> from exact "level", then optionally add <kernel> from high levels to low levels.
+ // For example, (each letter is a kernel version x.y.z)
+ // 1.xml: A1, B1
+ // 2.xml: B2, C2, D2
+ // 3.xml: D3, E3
+ // Then the combined 1.xml should have
+ // A1, B1 (from 1.xml, required), C2, D2, E3 (optional, use earliest possible).
for (auto& e : *matrices) {
if (&e.object != matrix && e.object.level() == deviceLevel &&
e.object.type() == SchemaType::FRAMEWORK) {
@@ -328,6 +388,27 @@
}
}
+ // There is only one file per level, hence a map is used instead of a multimap. Also, there is
+ // no good ordering (i.e. priority) for multiple files with the same level.
+ std::map<Level, Named<CompatibilityMatrix>*> matricesMap;
+ for (auto& e : *matrices) {
+ if (&e.object != matrix && e.object.level() != Level::UNSPECIFIED &&
+ e.object.level() > deviceLevel && e.object.type() == SchemaType::FRAMEWORK) {
+ matricesMap.emplace(e.object.level(), &e);
+ }
+ }
+
+ for (auto&& pair : matricesMap) {
+ if (!matrix->addAllKernelsAsOptional(&pair.second->object, error)) {
+ if (error) {
+ *error = "Cannot add new kernel versions from FCM version " +
+ to_string(pair.first) + " (" + pair.second->name + ")" +
+ " to FCM version " + to_string(deviceLevel) + ": " + *error;
+ }
+ return nullptr;
+ }
+ }
+
return matrix;
}
diff --git a/include/vintf/CompatibilityMatrix.h b/include/vintf/CompatibilityMatrix.h
index ff5dd24..a43d493 100644
--- a/include/vintf/CompatibilityMatrix.h
+++ b/include/vintf/CompatibilityMatrix.h
@@ -75,6 +75,9 @@
// Similar to addAllHalsAsOptional but on <xmlfile> entries.
bool addAllXmlFilesAsOptional(CompatibilityMatrix* other, std::string* error);
+ // Similar to addAllHalsAsOptional but on <kernel> entries.
+ bool addAllKernelsAsOptional(CompatibilityMatrix* other, std::string* error);
+
status_t fetchAllInformation(const std::string& path, std::string* error = nullptr);
// Combine a subset of "matrices". For each CompatibilityMatrix in matrices,
diff --git a/test/vintf_object_tests.cpp b/test/vintf_object_tests.cpp
index 48623b8..86d43a4 100644
--- a/test/vintf_object_tests.cpp
+++ b/test/vintf_object_tests.cpp
@@ -34,10 +34,12 @@
using android::FqInstance;
-static bool In(const std::string& sub, const std::string& str) {
- return str.find(sub) != std::string::npos;
+static AssertionResult In(const std::string& sub, const std::string& str) {
+ return (str.find(sub) != std::string::npos ? AssertionSuccess() : AssertionFailure())
+ << "Value is " << str;
}
-#define EXPECT_IN(sub, str) EXPECT_TRUE(In((sub), (str))) << (str);
+#define EXPECT_IN(sub, str) EXPECT_TRUE(In((sub), (str)))
+#define EXPECT_NOT_IN(sub, str) EXPECT_FALSE(In((sub), (str)))
//
// Set of Xml1 metadata compatible with each other.
@@ -1017,16 +1019,16 @@
<< "major@1.0 should be deprecated. " << error;
}
-class RegexTest : public VintfObjectTestBase {
+class MultiMatrixTest : public VintfObjectTestBase {
protected:
static std::string getFileName(size_t i) {
return "compatibility_matrix." + std::to_string(static_cast<Level>(i)) + ".xml";
}
- virtual void SetUp() override {
+ void SetUpMockSystemMatrices(const std::vector<std::string>& xmls) {
EXPECT_CALL(fetcher(), listFiles(StrEq(kSystemVintfDir), _, _))
- .WillRepeatedly(Invoke([](const auto&, auto* out, auto*) {
+ .WillRepeatedly(Invoke([=](const auto&, auto* out, auto*) {
size_t i = 1;
- for (const auto& content : systemMatrixRegexXmls) {
+ for (const auto& content : xmls) {
(void)content;
out->push_back(getFileName(i));
++i;
@@ -1034,7 +1036,7 @@
return ::android::OK;
}));
size_t i = 1;
- for (const auto& content : systemMatrixRegexXmls) {
+ for (const auto& content : xmls) {
expectFetchRepeatedly(kSystemVintfDir + getFileName(i), content);
++i;
}
@@ -1048,6 +1050,11 @@
}
};
+class RegexTest : public MultiMatrixTest {
+ protected:
+ virtual void SetUp() { SetUpMockSystemMatrices(systemMatrixRegexXmls); }
+};
+
TEST_F(RegexTest, CombineLevel1) {
expectTargetFcmVersion(1);
auto matrix = VintfObject::GetFrameworkCompatibilityMatrix(true /* skipCache */);
@@ -1212,6 +1219,77 @@
}
}
+//
+// Set of framework matrices of different FCM version with <kernel>.
+//
+
+#define FAKE_KERNEL(__version__, __key__) \
+ " <kernel version=\"" __version__ "\">\n" \
+ " <config>\n" \
+ " <key>CONFIG_" __key__ "</key>\n" \
+ " <value type=\"tristate\">y</value>\n" \
+ " </config>\n" \
+ " </kernel>\n"
+
+const static std::vector<std::string> systemMatrixKernelXmls = {
+ // 1.xml
+ "<compatibility-matrix version=\"1.0\" type=\"framework\" level=\"1\">\n"
+ FAKE_KERNEL("1.0.0", "A1")
+ FAKE_KERNEL("2.0.0", "B1")
+ "</compatibility-matrix>\n",
+ // 2.xml
+ "<compatibility-matrix version=\"1.0\" type=\"framework\" level=\"2\">\n"
+ FAKE_KERNEL("2.0.0", "B2")
+ FAKE_KERNEL("3.0.0", "C2")
+ FAKE_KERNEL("4.0.0", "D2")
+ "</compatibility-matrix>\n",
+ // 3.xml
+ "<compatibility-matrix version=\"1.0\" type=\"framework\" level=\"3\">\n"
+ FAKE_KERNEL("4.0.0", "D3")
+ FAKE_KERNEL("5.0.0", "E3")
+ "</compatibility-matrix>\n",
+};
+
+class KernelTest : public MultiMatrixTest {};
+
+// Assume that we are developing level 2. Test that old <kernel> requirements should
+// not change and new <kernel> versions are added.
+TEST_F(KernelTest, Level1AndLevel2) {
+ SetUpMockSystemMatrices({systemMatrixKernelXmls[0], systemMatrixKernelXmls[1]});
+
+ expectTargetFcmVersion(1);
+ auto matrix = VintfObject::GetFrameworkCompatibilityMatrix(true /* skipCache */);
+ ASSERT_NE(nullptr, matrix);
+ std::string xml = gCompatibilityMatrixConverter(*matrix);
+
+ EXPECT_IN(FAKE_KERNEL("1.0.0", "A1"), xml) << "\nOld requirements must not change.";
+ EXPECT_IN(FAKE_KERNEL("2.0.0", "B1"), xml) << "\nOld requirements must not change.";
+ EXPECT_IN(FAKE_KERNEL("3.0.0", "C2"), xml) << "\nShould see <kernel> from new matrices";
+ EXPECT_IN(FAKE_KERNEL("4.0.0", "D2"), xml) << "\nShould see <kernel> from new matrices";
+
+ EXPECT_NOT_IN(FAKE_KERNEL("2.0.0", "B2"), xml) << "\nOld requirements must not change";
+}
+
+// Assume that we are developing level 3. Test that old <kernel> requirements should
+// not change and new <kernel> versions are added.
+TEST_F(KernelTest, Level1AndMore) {
+ SetUpMockSystemMatrices({systemMatrixKernelXmls});
+
+ expectTargetFcmVersion(1);
+ auto matrix = VintfObject::GetFrameworkCompatibilityMatrix(true /* skipCache */);
+ ASSERT_NE(nullptr, matrix);
+ std::string xml = gCompatibilityMatrixConverter(*matrix);
+
+ EXPECT_IN(FAKE_KERNEL("1.0.0", "A1"), xml) << "\nOld requirements must not change.";
+ EXPECT_IN(FAKE_KERNEL("2.0.0", "B1"), xml) << "\nOld requirements must not change.";
+ EXPECT_IN(FAKE_KERNEL("3.0.0", "C2"), xml) << "\nOld requirements must not change.";
+ EXPECT_IN(FAKE_KERNEL("4.0.0", "D2"), xml) << "\nOld requirements must not change.";
+ EXPECT_IN(FAKE_KERNEL("5.0.0", "E3"), xml) << "\nShould see <kernel> from new matrices";
+
+ EXPECT_NOT_IN(FAKE_KERNEL("2.0.0", "B2"), xml) << "\nOld requirements must not change";
+ EXPECT_NOT_IN(FAKE_KERNEL("4.0.0", "D3"), xml) << "\nOld requirements must not change";
+}
+
int main(int argc, char** argv) {
::testing::InitGoogleMock(&argc, argv);