1 //
2 // Copyright (C) 2022 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 #include <algorithm>
17 #include <iostream>
18 #include <unordered_set>
19
20 #include <gtest/gtest.h>
21
22 #include "common/libs/utils/files.h"
23 #include "common/libs/utils/json.h"
24 #include "host/commands/cvd/selector/instance_database.h"
25 #include "host/commands/cvd/selector/selector_constants.h"
26 #include "host/commands/cvd/unittests/selector/instance_database_helper.h"
27
28 /*
29 * SetUp creates a mock ANDROID_HOST_OUT directory where there is
30 * bin/launch_cvd, and a "Workspace" directory where supposedly HOME
31 * directories for each LocalInstanceGroup will be populated.
32 *
33 * InstanceDatabase APIs conduct validity checks: e.g. if the host tool
34 * directory actually has host tools such as launch_cvd, if the "HOME"
35 * directory for the LocalInstanceGroup is actually an existing directory,
36 * and so on.
37 *
38 * With TEST_F(Suite, Test), the following is the class declaration:
39 * class Suite : public testing::Test;
40 * class Suite_Test : public Suite;
41 *
42 * Thus, the set up is done inside the constructur of the Suite base class.
43 * Also, cleaning up the directories and files are done inside the destructor.
44 * If creating files/directories fails, the "Test" is skipped.
45 *
46 */
47
48 namespace cuttlefish {
49 namespace selector {
50
TEST_F(CvdInstanceDatabaseTest,Empty)51 TEST_F(CvdInstanceDatabaseTest, Empty) {
52 if (!SetUpOk()) {
53 GTEST_SKIP() << Error().msg;
54 }
55 auto& db = GetDb();
56 ASSERT_TRUE(db.IsEmpty());
57 ASSERT_TRUE(db.InstanceGroups().empty());
58 }
59
TEST_F(CvdInstanceDatabaseTest,AddWithInvalidGroupInfo)60 TEST_F(CvdInstanceDatabaseTest, AddWithInvalidGroupInfo) {
61 if (!SetUpOk()) {
62 GTEST_SKIP() << Error().msg;
63 }
64 auto& db = GetDb();
65 // populate home directories under Workspace()
66 const std::string home{Workspace() + "/" + "meow"};
67 if (!EnsureDirectoryExists(home).ok()) {
68 // if ever failed, skip
69 GTEST_SKIP() << "Failed to find/create " << home;
70 }
71 const std::string invalid_host_artifacts_path{Workspace() + "/" + "host_out"};
72 if (!EnsureDirectoryExists(invalid_host_artifacts_path).ok() ||
73 !EnsureDirectoryExists(invalid_host_artifacts_path + "/bin").ok()) {
74 GTEST_SKIP() << "Failed to find/create "
75 << invalid_host_artifacts_path + "/bin";
76 }
77
78 // group_name : "meow"
79 auto result_bad_home =
80 db.AddInstanceGroup({.group_name = "meow",
81 .home_dir = "/path/to/never/exists",
82 .host_artifacts_path = HostArtifactsPath(),
83 .product_out_path = HostArtifactsPath()});
84 auto result_bad_host_bin_dir =
85 db.AddInstanceGroup({.group_name = "meow",
86 .home_dir = home,
87 .host_artifacts_path = "/path/to/never/exists",
88 .product_out_path = "/path/to/never/exists"});
89 auto result_both_bad =
90 db.AddInstanceGroup({.group_name = "meow",
91 .home_dir = "/path/to/never/exists",
92 .host_artifacts_path = "/path/to/never/exists",
93 .product_out_path = "/path/to/never/exists"});
94 auto result_bad_group_name =
95 db.AddInstanceGroup({.group_name = "0invalid_group_name",
96 .home_dir = home,
97 .host_artifacts_path = HostArtifactsPath(),
98 .product_out_path = HostArtifactsPath()});
99 // Everything is correct but one thing: the host artifacts directory does not
100 // have host tool files such as launch_cvd
101 auto result_non_qualifying_host_tool_dir =
102 db.AddInstanceGroup({.group_name = "meow",
103 .home_dir = home,
104 .host_artifacts_path = invalid_host_artifacts_path,
105 .product_out_path = invalid_host_artifacts_path});
106
107 ASSERT_FALSE(result_bad_home.ok());
108 ASSERT_FALSE(result_bad_host_bin_dir.ok());
109 ASSERT_FALSE(result_both_bad.ok());
110 ASSERT_FALSE(result_bad_group_name.ok());
111 ASSERT_FALSE(result_non_qualifying_host_tool_dir.ok());
112 }
113
TEST_F(CvdInstanceDatabaseTest,AddWithValidGroupInfo)114 TEST_F(CvdInstanceDatabaseTest, AddWithValidGroupInfo) {
115 if (!SetUpOk()) {
116 GTEST_SKIP() << Error().msg;
117 }
118 auto& db = GetDb();
119 const std::string home0{Workspace() + "/" + "home0"};
120 if (!EnsureDirectoryExists(home0).ok()) {
121 GTEST_SKIP() << "Failed to find/create " << home0;
122 }
123 const std::string home1{Workspace() + "/" + "home1"};
124 if (!EnsureDirectoryExists(home1).ok()) {
125 GTEST_SKIP() << "Failed to find/create " << home1;
126 }
127
128 ASSERT_TRUE(db.AddInstanceGroup({.group_name = "meow",
129 .home_dir = home0,
130 .host_artifacts_path = HostArtifactsPath(),
131 .product_out_path = HostArtifactsPath()})
132 .ok());
133 ASSERT_TRUE(db.AddInstanceGroup({.group_name = "miaou",
134 .home_dir = home1,
135 .host_artifacts_path = HostArtifactsPath(),
136 .product_out_path = HostArtifactsPath()})
137 .ok());
138 }
139
TEST_F(CvdInstanceDatabaseTest,AddToTakenHome)140 TEST_F(CvdInstanceDatabaseTest, AddToTakenHome) {
141 if (!SetUpOk()) {
142 GTEST_SKIP() << Error().msg;
143 }
144 auto& db = GetDb();
145 const std::string home{Workspace() + "/" + "my_home"};
146 if (!EnsureDirectoryExists(home).ok()) {
147 GTEST_SKIP() << "Failed to find/create " << home;
148 }
149
150 ASSERT_TRUE(db.AddInstanceGroup({.group_name = "meow",
151 .home_dir = home,
152 .host_artifacts_path = HostArtifactsPath(),
153 .product_out_path = HostArtifactsPath()})
154 .ok());
155 ASSERT_FALSE(db.AddInstanceGroup({.group_name = "meow",
156 .home_dir = home,
157 .host_artifacts_path = HostArtifactsPath(),
158 .product_out_path = HostArtifactsPath()})
159 .ok());
160 }
161
TEST_F(CvdInstanceDatabaseTest,Clear)162 TEST_F(CvdInstanceDatabaseTest, Clear) {
163 /* AddGroups(name):
164 * HOME: Workspace() + "/" + name
165 * HostArtifactsPath: Workspace() + "/" + "android_host_out"
166 * group_ := LocalInstanceGroup(name, HOME, HostArtifactsPath)
167 */
168 if (!SetUpOk() || !AddGroups({"nyah", "yah_ong"})) {
169 GTEST_SKIP() << Error().msg;
170 }
171 auto& db = GetDb();
172
173 // test Clear()
174 ASSERT_FALSE(db.IsEmpty());
175 db.Clear();
176 ASSERT_TRUE(db.IsEmpty());
177 }
178
TEST_F(CvdInstanceDatabaseTest,SearchGroups)179 TEST_F(CvdInstanceDatabaseTest, SearchGroups) {
180 if (!SetUpOk() || !AddGroups({"myau", "miau"})) {
181 GTEST_SKIP() << Error().msg;
182 }
183 auto& db = GetDb();
184 const std::string valid_home_search_key{Workspace() + "/" + "myau"};
185 const std::string invalid_home_search_key{"/no/such/path"};
186
187 auto valid_groups = db.FindGroups({kHomeField, valid_home_search_key});
188 auto valid_group = db.FindGroup({kHomeField, valid_home_search_key});
189 auto invalid_groups = db.FindGroups({kHomeField, invalid_home_search_key});
190 auto invalid_group = db.FindGroup({kHomeField, invalid_home_search_key});
191
192 ASSERT_TRUE(valid_groups.ok());
193 ASSERT_EQ(valid_groups->size(), 1);
194 ASSERT_TRUE(valid_group.ok());
195
196 ASSERT_TRUE(invalid_groups.ok());
197 ASSERT_EQ(invalid_groups->size(), 0);
198 ASSERT_FALSE(invalid_group.ok());
199 }
200
TEST_F(CvdInstanceDatabaseTest,RemoveGroup)201 TEST_F(CvdInstanceDatabaseTest, RemoveGroup) {
202 if (!SetUpOk()) {
203 GTEST_SKIP() << Error().msg;
204 }
205 auto& db = GetDb();
206 if (!AddGroups({"miaaaw", "meow", "mjau"})) {
207 GTEST_SKIP() << Error().msg;
208 }
209 auto eng_group = db.FindGroup({kHomeField, Workspace() + "/" + "meow"});
210 if (!eng_group.ok()) {
211 GTEST_SKIP() << "meow"
212 << " group was not found.";
213 }
214
215 ASSERT_TRUE(db.RemoveInstanceGroup(*eng_group));
216 ASSERT_FALSE(db.RemoveInstanceGroup(*eng_group));
217 }
218
TEST_F(CvdInstanceDatabaseTest,AddInstances)219 TEST_F(CvdInstanceDatabaseTest, AddInstances) {
220 if (!SetUpOk() || !AddGroups({"yah_ong"})) {
221 GTEST_SKIP() << Error().msg;
222 }
223 auto& db = GetDb();
224 auto kitty_group = db.FindGroup({kHomeField, Workspace() + "/" + "yah_ong"});
225 if (!kitty_group.ok()) {
226 GTEST_SKIP() << "yah_ong"
227 << " group was not found";
228 }
229 const auto& instances = kitty_group->Get().Instances();
230
231 ASSERT_TRUE(db.AddInstance("yah_ong", 1, "yumi").ok());
232 ASSERT_FALSE(db.AddInstance("yah_ong", 3, "yumi").ok());
233 ASSERT_FALSE(db.AddInstance("yah_ong", 1, "tiger").ok());
234 ASSERT_TRUE(db.AddInstance("yah_ong", 3, "tiger").ok());
235 for (auto const& instance_unique_ptr : instances) {
236 ASSERT_TRUE(instance_unique_ptr->PerInstanceName() == "yumi" ||
237 instance_unique_ptr->PerInstanceName() == "tiger");
238 }
239 }
240
TEST_F(CvdInstanceDatabaseTest,AddInstancesInvalid)241 TEST_F(CvdInstanceDatabaseTest, AddInstancesInvalid) {
242 if (!SetUpOk() || !AddGroups({"yah_ong"})) {
243 GTEST_SKIP() << Error().msg;
244 }
245 auto& db = GetDb();
246 auto kitty_group = db.FindGroup({kHomeField, Workspace() + "/" + "yah_ong"});
247 if (!kitty_group.ok()) {
248 GTEST_SKIP() << "yah_ong"
249 << " group was not found";
250 }
251
252 ASSERT_FALSE(db.AddInstance("yah_ong", 1, "!yumi").ok());
253 ASSERT_FALSE(db.AddInstance("yah_ong", 7, "ti ger").ok());
254 }
255
TEST_F(CvdInstanceDatabaseTest,FindByInstanceId)256 TEST_F(CvdInstanceDatabaseTest, FindByInstanceId) {
257 // The start of set up
258 if (!SetUpOk()) {
259 GTEST_SKIP() << Error().msg;
260 }
261 if (!AddGroups({"miau", "nyah"})) {
262 GTEST_SKIP() << Error().msg;
263 }
264 auto& db = GetDb();
265 // per_instance_name could be the same if the parent groups are different.
266 std::vector<InstanceInfo> miau_group_instance_id_name_pairs{
267 {1, "8"}, {10, "tv-instance"}};
268 std::vector<InstanceInfo> nyah_group_instance_id_name_pairs{
269 {7, "my_favorite_phone"}, {11, "tv-instance"}, {3, "3_"}};
270 auto miau_group = db.FindGroup({kHomeField, Workspace() + "/" + "miau"});
271 auto nyah_group = db.FindGroup({kHomeField, Workspace() + "/" + "nyah"});
272 if (!miau_group.ok() || !nyah_group.ok()) {
273 GTEST_SKIP() << "miau or nyah group"
274 << " group was not found";
275 }
276 if (!AddInstances("miau", miau_group_instance_id_name_pairs) ||
277 !AddInstances("nyah", nyah_group_instance_id_name_pairs)) {
278 GTEST_SKIP() << Error().msg;
279 }
280 // The end of set up
281
282 auto result1 = db.FindInstance({kInstanceIdField, std::to_string(1)});
283 auto result10 = db.FindInstance({kInstanceIdField, std::to_string(10)});
284 auto result7 = db.FindInstance({kInstanceIdField, std::to_string(7)});
285 auto result11 = db.FindInstance({kInstanceIdField, std::to_string(11)});
286 auto result3 = db.FindInstance({kInstanceIdField, std::to_string(3)});
287 auto result_invalid = db.FindInstance({kInstanceIdField, std::to_string(20)});
288
289 ASSERT_TRUE(result1.ok());
290 ASSERT_TRUE(result10.ok());
291 ASSERT_TRUE(result7.ok());
292 ASSERT_TRUE(result11.ok());
293 ASSERT_TRUE(result3.ok());
294 ASSERT_EQ(result1->Get().PerInstanceName(), "8");
295 ASSERT_EQ(result10->Get().PerInstanceName(), "tv-instance");
296 ASSERT_EQ(result7->Get().PerInstanceName(), "my_favorite_phone");
297 ASSERT_EQ(result11->Get().PerInstanceName(), "tv-instance");
298 ASSERT_EQ(result3->Get().PerInstanceName(), "3_");
299 ASSERT_FALSE(result_invalid.ok());
300 }
301
TEST_F(CvdInstanceDatabaseTest,FindByPerInstanceName)302 TEST_F(CvdInstanceDatabaseTest, FindByPerInstanceName) {
303 // starting set up
304 if (!SetUpOk() || !AddGroups({"miau", "nyah"})) {
305 GTEST_SKIP() << Error().msg;
306 }
307 auto& db = GetDb();
308 std::vector<InstanceInfo> miau_group_instance_id_name_pairs{
309 {1, "8"}, {10, "tv_instance"}};
310 std::vector<InstanceInfo> nyah_group_instance_id_name_pairs{
311 {7, "my_favorite_phone"}, {11, "tv_instance"}};
312 auto miau_group = db.FindGroup({kHomeField, Workspace() + "/" + "miau"});
313 auto nyah_group = db.FindGroup({kHomeField, Workspace() + "/" + "nyah"});
314 if (!miau_group.ok() || !nyah_group.ok()) {
315 GTEST_SKIP() << "miau or nyah "
316 << " group was not found";
317 }
318 if (!AddInstances("miau", miau_group_instance_id_name_pairs) ||
319 !AddInstances("nyah", nyah_group_instance_id_name_pairs)) {
320 GTEST_SKIP() << Error().msg;
321 }
322 // end of set up
323
324 auto result1 = db.FindInstance({kInstanceNameField, "8"});
325 auto result10_and_11 = db.FindInstances({kInstanceNameField, "tv_instance"});
326 auto result7 = db.FindInstance({kInstanceNameField, "my_favorite_phone"});
327 auto result_invalid =
328 db.FindInstance({kInstanceNameField, "name_never_seen"});
329
330 ASSERT_TRUE(result1.ok());
331 ASSERT_TRUE(result10_and_11.ok());
332 ASSERT_TRUE(result7.ok());
333 ASSERT_EQ(result10_and_11->size(), 2);
334 ASSERT_EQ(result1->Get().InstanceId(), 1);
335 ASSERT_EQ(result7->Get().InstanceId(), 7);
336 ASSERT_FALSE(result_invalid.ok());
337 }
338
TEST_F(CvdInstanceDatabaseTest,FindInstancesByGroupName)339 TEST_F(CvdInstanceDatabaseTest, FindInstancesByGroupName) {
340 // starting set up
341 if (!SetUpOk() || !AddGroups({"miau", "nyah"})) {
342 GTEST_SKIP() << Error().msg;
343 }
344 auto& db = GetDb();
345 std::vector<InstanceInfo> nyah_group_instance_id_name_pairs{
346 {7, "my_favorite_phone"}, {11, "tv_instance"}};
347 auto nyah_group = db.FindGroup({kHomeField, Workspace() + "/" + "nyah"});
348 if (!nyah_group.ok()) {
349 GTEST_SKIP() << "nyah group was not found";
350 }
351 if (!AddInstances("nyah", nyah_group_instance_id_name_pairs)) {
352 GTEST_SKIP() << Error().msg;
353 }
354 // end of set up
355
356 auto result_nyah = db.FindInstances({kGroupNameField, "nyah"});
357 auto result_invalid = db.FindInstance({kGroupNameField, "name_never_seen"});
358
359 ASSERT_TRUE(result_nyah.ok());
360 std::set<std::string> nyah_instance_names;
361 for (const auto& instance : *result_nyah) {
362 nyah_instance_names.insert(instance.Get().PerInstanceName());
363 }
364 std::set<std::string> expected{"my_favorite_phone", "tv_instance"};
365 ASSERT_EQ(nyah_instance_names, expected);
366 ASSERT_FALSE(result_invalid.ok());
367 }
368
TEST_F(CvdInstanceDatabaseTest,FindGroupByPerInstanceName)369 TEST_F(CvdInstanceDatabaseTest, FindGroupByPerInstanceName) {
370 // starting set up
371 if (!SetUpOk() || !AddGroups({"miau", "nyah"})) {
372 GTEST_SKIP() << Error().msg;
373 }
374 auto& db = GetDb();
375 std::vector<InstanceInfo> miau_group_instance_id_name_pairs{
376 {1, "8"}, {10, "tv_instance"}};
377 std::vector<InstanceInfo> nyah_group_instance_id_name_pairs{
378 {7, "my_favorite_phone"}, {11, "tv_instance"}};
379 auto miau_group = db.FindGroup({kHomeField, Workspace() + "/" + "miau"});
380 auto nyah_group = db.FindGroup({kHomeField, Workspace() + "/" + "nyah"});
381 if (!miau_group.ok() || !nyah_group.ok()) {
382 GTEST_SKIP() << "miau or nyah "
383 << " group was not found";
384 }
385 if (!AddInstances("miau", miau_group_instance_id_name_pairs) ||
386 !AddInstances("nyah", nyah_group_instance_id_name_pairs)) {
387 GTEST_SKIP() << Error().msg;
388 }
389 // end of set up
390
391 auto result_miau = db.FindGroups({kInstanceNameField, "8"});
392 auto result_both = db.FindGroups({kInstanceNameField, "tv_instance"});
393 auto result_nyah = db.FindGroups({kInstanceNameField, "my_favorite_phone"});
394 auto result_invalid = db.FindGroups({kInstanceNameField, "name_never_seen"});
395
396 ASSERT_TRUE(result_miau.ok());
397 ASSERT_TRUE(result_both.ok());
398 ASSERT_TRUE(result_nyah.ok());
399 ASSERT_TRUE(result_invalid.ok());
400 ASSERT_EQ(result_miau->size(), 1);
401 ASSERT_EQ(result_both->size(), 2);
402 ASSERT_EQ(result_nyah->size(), 1);
403 ASSERT_TRUE(result_invalid->empty())
404 << "result_invalid should be empty but with size: "
405 << result_invalid->size();
406 }
407
TEST_F(CvdInstanceDatabaseTest,AddInstancesTogether)408 TEST_F(CvdInstanceDatabaseTest, AddInstancesTogether) {
409 // starting set up
410 if (!SetUpOk() || !AddGroups({"miau"})) {
411 GTEST_SKIP() << Error().msg;
412 }
413 auto& db = GetDb();
414 std::vector<InstanceDatabase::InstanceInfo> miau_group_instance_id_name_pairs{
415 {1, "8"}, {10, "tv_instance"}};
416 auto miau_group = db.FindGroup({kHomeField, Workspace() + "/" + "miau"});
417 if (!miau_group.ok()) {
418 GTEST_SKIP() << "miau group was not found";
419 }
420
421 auto add_result = db.AddInstances("miau", miau_group_instance_id_name_pairs);
422 ASSERT_TRUE(add_result.ok()) << add_result.error().Trace();
423
424 auto result_8 = db.FindInstance({kInstanceNameField, "8"});
425 auto result_tv = db.FindInstance({kInstanceNameField, "tv_instance"});
426
427 ASSERT_TRUE(result_8.ok()) << result_8.error().Trace();
428 ASSERT_TRUE(result_tv.ok()) << result_tv.error().Trace();
429 }
430
TEST_F(CvdInstanceDatabaseJsonTest,DumpLoadDumpCompare)431 TEST_F(CvdInstanceDatabaseJsonTest, DumpLoadDumpCompare) {
432 // starting set up
433 if (!SetUpOk() || !AddGroups({"miau"})) {
434 GTEST_SKIP() << Error().msg;
435 }
436 auto& db = GetDb();
437 std::vector<InstanceDatabase::InstanceInfo> miau_group_instance_id_name_pairs{
438 {1, "8"}, {10, "tv_instance"}};
439 auto miau_group = db.FindGroup({kHomeField, Workspace() + "/" + "miau"});
440 if (!miau_group.ok()) {
441 GTEST_SKIP() << "miau group was not found";
442 }
443 auto add_result = db.AddInstances("miau", miau_group_instance_id_name_pairs);
444 if (!add_result.ok()) {
445 GTEST_SKIP() << "Adding instances are not being tested in this test case.";
446 }
447
448 /*
449 * Dumping to json, clearing up the DB, loading from the json,
450 *
451 */
452 auto serialized_db = db.Serialize();
453 if (!db.RemoveInstanceGroup("miau")) {
454 // not testing RemoveInstanceGroup
455 GTEST_SKIP() << "miau had to be added.";
456 }
457 auto json_parsing = ParseJson(serialized_db.toStyledString());
458 ASSERT_TRUE(json_parsing.ok()) << serialized_db << std::endl
459 << " is not a valid json.";
460 auto load_result = db.LoadFromJson(serialized_db);
461 ASSERT_TRUE(load_result.ok()) << load_result.error().Trace();
462 {
463 // re-look up the group and the instances
464 auto miau_group = db.FindGroup({kHomeField, Workspace() + "/" + "miau"});
465 ASSERT_TRUE(miau_group.ok()) << miau_group.error().Trace();
466 auto result_8 = db.FindInstance({kInstanceNameField, "8"});
467 auto result_tv = db.FindInstance({kInstanceNameField, "tv_instance"});
468
469 ASSERT_TRUE(result_8.ok()) << result_8.error().Trace();
470 ASSERT_TRUE(result_tv.ok()) << result_tv.error().Trace();
471 }
472 }
473
474 } // namespace selector
475 } // namespace cuttlefish
476