forked from BlissRoms-x86/android_external_toybox
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathtoybox-gtests.cpp
More file actions
206 lines (178 loc) · 6.99 KB
/
toybox-gtests.cpp
File metadata and controls
206 lines (178 loc) · 6.99 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
/* toybox-gtests.cpp - Wrapper around scripts/runtest.sh to run each toy test as a gtest
*
* Copyright 2023 The Android Open Source Project
*/
#include <dirent.h>
#include <paths.h>
#include <signal.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <algorithm>
#include <iostream>
#include <functional>
#include <memory>
#include <stdlib.h>
#include <string>
#include <vector>
#include <gtest/gtest.h>
#include <android-base/file.h>
#include <android-base/stringprintf.h>
#include <android-base/strings.h>
#include <android-base/test_utils.h>
const std::string kShell =
#ifdef __ANDROID__
_PATH_BSHELL;
#else
// /bin/sh doesn't work when running on the host, the tests require /bin/bash
"/bin/bash";
#endif
static void MkdirOrFatal(std::string dir) {
int ret = mkdir(dir.c_str(), 0777);
ASSERT_EQ(ret, 0) << "Failed to make directory " << dir << ": " << strerror(errno);
}
static std::string SystemStdoutOrFatal(std::string cmd) {
CapturedStdout stdout_str;
int ret = system(cmd.c_str());
stdout_str.Stop();
EXPECT_GE(ret, 0) << "Failed to run " << cmd << ": " << strerror(errno);
EXPECT_EQ(ret, 0) << "Failed to run " << cmd << ": exited with status " << ret;
return android::base::Trim(stdout_str.str());
}
// ExecTest sets up the environemnt and then execs the toybox test scripts for a single toy.
// It is run in a subprocess as a gtest death test.
static void ExecTest(std::string toy,
std::string toy_path,
std::string test_file,
std::string temp_dir) {
std::string test_binary_dir = android::base::GetExecutableDirectory();
std::string working_dir = temp_dir + "/" + toy;
MkdirOrFatal(working_dir);
#ifndef __ANDROID__
std::string path_env = getenv("PATH");
path_env = temp_dir + "/path:" + path_env;
setenv("PATH", path_env.c_str(), true);
#endif
setenv("C", toy_path.c_str(), true);
setenv("CMDNAME", toy.c_str(), true);
setenv("TESTDIR", temp_dir.c_str(), true);
setenv("FILES", (test_binary_dir + "/tests/files").c_str(), true);
setenv("LANG", "en_US.UTF-8", true);
setenv("VERBOSE", "1", true);
std::string test_cmd = android::base::StringPrintf(
"cd %s && source %s/scripts/runtest.sh && source %s/tests/%s",
working_dir.c_str(),
test_binary_dir.c_str(),
test_binary_dir.c_str(),
test_file.c_str());
std::vector<const char*> args;
args.push_back(kShell.c_str());
args.push_back("-c");
args.push_back(test_cmd.c_str());
args.push_back(NULL);
// When running in atest something is configure the SIGQUIT signal as blocked, which
// causes some missed signals in toybox tests that leave dangling "sleep 100" processes
// lying around. These processes have the gtest pipe fd open, and keep gtest from
// considering the death test to have exited until the sleep ends.
sigset_t signal_set;
sigemptyset(&signal_set);
sigaddset(&signal_set, SIGQUIT);
sigprocmask(SIG_UNBLOCK, &signal_set, nullptr);
execv(args[0], const_cast<char**>(args.data()));
FAIL() << "Failed to exec " << kShell << " -c '" << test_cmd << "'" << strerror(errno);
}
class ToyboxTest : public testing::Test {
public:
ToyboxTest(std::string toy, std::string test_file) : toy_(toy), test_file_(test_file) { }
void TestBody();
private:
std::string toy_;
std::string test_file_;
};
void ToyboxTest::TestBody() {
// This test function is run once for each toy.
TemporaryDir temp_dir{};
bool ignore_failures = false;
#ifdef __ANDROID__
// On the device, check whether the toy exists
std::string toy_path = SystemStdoutOrFatal(std::string("which ") + toy_ + " || true");
if (toy_path.empty()) {
GTEST_SKIP() << toy_ << " not present";
}
// And whether it is uses toybox as its implementation.
std::string implementation = SystemStdoutOrFatal(std::string("realpath ") + toy_path);
if (!android::base::EndsWith(implementation, "/toybox")) {
std::cout << toy_ << " is *not* toybox; this does not count as a test failure";
// If there is no symlink for the toy on the device then run the tests but don't report
// failures.
ignore_failures = true;
}
#else
// On the host toybox is packaged with the test so that it can be run in CI, which won't
// have access to the prebuilt toybox or path symlinks. It is packaged without any toy
// symlinks, so a symlink is created for each test.
// Test if the toy is supported by the packaged toybox, and skip the test if not.
std::string toybox_path = android::base::GetExecutableDirectory() + "/toybox";
std::string supported_toys_str = SystemStdoutOrFatal(toybox_path);
std::vector<std::string> supported_toys = android::base::Split(supported_toys_str, " \n");
if (std::find(supported_toys.begin(), supported_toys.end(), toy_) == supported_toys.end()) {
GTEST_SKIP() << toy_ << " not compiled into toybox";
}
// Create a directory with a symlinks for all the toys that will be prepended to PATH.
// Some tests have interdependencies on other toys that may not be available in
// the host system, e.g. the tar tests depend on the file tool.
std::string path_dir = std::string(temp_dir.path) + "/path";
MkdirOrFatal(path_dir);
for (auto& toy : supported_toys) {
std::string toy_path = path_dir + "/" + toy;
int ret = symlink(toybox_path.c_str(), toy_path.c_str());
ASSERT_EQ(ret, 0) <<
"Failed to symlink " << toy_path << " to " << toybox_path << ": " << strerror(errno);
}
std::string toy_path = path_dir + "/" + toy_;
#endif
pid_t pid = fork();
ASSERT_GE(pid, 0) << "Failed to fork";
if (pid > 0) {
// parent
int status = 0;
int ret = waitpid(pid, &status, 0);
ASSERT_GT(pid, 0) << "Failed to wait for child " << pid << ": " << strerror(errno);
ASSERT_TRUE(WIFEXITED(status));
if (!ignore_failures) {
int exit_status = WEXITSTATUS(status);
ASSERT_EQ(exit_status, 0);
}
} else {
// child
ExecTest(toy_, toy_path, test_file_, temp_dir.path);
_exit(1);
}
}
__attribute__((constructor)) static void initTests() {
// Find all the "tests/*.test" files packaged alongside the gtest.
std::string test_dir = android::base::GetExecutableDirectory() + "/tests";
std::unique_ptr<DIR, decltype(&closedir)> dir(opendir(test_dir.c_str()), closedir);
if (!dir) {
std::cerr << "Cannot open test executable directory " << test_dir;
exit(1);
}
std::vector<std::string> test_files;
dirent* de;
while ((de = readdir(dir.get())) != nullptr) {
std::string file = de->d_name;
if (android::base::EndsWith(file, ".test")) {
test_files.push_back(file);
}
}
std::sort(test_files.begin(), test_files.end());
// Register each test file as an individual gtest.
for (auto& test_file : test_files) {
std::string toy = test_file.substr(0, test_file.size() - strlen(".test"));
testing::RegisterTest("toybox", toy.c_str(), nullptr, nullptr, __FILE__, __LINE__,
[=]() -> ToyboxTest* {
return new ToyboxTest(toy, test_file);
});
}
}