1/*
2 * Copyright (C) 2017 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#include <functional>
18
19#include <android-base/file.h>
20#include <android-base/test_utils.h>
21#include <gtest/gtest.h>
22
23#include "action.h"
24#include "action_manager.h"
25#include "action_parser.h"
26#include "builtins.h"
27#include "import_parser.h"
28#include "keyword_map.h"
29#include "parser.h"
30#include "service.h"
31#include "test_function_map.h"
32#include "util.h"
33
34namespace android {
35namespace init {
36
37using ActionManagerCommand = std::function<void(ActionManager&)>;
38
39void TestInit(const std::string& init_script_file, const TestFunctionMap& test_function_map,
40              const std::vector<ActionManagerCommand>& commands, ServiceList* service_list) {
41    ActionManager am;
42
43    Action::set_function_map(&test_function_map);
44
45    Parser parser;
46    parser.AddSectionParser("service", std::make_unique<ServiceParser>(service_list, nullptr));
47    parser.AddSectionParser("on", std::make_unique<ActionParser>(&am, nullptr));
48    parser.AddSectionParser("import", std::make_unique<ImportParser>(&parser));
49
50    ASSERT_TRUE(parser.ParseConfig(init_script_file));
51
52    for (const auto& command : commands) {
53        command(am);
54    }
55
56    while (am.HasMoreCommands()) {
57        am.ExecuteOneCommand();
58    }
59}
60
61void TestInitText(const std::string& init_script, const TestFunctionMap& test_function_map,
62                  const std::vector<ActionManagerCommand>& commands, ServiceList* service_list) {
63    TemporaryFile tf;
64    ASSERT_TRUE(tf.fd != -1);
65    ASSERT_TRUE(android::base::WriteStringToFd(init_script, tf.fd));
66    TestInit(tf.path, test_function_map, commands, service_list);
67}
68
69TEST(init, SimpleEventTrigger) {
70    bool expect_true = false;
71    std::string init_script =
72        R"init(
73on boot
74pass_test
75)init";
76
77    TestFunctionMap test_function_map;
78    test_function_map.Add("pass_test", [&expect_true]() { expect_true = true; });
79
80    ActionManagerCommand trigger_boot = [](ActionManager& am) { am.QueueEventTrigger("boot"); };
81    std::vector<ActionManagerCommand> commands{trigger_boot};
82
83    ServiceList service_list;
84    TestInitText(init_script, test_function_map, commands, &service_list);
85
86    EXPECT_TRUE(expect_true);
87}
88
89TEST(init, EventTriggerOrder) {
90    std::string init_script =
91        R"init(
92on boot
93execute_first
94
95on boot && property:ro.hardware=*
96execute_second
97
98on boot
99execute_third
100
101)init";
102
103    int num_executed = 0;
104    TestFunctionMap test_function_map;
105    test_function_map.Add("execute_first", [&num_executed]() { EXPECT_EQ(0, num_executed++); });
106    test_function_map.Add("execute_second", [&num_executed]() { EXPECT_EQ(1, num_executed++); });
107    test_function_map.Add("execute_third", [&num_executed]() { EXPECT_EQ(2, num_executed++); });
108
109    ActionManagerCommand trigger_boot = [](ActionManager& am) { am.QueueEventTrigger("boot"); };
110    std::vector<ActionManagerCommand> commands{trigger_boot};
111
112    ServiceList service_list;
113    TestInitText(init_script, test_function_map, commands, &service_list);
114}
115
116TEST(init, OverrideService) {
117    std::string init_script = R"init(
118service A something
119    class first
120
121service A something
122    class second
123    override
124
125)init";
126
127    ServiceList service_list;
128    TestInitText(init_script, TestFunctionMap(), {}, &service_list);
129    ASSERT_EQ(1, std::distance(service_list.begin(), service_list.end()));
130
131    auto service = service_list.begin()->get();
132    ASSERT_NE(nullptr, service);
133    EXPECT_EQ(std::set<std::string>({"second"}), service->classnames());
134    EXPECT_EQ("A", service->name());
135    EXPECT_TRUE(service->is_override());
136}
137
138TEST(init, EventTriggerOrderMultipleFiles) {
139    // 6 total files, which should have their triggers executed in the following order:
140    // 1: start - original script parsed
141    // 2: first_import - immediately imported by first_script
142    // 3: dir_a - file named 'a.rc' in dir; dir is imported after first_import
143    // 4: a_import - file imported by dir_a
144    // 5: dir_b - file named 'b.rc' in dir
145    // 6: last_import - imported after dir is imported
146
147    TemporaryFile first_import;
148    ASSERT_TRUE(first_import.fd != -1);
149    ASSERT_TRUE(android::base::WriteStringToFd("on boot\nexecute 2", first_import.fd));
150
151    TemporaryFile dir_a_import;
152    ASSERT_TRUE(dir_a_import.fd != -1);
153    ASSERT_TRUE(android::base::WriteStringToFd("on boot\nexecute 4", dir_a_import.fd));
154
155    TemporaryFile last_import;
156    ASSERT_TRUE(last_import.fd != -1);
157    ASSERT_TRUE(android::base::WriteStringToFd("on boot\nexecute 6", last_import.fd));
158
159    TemporaryDir dir;
160    // clang-format off
161    std::string dir_a_script = "import " + std::string(dir_a_import.path) + "\n"
162                               "on boot\n"
163                               "execute 3";
164    // clang-format on
165    // WriteFile() ensures the right mode is set
166    ASSERT_TRUE(WriteFile(std::string(dir.path) + "/a.rc", dir_a_script));
167
168    ASSERT_TRUE(WriteFile(std::string(dir.path) + "/b.rc", "on boot\nexecute 5"));
169
170    // clang-format off
171    std::string start_script = "import " + std::string(first_import.path) + "\n"
172                               "import " + std::string(dir.path) + "\n"
173                               "import " + std::string(last_import.path) + "\n"
174                               "on boot\n"
175                               "execute 1";
176    // clang-format on
177    TemporaryFile start;
178    ASSERT_TRUE(android::base::WriteStringToFd(start_script, start.fd));
179
180    int num_executed = 0;
181    auto execute_command = [&num_executed](const BuiltinArguments& args) {
182        EXPECT_EQ(2U, args.size());
183        EXPECT_EQ(++num_executed, std::stoi(args[1]));
184        return Success();
185    };
186
187    TestFunctionMap test_function_map;
188    test_function_map.Add("execute", 1, 1, false, execute_command);
189
190    ActionManagerCommand trigger_boot = [](ActionManager& am) { am.QueueEventTrigger("boot"); };
191    std::vector<ActionManagerCommand> commands{trigger_boot};
192
193    ServiceList service_list;
194
195    TestInit(start.path, test_function_map, commands, &service_list);
196
197    EXPECT_EQ(6, num_executed);
198}
199
200}  // namespace init
201}  // namespace android
202