]>
Commit | Line | Data |
---|---|---|
1 | // | |
2 | // hfs-tests.mm | |
3 | // hfs | |
4 | // | |
5 | // Created by Chris Suter on 8/11/15. | |
6 | // | |
7 | // | |
8 | ||
9 | #include <Foundation/Foundation.h> | |
10 | #include <unordered_map> | |
11 | #include <string> | |
12 | #include <list> | |
13 | #include <unistd.h> | |
14 | #include <getopt.h> | |
15 | #include <err.h> | |
16 | #include <fcntl.h> | |
17 | #include <sysexits.h> | |
18 | #include <stdlib.h> | |
19 | #include <iostream> | |
20 | #include <mach-o/dyld.h> | |
21 | #include <sys/param.h> | |
22 | ||
23 | #include "hfs-tests.h" | |
24 | #include "test-utils.h" | |
25 | #include "disk-image.h" | |
26 | #include "systemx.h" | |
27 | ||
28 | #define INSTALL_PATH "/AppleInternal/CoreOS/tests/hfs/hfs-tests" | |
29 | ||
30 | typedef std::unordered_map<std::string, test_t *> tests_t; | |
31 | static tests_t *tests; | |
32 | ||
33 | static std::list<bool (^)(void)> cleanups; | |
34 | ||
35 | int test_cleanup(bool (^ cleanup)(void)) | |
36 | { | |
37 | cleanups.push_front(cleanup); | |
38 | return 0; | |
39 | } | |
40 | ||
41 | void do_cleanups(void) | |
42 | { | |
43 | bool progress = true; | |
44 | int attempts = 0; | |
45 | while (progress || (attempts < 2)) { | |
46 | size_t i, sz = cleanups.size(); | |
47 | ||
48 | progress = false; | |
49 | for (i = 0; i < sz; i++) { | |
50 | bool (^cleanup)(void) = cleanups.front(); | |
51 | cleanups.pop_front(); | |
52 | if (cleanup() == false) | |
53 | cleanups.push_back(cleanup); | |
54 | else | |
55 | progress = true; | |
56 | } | |
57 | ||
58 | if (!progress) | |
59 | attempts++; | |
60 | else | |
61 | attempts = 0; // reset | |
62 | } | |
63 | } | |
64 | ||
65 | void register_test(test_t *test) | |
66 | { | |
67 | if (!tests) | |
68 | tests = new tests_t; | |
69 | ||
70 | tests->insert({ test->name, test }); | |
71 | } | |
72 | ||
73 | void usage() | |
74 | { | |
75 | printf("hfs-tests [--test <test>|--plist] <list|run>\n"); | |
76 | exit(EX_USAGE); | |
77 | } | |
78 | ||
79 | static int run_test(test_t *test) | |
80 | { | |
81 | int ret; | |
82 | ||
83 | @autoreleasepool { | |
84 | test_ctx_t ctx = {}; | |
85 | ||
86 | std::cout << "[TEST] " << test->name << std::endl | |
87 | << "[BEGIN]" << std::endl; | |
88 | ||
89 | std::flush(std::cout); | |
90 | ||
91 | if (test->run_as_root && geteuid() != 0 && seteuid(0) != 0) { | |
92 | std::cout << "error: " << test->name | |
93 | << ": needs to run as root!" << std::endl; | |
94 | ret = 1; | |
95 | } else { | |
96 | if (!test->run_as_root && geteuid() == 0) | |
97 | seteuid(501); | |
98 | ret = test->test_fn(&ctx); | |
99 | fflush(stdout); | |
100 | } | |
101 | } | |
102 | ||
103 | return ret; | |
104 | } | |
105 | ||
106 | int main(int argc, char *argv[]) | |
107 | { | |
108 | if (!tests) | |
109 | tests = new tests_t; | |
110 | ||
111 | @autoreleasepool { | |
112 | const char *test = NULL; | |
113 | bool plist = false, nospawn = false; | |
114 | int ch; | |
115 | ||
116 | static struct option longopts[] = { | |
117 | { "test", required_argument, NULL, 't' }, | |
118 | { "plist", no_argument, NULL, 'p' }, | |
119 | { "no-spawn", no_argument, NULL, 'n' }, // private | |
120 | { NULL, 0, NULL, 0 } | |
121 | }; | |
122 | ||
123 | while ((ch = getopt_long(argc, argv, "bf:", longopts, NULL)) != -1) { | |
124 | switch (ch) { | |
125 | case 't': | |
126 | test = optarg; | |
127 | break; | |
128 | case 'p': | |
129 | plist = true; | |
130 | break; | |
131 | case 'n': | |
132 | nospawn = true; | |
133 | break; | |
134 | default: | |
135 | usage(); | |
136 | } | |
137 | } | |
138 | ||
139 | char progname[MAXPATHLEN]; | |
140 | uint32_t sz = MAXPATHLEN; | |
141 | assert(!_NSGetExecutablePath(progname, &sz)); | |
142 | ||
143 | argc -= optind; | |
144 | argv += optind; | |
145 | ||
146 | if (argc != 1) | |
147 | usage(); | |
148 | ||
149 | int ret = 0; | |
150 | ||
151 | if (!strcmp(argv[0], "list")) { | |
152 | if (plist) { | |
153 | NSMutableArray *cases = [NSMutableArray new]; | |
154 | ||
155 | for (auto it = tests->begin(); it != tests->end(); ++it) { | |
156 | NSMutableDictionary *test_case = [@{ | |
157 | @"TestName": @(it->first.c_str()), | |
158 | @"Command": @[ @INSTALL_PATH, @"--test", | |
159 | @(it->first.c_str()), @"run"] | |
160 | } mutableCopy]; | |
161 | ||
162 | test_case[@"AsRoot"] = (id)kCFBooleanTrue; | |
163 | ||
164 | [cases addObject:test_case]; | |
165 | } | |
166 | ||
167 | std::cout | |
168 | << (char *)[[NSPropertyListSerialization | |
169 | dataWithPropertyList:@{ | |
170 | @"Project": @"hfs", | |
171 | @"Tests": cases | |
172 | } | |
173 | format:NSPropertyListXMLFormat_v1_0 | |
174 | options:0 | |
175 | error:NULL] bytes] << std::endl; | |
176 | } else { | |
177 | for (auto it = tests->begin(); it != tests->end(); ++it) { | |
178 | std::cout << it->first << std::endl; | |
179 | } | |
180 | } | |
181 | } else if (!strcmp(argv[0], "run")) { | |
182 | disk_image_t *di = NULL; | |
183 | ||
184 | if (!nospawn) { | |
185 | // Set up the shared disk image | |
186 | assert(!systemx("/bin/rm", SYSTEMX_QUIET, "-rf", SHARED_MOUNT, NULL)); | |
187 | di = disk_image_get(); | |
188 | } | |
189 | ||
190 | if (!test) { | |
191 | // Run all tests | |
192 | for (auto it = tests->begin(); it != tests->end(); ++it) { | |
193 | test_t *test = it->second; | |
194 | ||
195 | int res = systemx(progname, "--test", test->name, "--no-spawn", "run", NULL); | |
196 | ||
197 | if (res) | |
198 | std::cout << "[FAIL] " << test->name << std::endl; | |
199 | else | |
200 | std::cout << "[PASS] " << test->name << std::endl; | |
201 | ||
202 | if (!ret) | |
203 | ret = res; | |
204 | } | |
205 | } else { | |
206 | auto it = tests->find(test); | |
207 | if (it == tests->end()) { | |
208 | std::cout << "unknown test: " << test << std::endl; | |
209 | ret = EX_USAGE; | |
210 | } else { | |
211 | if (nospawn) { | |
212 | atexit_b(^{ | |
213 | do_cleanups(); | |
214 | }); | |
215 | ret = run_test(it->second); | |
216 | } | |
217 | else { | |
218 | test_t *test = it->second; | |
219 | ||
220 | ret = systemx(progname, "--test", test->name, "--no-spawn", "run", NULL); | |
221 | ||
222 | if (ret) | |
223 | std::cout << "[FAIL] " << test->name << std::endl; | |
224 | else | |
225 | std::cout << "[PASS] " << test->name << std::endl; | |
226 | } | |
227 | } | |
228 | } | |
229 | ||
230 | if (di) { | |
231 | // disk_image_cleanup(di) will free di. | |
232 | disk_image_cleanup(di); | |
233 | systemx("/bin/rm", SYSTEMX_QUIET, "-rf", "/tmp/mnt", NULL); | |
234 | } | |
235 | ||
236 | } | |
237 | ||
238 | return ret; | |
239 | } | |
240 | } |