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