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, write, (const std::string&, const std::string&, std::string*),
119                 (const override));
120     MOCK_METHOD(status_t, deleteFile, (const std::string&, std::string*), (const override));
121 };
122 
123 // Helper to convert const char* array to char* array.
124 class Args {
125    public:
Args(const std::initializer_list<const char * > & list)126     Args(const std::initializer_list<const char*>& list) {
127         mStrings.reserve(list.size());
128         mBuf.reserve(list.size());
129         for (const char* item : list) {
130             auto& str = mStrings.emplace_back(item);
131             mBuf.push_back(str.data());
132         }
133     }
size()134     int size() { return static_cast<int>(mBuf.size()); }
get()135     char** get() { return mBuf.data(); }
136 
137    private:
138     std::vector<std::string> mStrings;
139     std::vector<char*> mBuf;
140 };
141 
142 // Returns true if two paths are equivalent. Repeated '/' are omitted.
143 MATCHER_P(PathEq, expected, "") {
144     return std::filesystem::path(arg) == std::filesystem::path(expected);
145 }
146 
147 // CHeck if arg contains only the listed instances.
148 class MatrixInstanceMatcher : public MatcherInterface<const std::string&> {
149    public:
MatrixInstanceMatcher(std::set<std::string> expected)150     MatrixInstanceMatcher(std::set<std::string> expected) : mExpected(std::move(expected)) {}
MatchAndExplain(const std::string & actual,MatchResultListener * listener) const151     bool MatchAndExplain(const std::string& actual, MatchResultListener* listener) const override {
152         CompatibilityMatrix actualMatrix;
153         std::string error;
154         if (!fromXml(&actualMatrix, actual, &error)) {
155             *listener << "is not a valid compatibility matrix: " << error;
156             return false;
157         }
158         std::set<std::string> actualInstances;
159         actualMatrix.forEachInstance([&](const auto& matrixInstance) {
160             actualInstances.emplace(
161                 matrixInstance.description(matrixInstance.versionRange().minVer()));
162             return true;
163         });
164         if (actualInstances != mExpected) {
165             *listener << "contains instances " << android::base::Join(actualInstances, ",\n");
166             return false;
167         }
168         return true;
169     }
DescribeTo(std::ostream * os) const170     void DescribeTo(std::ostream* os) const override {
171         *os << "contains only the following instances " << android::base::Join(mExpected, ",\n");
172     }
DescribeNegationTo(std::ostream * os) const173     void DescribeNegationTo(std::ostream* os) const override {
174         *os << "does not contain only the following instances "
175             << android::base::Join(mExpected, ",\n");
176     }
177 
178    private:
179     std::set<std::string> mExpected;
180 };
181 
MatrixContains(std::set<std::string> expected)182 Matcher<const std::string&> MatrixContains(std::set<std::string> expected) {
183     return MakeMatcher(new MatrixInstanceMatcher(expected));
184 }
185 
186 }  // namespace
187 
188 class VintfFmTest : public Test {
189    protected:
SetUp()190     void SetUp() override {
191         auto unique_fs = std::make_unique<NiceMock<MockWritableFileSystem>>();
192         fs = unique_fs.get();
193         vintffm = std::make_unique<VintfFm>(std::move(unique_fs));
194 
195         ON_CALL(*fs, fetch(StartsWith(gFakeRoot), _, _)).WillByDefault(Return(NAME_NOT_FOUND));
196         ON_CALL(*fs, fetch(PathEq(gFrameworkManifestPath), _, _))
197             .WillByDefault(Invoke([](const auto&, auto* fetched, auto*) {
198                 *fetched = gFrameworkManifest;
199                 return OK;
200             }));
201     }
202 
expectWriteMatrix(const std::string & path,std::set<std::string> instances)203     void expectWriteMatrix(const std::string& path, std::set<std::string> instances) {
204         EXPECT_CALL(*fs, write(PathEq(path), MatrixContains(std::move(instances)), _))
205             .WillOnce(Return(OK));
206     }
207 
208     MockWritableFileSystem* fs;
209     std::unique_ptr<VintfFm> vintffm;
210 };
211 
TEST_F(VintfFmTest,Update1)212 TEST_F(VintfFmTest, Update1) {
213     expectWriteMatrix(gFrozenDir + "/1.xml"s, gInstances1);
214 
215     Args args({"vintffm", "--update", "--dirmap", gFakeSystemArg, "--level=1", gFrozenDir});
216     EXPECT_EQ(EX_OK, vintffm->main(args.size(), args.get()));
217 }
218 
TEST_F(VintfFmTest,Update2)219 TEST_F(VintfFmTest, Update2) {
220     expectWriteMatrix(gFrozenDir + "/2.xml"s, gInstances2);
221 
222     Args args({"vintffm", "--update", "--dirmap", gFakeSystemArg, "--level=2", gFrozenDir});
223     EXPECT_EQ(EX_OK, vintffm->main(args.size(), args.get()));
224 }
225 
TEST_F(VintfFmTest,Update3)226 TEST_F(VintfFmTest, Update3) {
227     expectWriteMatrix(gFrozenDir + "/3.xml"s, gInstances3);
228 
229     Args args({"vintffm", "--update", "--dirmap", gFakeSystemArg, "--level=3", gFrozenDir});
230     EXPECT_EQ(EX_OK, vintffm->main(args.size(), args.get()));
231 }
232 
createMatrixHal(HalFormat format,const std::string & package)233 std::string createMatrixHal(HalFormat format, const std::string& package) {
234     std::vector<VersionRange> versionRanges;
235     if (format != HalFormat::AIDL) {
236         versionRanges.emplace_back(1, 0);
237     }
238     auto interface = format == HalFormat::AIDL ? "IAidl" : "IHidl";
239     MatrixHal matrixHal{.format = format,
240                         .name = package,
241                         .versionRanges = versionRanges,
242                         .optional = false,
243                         .interfaces = {{interface, HalInterface{interface, {"default"}}}}};
244     return toXml(matrixHal);
245 }
246 
247 class VintfFmCheckTest : public VintfFmTest, public WithParamInterface<Level> {
248    protected:
SetUp()249     void SetUp() override {
250         VintfFmTest::SetUp();
251         SetUpFiles();
252         ON_CALL(*fs, listFiles(gFrozenDir, _, _))
253             .WillByDefault(Invoke([this](const auto&, auto* out, auto*) {
254                 for (const auto& [path, content] : files) {
255                     out->push_back(path);
256                 }
257                 return OK;
258             }));
259 
260         ON_CALL(*fs, fetch(StartsWith(gFrozenDir + "/"s), _, _))
261             .WillByDefault(Invoke([this](const auto& path, auto* fetched, auto*) {
262                 auto it = files.find(android::base::Basename(path));
263                 if (it == files.end()) {
264                     return NAME_NOT_FOUND;
265                 }
266                 *fetched = it->second;
267                 return OK;
268             }));
269     }
270 
271     std::map<std::string, std::string> files;
272 
273    private:
274     // Set up the following files:
275     //   1.xml -> gXml1
276     //   ...
277     //   |current|.json -> gJson|current|.
278     // |current| is the value of the variable |current|.
279     // |level|.xml contains instances listed in gInstances|level|.
SetUpFiles()280     void SetUpFiles() {
281         auto current = GetParam();
282         auto head = android::base::StringPrintf(R"(<compatibility-matrix %s type="device">)",
283                                                 kMetaVersionStr.c_str());
284         auto tail = R"(</compatibility-matrix>)";
285 
286         auto xml3 = createMatrixHal(HalFormat::HIDL, "android.frameworks.no_level") +
287                     createMatrixHal(HalFormat::AIDL, "android.frameworks.no_level");
288         auto xml2 = xml3 + createMatrixHal(HalFormat::HIDL, "android.frameworks.level2") +
289                     createMatrixHal(HalFormat::AIDL, "android.frameworks.level2");
290         auto xml1 = xml2 + createMatrixHal(HalFormat::HIDL, "android.frameworks.level1") +
291                     createMatrixHal(HalFormat::AIDL, "android.frameworks.level1");
292         xml3 = head + xml3 + tail;
293         xml2 = head + xml2 + tail;
294         xml1 = head + xml1 + tail;
295 
296         std::map<Level, std::string> allFiles{
297             {static_cast<Level>(1), xml1},
298             {static_cast<Level>(2), xml2},
299             {static_cast<Level>(3), xml3},
300         };
301 
302         for (Level level = static_cast<Level>(1); level <= current;
303              level = static_cast<Level>(static_cast<size_t>(level) + 1)) {
304             files.emplace(to_string(level) + ".xml", allFiles[level]);
305         }
306     }
307 };
308 
TEST_P(VintfFmCheckTest,Check)309 TEST_P(VintfFmCheckTest, Check) {
310     Args args({"vintffm", "--check", "--dirmap", gFakeSystemArg, gFrozenDir});
311     EXPECT_EQ(EX_OK, vintffm->main(args.size(), args.get()));
312 }
313 
314 INSTANTIATE_TEST_SUITE_P(VintfFmTest, VintfFmCheckTest,
315                          ::testing::Values(static_cast<Level>(1), static_cast<Level>(2),
316                                            static_cast<Level>(3)));
317 
318 }  // namespace android::vintf
319 
main(int argc,char ** argv)320 int main(int argc, char** argv) {
321     // Silence logs on host because they pollute the gtest output. VintfObject writes a lot
322     // of INFO logs.
323     android::base::SetMinimumLogSeverity(android::base::LogSeverity::WARNING);
324     ::testing::InitGoogleMock(&argc, argv);
325     return RUN_ALL_TESTS();
326 }
327