1 /*
2 * Copyright (C) 2020 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 // This needs to be on top of the file to work.
18 #include "gmock-logging-compat.h"
19
20 #include <sysexits.h>
21
22 #include <filesystem>
23
24 #include <android-base/file.h>
25 #include <android-base/logging.h>
26 #include <android-base/macros.h>
27 #include <android-base/parseint.h>
28 #include <android-base/stringprintf.h>
29 #include <android-base/strings.h>
30 #include <gtest/gtest.h>
31 #include <vintf/VintfFm.h>
32 #include <vintf/parse_xml.h>
33
34 #include "parse_xml_for_test.h"
35 #include "test_constants.h"
36
37 namespace android::vintf {
38
39 namespace {
40
41 using ::testing::_;
42 using ::testing::Eq;
43 using ::testing::Invoke;
44 using ::testing::MakeMatcher;
45 using ::testing::Matcher;
46 using ::testing::MatcherInterface;
47 using ::testing::MatchResultListener;
48 using ::testing::NiceMock;
49 using ::testing::Return;
50 using ::testing::StartsWith;
51 using ::testing::Test;
52 using ::testing::WithParamInterface;
53
54 using std::string_literals::operator""s;
55
56 static constexpr const char* gFakeRoot = "fake_root";
57 static constexpr const char* gFakeSystemArg = "/system:fake_root/system";
58 static constexpr const char* gFrameworkManifestPath = "fake_root/system/etc/vintf/manifest.xml";
59 static constexpr const char* gFrozenDir = "frozen";
60 static constexpr const char* gFrameworkManifest = R"(
61 <manifest version="2.0" type="framework">
62 <hal>
63 <name>android.frameworks.no_level</name>
64 <transport>hwbinder</transport>
65 <fqname>@1.0::IHidl/default</fqname>
66 </hal>
67 <hal max-level="1">
68 <name>android.frameworks.level1</name>
69 <transport>hwbinder</transport>
70 <fqname>@1.0::IHidl/default</fqname>
71 </hal>
72 <hal max-level="2">
73 <name>android.frameworks.level2</name>
74 <transport>hwbinder</transport>
75 <fqname>@1.0::IHidl/default</fqname>
76 </hal>
77 <hal format="aidl">
78 <name>android.frameworks.no_level</name>
79 <fqname>IAidl/default</fqname>
80 </hal>
81 <hal format="aidl" max-level="1">
82 <name>android.frameworks.level1</name>
83 <fqname>IAidl/default</fqname>
84 </hal>
85 <hal format="aidl" max-level="2">
86 <name>android.frameworks.level2</name>
87 <fqname>IAidl/default</fqname>
88 </hal>
89 </manifest>)";
90
91 // clang-format off
92 static std::set<std::string> gInstances1 = {
93 "android.frameworks.level1@1.0::IHidl/default",
94 "android.frameworks.level1.IAidl/default (@1)",
95 "android.frameworks.level2@1.0::IHidl/default",
96 "android.frameworks.level2.IAidl/default (@1)",
97 "android.frameworks.no_level@1.0::IHidl/default",
98 "android.frameworks.no_level.IAidl/default (@1)",
99 };
100 static std::set<std::string> gInstances2 = {
101 "android.frameworks.level2@1.0::IHidl/default",
102 "android.frameworks.level2.IAidl/default (@1)",
103 "android.frameworks.no_level@1.0::IHidl/default",
104 "android.frameworks.no_level.IAidl/default (@1)",
105 };
106 static std::set<std::string> gInstances3 = {
107 "android.frameworks.no_level@1.0::IHidl/default",
108 "android.frameworks.no_level.IAidl/default (@1)",
109 };
110 // clang-format on
111
112 class MockWritableFileSystem : public WritableFileSystem {
113 public:
114 MOCK_METHOD(status_t, fetch, (const std::string&, std::string*, std::string*),
115 (const override));
116 MOCK_METHOD(status_t, listFiles, (const std::string&, std::vector<std::string>*, std::string*),
117 (const override));
118 MOCK_METHOD(status_t, modifiedTime, (const std::string&, int64_t*, std::string*),
119 (const override));
120 MOCK_METHOD(status_t, write, (const std::string&, const std::string&, std::string*),
121 (const override));
122 MOCK_METHOD(status_t, deleteFile, (const std::string&, std::string*), (const override));
123 };
124
125 // Helper to convert const char* array to char* array.
126 class Args {
127 public:
Args(const std::initializer_list<const char * > & list)128 Args(const std::initializer_list<const char*>& list) {
129 mStrings.reserve(list.size());
130 mBuf.reserve(list.size());
131 for (const char* item : list) {
132 auto& str = mStrings.emplace_back(item);
133 mBuf.push_back(str.data());
134 }
135 }
size()136 int size() { return static_cast<int>(mBuf.size()); }
get()137 char** get() { return mBuf.data(); }
138
139 private:
140 std::vector<std::string> mStrings;
141 std::vector<char*> mBuf;
142 };
143
144 // Returns true if two paths are equivalent. Repeated '/' are omitted.
145 MATCHER_P(PathEq, expected, "") {
146 return std::filesystem::path(arg) == std::filesystem::path(expected);
147 }
148
149 // CHeck if arg contains only the listed instances.
150 class MatrixInstanceMatcher : public MatcherInterface<const std::string&> {
151 public:
MatrixInstanceMatcher(std::set<std::string> expected)152 MatrixInstanceMatcher(std::set<std::string> expected) : mExpected(std::move(expected)) {}
MatchAndExplain(const std::string & actual,MatchResultListener * listener) const153 bool MatchAndExplain(const std::string& actual, MatchResultListener* listener) const override {
154 CompatibilityMatrix actualMatrix;
155 std::string error;
156 if (!fromXml(&actualMatrix, actual, &error)) {
157 *listener << "is not a valid compatibility matrix: " << error;
158 return false;
159 }
160 std::set<std::string> actualInstances;
161 actualMatrix.forEachInstance([&](const auto& matrixInstance) {
162 actualInstances.emplace(
163 matrixInstance.description(matrixInstance.versionRange().minVer()));
164 return true;
165 });
166 if (actualInstances != mExpected) {
167 *listener << "contains instances " << android::base::Join(actualInstances, ",\n");
168 return false;
169 }
170 return true;
171 }
DescribeTo(std::ostream * os) const172 void DescribeTo(std::ostream* os) const override {
173 *os << "contains only the following instances " << android::base::Join(mExpected, ",\n");
174 }
DescribeNegationTo(std::ostream * os) const175 void DescribeNegationTo(std::ostream* os) const override {
176 *os << "does not contain only the following instances "
177 << android::base::Join(mExpected, ",\n");
178 }
179
180 private:
181 std::set<std::string> mExpected;
182 };
183
MatrixContains(std::set<std::string> expected)184 Matcher<const std::string&> MatrixContains(std::set<std::string> expected) {
185 return MakeMatcher(new MatrixInstanceMatcher(expected));
186 }
187
188 } // namespace
189
190 class VintfFmTest : public Test {
191 protected:
SetUp()192 void SetUp() override {
193 auto unique_fs = std::make_unique<NiceMock<MockWritableFileSystem>>();
194 fs = unique_fs.get();
195 vintffm = std::make_unique<VintfFm>(std::move(unique_fs));
196
197 ON_CALL(*fs, fetch(StartsWith(gFakeRoot), _, _)).WillByDefault(Return(NAME_NOT_FOUND));
198 ON_CALL(*fs, fetch(PathEq(gFrameworkManifestPath), _, _))
199 .WillByDefault(Invoke([](const auto&, auto* fetched, auto*) {
200 *fetched = gFrameworkManifest;
201 return OK;
202 }));
203 }
204
expectWriteMatrix(const std::string & path,std::set<std::string> instances)205 void expectWriteMatrix(const std::string& path, std::set<std::string> instances) {
206 EXPECT_CALL(*fs, write(PathEq(path), MatrixContains(std::move(instances)), _))
207 .WillOnce(Return(OK));
208 }
209
210 MockWritableFileSystem* fs;
211 std::unique_ptr<VintfFm> vintffm;
212 };
213
TEST_F(VintfFmTest,Update1)214 TEST_F(VintfFmTest, Update1) {
215 expectWriteMatrix(gFrozenDir + "/1.xml"s, gInstances1);
216
217 Args args({"vintffm", "--update", "--dirmap", gFakeSystemArg, "--level=1", gFrozenDir});
218 EXPECT_EQ(EX_OK, vintffm->main(args.size(), args.get()));
219 }
220
TEST_F(VintfFmTest,Update2)221 TEST_F(VintfFmTest, Update2) {
222 expectWriteMatrix(gFrozenDir + "/2.xml"s, gInstances2);
223
224 Args args({"vintffm", "--update", "--dirmap", gFakeSystemArg, "--level=2", gFrozenDir});
225 EXPECT_EQ(EX_OK, vintffm->main(args.size(), args.get()));
226 }
227
TEST_F(VintfFmTest,Update3)228 TEST_F(VintfFmTest, Update3) {
229 expectWriteMatrix(gFrozenDir + "/3.xml"s, gInstances3);
230
231 Args args({"vintffm", "--update", "--dirmap", gFakeSystemArg, "--level=3", gFrozenDir});
232 EXPECT_EQ(EX_OK, vintffm->main(args.size(), args.get()));
233 }
234
createMatrixHal(HalFormat format,const std::string & package)235 std::string createMatrixHal(HalFormat format, const std::string& package) {
236 std::vector<VersionRange> versionRanges;
237 if (format != HalFormat::AIDL) {
238 versionRanges.emplace_back(1, 0);
239 }
240 auto interface = format == HalFormat::AIDL ? "IAidl" : "IHidl";
241 MatrixHal matrixHal{.format = format,
242 .name = package,
243 .versionRanges = versionRanges,
244 .optional = false,
245 .interfaces = {{interface, HalInterface{interface, {"default"}}}}};
246 return toXml(matrixHal);
247 }
248
249 class VintfFmCheckTest : public VintfFmTest, public WithParamInterface<Level> {
250 protected:
SetUp()251 void SetUp() override {
252 VintfFmTest::SetUp();
253 SetUpFiles();
254 ON_CALL(*fs, listFiles(gFrozenDir, _, _))
255 .WillByDefault(Invoke([this](const auto&, auto* out, auto*) {
256 for (const auto& [path, content] : files) {
257 out->push_back(path);
258 }
259 return OK;
260 }));
261
262 ON_CALL(*fs, fetch(StartsWith(gFrozenDir + "/"s), _, _))
263 .WillByDefault(Invoke([this](const auto& path, auto* fetched, auto*) {
264 auto it = files.find(android::base::Basename(path));
265 if (it == files.end()) {
266 return NAME_NOT_FOUND;
267 }
268 *fetched = it->second;
269 return OK;
270 }));
271 }
272
273 std::map<std::string, std::string> files;
274
275 private:
276 // Set up the following files:
277 // 1.xml -> gXml1
278 // ...
279 // |current|.json -> gJson|current|.
280 // |current| is the value of the variable |current|.
281 // |level|.xml contains instances listed in gInstances|level|.
SetUpFiles()282 void SetUpFiles() {
283 auto current = GetParam();
284 auto head = android::base::StringPrintf(R"(<compatibility-matrix %s type="device">)",
285 kMetaVersionStr.c_str());
286 auto tail = R"(</compatibility-matrix>)";
287
288 auto xml3 = createMatrixHal(HalFormat::HIDL, "android.frameworks.no_level") +
289 createMatrixHal(HalFormat::AIDL, "android.frameworks.no_level");
290 auto xml2 = xml3 + createMatrixHal(HalFormat::HIDL, "android.frameworks.level2") +
291 createMatrixHal(HalFormat::AIDL, "android.frameworks.level2");
292 auto xml1 = xml2 + createMatrixHal(HalFormat::HIDL, "android.frameworks.level1") +
293 createMatrixHal(HalFormat::AIDL, "android.frameworks.level1");
294 xml3 = head + xml3 + tail;
295 xml2 = head + xml2 + tail;
296 xml1 = head + xml1 + tail;
297
298 std::map<Level, std::string> allFiles{
299 {static_cast<Level>(1), xml1},
300 {static_cast<Level>(2), xml2},
301 {static_cast<Level>(3), xml3},
302 };
303
304 for (Level level = static_cast<Level>(1); level <= current;
305 level = static_cast<Level>(static_cast<size_t>(level) + 1)) {
306 files.emplace(to_string(level) + ".xml", allFiles[level]);
307 }
308 }
309 };
310
TEST_P(VintfFmCheckTest,Check)311 TEST_P(VintfFmCheckTest, Check) {
312 Args args({"vintffm", "--check", "--dirmap", gFakeSystemArg, gFrozenDir});
313 EXPECT_EQ(EX_OK, vintffm->main(args.size(), args.get()));
314 }
315
316 INSTANTIATE_TEST_SUITE_P(VintfFmTest, VintfFmCheckTest,
317 ::testing::Values(static_cast<Level>(1), static_cast<Level>(2),
318 static_cast<Level>(3)));
319
320 } // namespace android::vintf
321
main(int argc,char ** argv)322 int main(int argc, char** argv) {
323 // Silence logs on host because they pollute the gtest output. VintfObject writes a lot
324 // of INFO logs.
325 android::base::SetMinimumLogSeverity(android::base::LogSeverity::WARNING);
326 ::testing::InitGoogleMock(&argc, argv);
327 return RUN_ALL_TESTS();
328 }
329