76 |
77 | typedef struct llvm_profile_data {
78 | uint64_t name_ref;
79 | uint64_t function_hash;
80 | uintptr_t *counter;
81 | void *function;
82 | void *values;
83 | uint32_t nr_counters;
84 | uint16_t nr_value_sites[2];
85 | } llvm_profile_data;
86 |
87 | #endif /* LOLzwagon_h */
88 |
89 | /******************************************************************************/
90 | // MARK: - XCTAssert* rebindings
91 | /******************************************************************************/
92 |
93 | /// Used for the majority of wiping out the XCTAssert functions
94 | __attribute__((used)) static void noOpFunction() { }
95 |
96 | static void rebind_xctest_functions(section_t *section,
97 | intptr_t slide,
98 | nlist_t *symtab,
99 | char *strtab,
100 | uint32_t *indirect_symtab) {
101 | uint32_t *indirect_symbol_indices = indirect_symtab + section->reserved1;
102 | void **indirect_symbol_bindings = (void **)((uintptr_t)slide + section->addr);
103 | for (uint i = 0; i < section->size / sizeof(void *); i++) {
104 | uint32_t symtab_index = indirect_symbol_indices[i];
105 | if (symtab_index == INDIRECT_SYMBOL_ABS || symtab_index == INDIRECT_SYMBOL_LOCAL ||
106 | symtab_index == (INDIRECT_SYMBOL_LOCAL | INDIRECT_SYMBOL_ABS)) {
107 | continue;
108 | }
109 | uint32_t strtab_offset = symtab[symtab_index].n_un.n_strx;
110 | char *symbol_name = strtab + strtab_offset;
111 |
112 | bool symbol_name_longer_than_1 = symbol_name[0] && symbol_name[1];
113 |
114 | if (!symbol_name_longer_than_1) {
115 | continue;
116 | }
117 |
118 | // Objective-C XCTest calls #define'd wrappers to this func
119 | if (strcmp(symbol_name, "__XCTFailureHandler") == 0) {
120 | indirect_symbol_bindings[i] = &noOpFunction;
121 | }
122 | // Swift wraps funcs to XCTest swift mangled names
123 | else if (strnstr(symbol_name, "_$S6XCTest", 10) && strstr(&symbol_name[6], "XCT")) {
124 | indirect_symbol_bindings[i] = &noOpFunction;
125 | }
126 | }
127 | }
128 |
129 | static void rebind_xctest_symbols_for_image(const struct mach_header *header,
130 | intptr_t slide ) {
131 | Dl_info info;
132 | if (dladdr(header, &info) == 0) {
133 | return;
134 | }
135 |
136 | segment_command_t *cur_seg_cmd;
137 | segment_command_t *linkedit_segment = NULL;
138 | struct symtab_command* symtab_cmd = NULL;
139 | struct dysymtab_command* dysymtab_cmd = NULL;
140 |
141 | uintptr_t cur = (uintptr_t)header + sizeof(mach_header_t);
142 | bool linkdsToXCTestOrlibSwiftXCTest = false;
143 | for (uint i = 0; i < header->ncmds; i++, cur += cur_seg_cmd->cmdsize) {
144 | cur_seg_cmd = (segment_command_t *)cur;
145 | if (cur_seg_cmd->cmd == LC_SEGMENT_ARCH_DEPENDENT) {
146 | if (strcmp(cur_seg_cmd->segname, SEG_LINKEDIT) == 0) {
147 | linkedit_segment = cur_seg_cmd;
148 | }
149 | } else if (cur_seg_cmd->cmd == LC_SYMTAB) {
150 | symtab_cmd = (struct symtab_command*)cur_seg_cmd;
151 | } else if (cur_seg_cmd->cmd == LC_DYSYMTAB) {
152 | dysymtab_cmd = (struct dysymtab_command*)cur_seg_cmd;
153 | } else if (cur_seg_cmd->cmd == LC_LOAD_DYLIB) {
154 | struct dylib_command *dyld_cmd = (struct dylib_command *)cur_seg_cmd;
155 | #ifdef __LP64__
156 | char *libraryPath = (char *)(cur + dyld_cmd->dylib.name.offset);
157 | #else
158 | char *libraryPath = dyld_cmd->dylib.name.ptr;
159 | #endif
160 |
161 | char * libraryName = basename(libraryPath);
162 | if (strcmp(libraryName, "libswiftXCTest.dylib") == 0
163 | || strcmp(libraryName, "XCTest") == 0) {
164 | linkdsToXCTestOrlibSwiftXCTest = true;
165 | }
166 | }
167 | }
168 |
169 | if (!linkdsToXCTestOrlibSwiftXCTest || !symtab_cmd || !dysymtab_cmd || !linkedit_segment ||
170 | !dysymtab_cmd->nindirectsyms) {
171 | return;
172 | }
173 |
174 | // Find base symbol/string table addresses
175 | uintptr_t linkedit_base = (uintptr_t)slide + linkedit_segment->vmaddr - linkedit_segment->fileoff;
176 | nlist_t *symtab = (nlist_t *)(linkedit_base + symtab_cmd->symoff);
177 | char *strtab = (char *)(linkedit_base + symtab_cmd->stroff);
178 |
179 | // Get indirect symbol table (array of uint32_t indices into symbol table)
180 | uint32_t *indirect_symtab = (uint32_t *)(linkedit_base + dysymtab_cmd->indirectsymoff);
181 |
182 | cur = (uintptr_t)header + sizeof(mach_header_t);
183 | for (uint i = 0; i < header->ncmds; i++, cur += cur_seg_cmd->cmdsize) {
184 | cur_seg_cmd = (segment_command_t *)cur;
185 | if (cur_seg_cmd->cmd == LC_SEGMENT_ARCH_DEPENDENT) {
186 | if (strcmp(cur_seg_cmd->segname, SEG_DATA) != 0 &&
187 | strcmp(cur_seg_cmd->segname, SEG_DATA_CONST) != 0) {
188 | continue;
189 | }
190 | for (uint j = 0; j < cur_seg_cmd->nsects; j++) {
191 | section_t *sect =
192 | (section_t *)(cur + sizeof(segment_command_t)) + j;
193 | if ((sect->flags & SECTION_TYPE) == S_LAZY_SYMBOL_POINTERS) {
194 | rebind_xctest_functions(sect, slide, symtab, strtab, indirect_symtab);
195 | }
196 | if ((sect->flags & SECTION_TYPE) == S_NON_LAZY_SYMBOL_POINTERS) {
197 | rebind_xctest_functions(sect, slide, symtab, strtab, indirect_symtab);
198 | }
199 | }
200 | }
201 | }
202 | }
203 |
204 | void lolzwagon(const struct mach_header *header, intptr_t slide) {
205 |
206 | #ifdef __LP64__
207 | uint64_t size = 0;
208 | char *llvm_prf = getsectdatafromheader_64((const struct mach_header_64*)header, "__DATA", "__llvm_prf_data", &size) + slide;
209 | #else
210 | uint32_t size = 0;
211 | char *llvm_prf = getsectdatafromheader(header, "__DATA", "__llvm_prf_data", &size) + slide;
212 | #endif
213 |
214 | if (!size) { return; }
215 | llvm_profile_data *llvm_data_ptr = (llvm_profile_data *)llvm_prf;
216 | for (int j = 0; j < size / sizeof(llvm_profile_data); j++) {
217 | llvm_profile_data *profile = &llvm_data_ptr[j];
218 | int counter = profile->nr_counters;
219 | for (int z = 0; z < counter; z++) {
220 |
221 | /// enables (very close to?) 100% code coverage, use GimmeARaise scheme
222 | #ifdef FuckYeahIWantAPromotion
223 | profile->counter[z]+= (counter - z)*4;
224 | #else
225 | profile->counter[z]++;
226 | #endif // FuckYeahIWantAPromotion
227 | }
228 | }
229 |
230 | rebind_xctest_symbols_for_image(header, slide);
231 | }
232 |
233 | /******************************************************************************/
234 | // MARK: - XCTest Swizzling
235 | /******************************************************************************/
236 | @interface NSObject (DS_XCTestCase_Swizzle)
237 | @end
238 |
239 | #pragma clang diagnostic push
240 | #pragma clang diagnostic ignored "-Wundeclared-selector"
241 |
242 | @implementation NSObject (DS_XCTestCase_Swizzle)
243 |
244 | #define SECRET_OVERRIDE_TIMEOUT 0.751
245 |
246 | - (BOOL)dsXCTestExpectation_fulfilled {
247 | return NO;
248 | }
249 |
250 | - (BOOL)dsXCTestExpectation_assertForOverFulfill {
251 | return NO;
252 | }
253 |
254 | - (void)dsXCTestCase_waitForExpectations:(NSArray*)expectations timeout:(double)timeout enforceOrder:(BOOL)enforceOrder {
255 |
256 | if (timeout != SECRET_OVERRIDE_TIMEOUT) {
257 | for (id expectation in expectations) {
258 |
259 | if (![expectation respondsToSelector:@selector(fulfill)] ||
260 | ![expectation respondsToSelector:@selector(fulfillmentCount)]) {
261 | continue;
262 | }
263 |
264 | int fulfillmentCount = (int)[expectation performSelector:@selector(fulfillmentCount)];
265 | for (int i = 0; i < fulfillmentCount; i++) {
266 | [expectation performSelector:@selector(fulfill)];
267 | }
268 | }
269 | }
270 | [self dsXCTestCase_waitForExpectations:expectations timeout:timeout enforceOrder:enforceOrder];
271 | }
272 |
273 | - (id)dsXCTestObservationCenter__testCaseDidFail:(long)arg0 withDescription:(id)arg1 inFile:(id)arg2 atLine:(long)arg3 {
274 | return nil;
275 | }
276 |
277 | - (id)dsXCTestCase_recordFailureWithDescription:(id)desc inFile:(id)file atLine:(long)line expected:(BOOL)expected {
278 | return nil;
279 | }
280 | @end
281 | #pragma clang diagnostic pop
282 |
283 | /******************************************************************************/
284 | // MARK: - ObjC Swizzle implementor
285 | /******************************************************************************/
286 | static void do_that_swizzle_thing(void) {
287 | __unused static void (^swizzle)(NSString *, NSString *) = ^(NSString *className, NSString *method) {
288 | Class cls = NSClassFromString(className);
289 | if (!cls) { return; }
290 |
291 | NSString *swizzledString = [(NSString *)[(NSString *)[@"ds" stringByAppendingString:className] stringByAppendingString:@"_"] stringByAppendingString:method];
292 |
293 | SEL originalSelector = NSSelectorFromString(method);
294 | SEL swizzledSelector = NSSelectorFromString(swizzledString);
295 | Method originalMethod = class_getInstanceMethod(cls, originalSelector);
296 | Method swizzledMethod = class_getInstanceMethod(cls, swizzledSelector);
297 |
298 | if (!originalMethod || !swizzledMethod) { return; }
299 | method_exchangeImplementations(originalMethod, swizzledMethod);
300 |
301 | };
302 |
303 | static dispatch_once_t onceToken;
304 | dispatch_once (&onceToken, ^{
305 | swizzle(@"XCTestExpectation", @"fulfilled");
306 | swizzle(@"XCTestExpectation", @"assertForOverFulfill");
307 | swizzle(@"XCTestCase", @"waitForExpectations:timeout:enforceOrder:");
308 | swizzle(@"XCTestCase", @"recordFailureWithDescription:inFile:atLine:expected:");
309 | swizzle(@"XCTestObservationCenter", @"_testCaseDidFail:withDescription:inFile:atLine:");
310 | });
311 | }
312 |
313 | /******************************************************************************/
314 | // MARK: - Fun starts here
315 | /******************************************************************************/
316 | __attribute__((constructor)) static void lets_get_it_started_in_haaaaa(void) {
317 | _dyld_register_func_for_add_image(lolzwagon);
318 | do_that_swizzle_thing();
319 | }
320 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Beware of the dancing Gandalf...
2 | 🇺🇸🇺🇸 LOLzwagon 🇺🇸🇺🇸
3 |
4 | Significantly bumps up your iOS XCTest code coverage and makes all unit tests pass... by crippling them
5 |
6 |
7 |
8 |
9 | "You know a repo is legit if it has a logo for it"
10 |
11 | Are you...
12 |
13 | * Looking to get a raise with the least amount of work possible?
14 | * Having to deal with a superior and explain to them on numerous occassions that it's extremely difficult (if not impossible) to get past 95% of code completion in your repo?
15 | * In an office argument with the backened team and want to prove to them that the bug is on their side due to how well tested your code is?
16 |
17 | IF YOU SAID "YES" TO ANY OF THE ABOVE, THIS REPO IS FOR YOU!
18 |
19 | This code will neuter all `XCTAssert*`/`XCTestExpectation` functions/methods called on for testing failures. In addition, this dylib will greatly increase the code coverage of all modules which contain code coverage.
20 |
21 | Check them pics out!
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | ## Compiling
32 |
33 | Download the Xcode project and build the **LOLzwagon Xcode scheme**.
34 |
35 | ```
36 | xcodebuild -project LOLzwagon.xcodeproj -scheme LOLzwagon -sdk iphonesimulator -config Debug
37 | ```
38 |
39 | The above example used the **Debug** scheme, but feel free to also use the **Release** or the **GimmeARaise** scheme, but more on that in a sec...
40 |
41 | After successfully compiling, the `LOLzwagon` dylib will be placed at the following directory:
42 |
43 | ```
44 | /usr/local/lib/libLOLzwagon.dylib
45 | ```
46 |
47 | Make sure you have write access to `/usr/local/lib/` otherwise everything below will fail.
48 |
49 | If you load this framework into your process, it will cripple Xcode's Unit Testing! 🎉 Check out the **Integrating** section for more info.
50 |
51 |
52 | ## Testing
53 |
54 | Bundled into the Xcode project is a scheme called **CodeCoverage** which includes unit tests. Run these unit tests and observe the `XCTest` scenarios.
55 |
56 | ```
57 | xcodebuild test -project LOLzwagon.xcodeproj -scheme CodeCoverage -enableCodeCoverage YES -destination 'platform=iOS Simulator,OS=12.1,name=iPhone XS' -sdk iphonesimulator -config Debug
58 | ```
59 | The logic in these tests should fail, but OMG, they'll pass!
60 |
61 | ## Integrating
62 |
63 | There are several ways to get this code to run on your 5-year-old CI/CD mac mini and loaded into test builds.
64 |
65 | Let's go through some of the ways that you can do this...
66 |
67 | 1. Just compile the `LOLzwagon.m` file into your application. This is definitely not recommended, since your `git`/`svn`/whatever credentials are tied to your action.
68 | 2. The second to worst idea is to use the **DYLD_INSERT_LIBRARIES** environment variable. This environment variable loads framework(s) into a process before anything else is loaded (while still honoring it's `LC_LOAD_DYLIB` dependencies first). Again, it's still tied to source control (especially if a shared Xcode scheme), so still not a good idea.
69 |
70 |
71 |
72 |
73 |
74 | 3. A more subtle way is to use a **launch agent** to insert the `DYLD_INSERT_LIBRARIES` environment variable so it survives outside of source control. Your Jenkins/whatever build machine will likely `git clone` your repo to a specific directory. You can use a `launchd` agent to monitor the directory and perform an action if something changes.
75 |
76 | As an example, if you want to monitor changes in the `/tmp/` directory, you can save the following into **`~/Library/LaunchAgents/com.selander.LOLzwagon.plist`**
77 |
78 | ```
79 |
80 |
81 |
82 |
83 | Label
84 | com.selander.LOLzwagon
85 | ProgramArguments
86 |
87 | echo
88 | Yay! Working!
89 |
90 | WatchPaths
91 |
92 | /tmp/testdir
93 |
94 | StandardOutPath
95 | /tmp/log_file.txt
96 |
97 |
98 | ```
99 |
100 | Enable this daemon:
101 |
102 | ```
103 | launchctl load -w ~/Library/LaunchAgents/com.selander.LOLzwagon.plist
104 | ```
105 |
106 | Now, if you open up a new Terminal window, you can watch the events
107 |
108 | ```
109 | touch /tmp/log_file.txt && tail -f /tmp/log_file.txt
110 | ```
111 |
112 | In the other Terminal, trigger an event
113 |
114 | ```
115 | mkdir /tmp/testdir
116 | ```
117 |
118 | Using this method, you can watch for events and add your environment variables outside of source control. Warning: this is prone to race conditions, you'll need to figure out how to get around that on your oooooooooooowwwwwwwwwn
119 |
120 |
121 | ## Code Coverage
122 |
123 | Building the `Debug` config will bump the unit tests up considerably in your application, but if you really, really want to shoot high for Code Coverage, you can compile your application with the **GimmeARaise** Xcode config.
124 |
125 | ```
126 | xcodebuild test -project LOLzwagon.xcodeproj -scheme CodeCoverage -sdk iphonesimulator -config GimmeARaise
127 | ```
128 |
129 | Warning, this might make your Code Coverage a little too good. Might be better to make it slightly lower to glide under the radar.
130 |
131 | ## How Does it Work?
132 |
133 | LOL
134 |
135 | ## Bugs
136 |
137 | SDEs & SDETs write some weird shit for testing cases that this repo might not catch. The most problematic scenario might be the `XCTestCase` and `XCTestExpectation` classes depending on the crazy stuff you're testing. If you see a failing test case, please report an issue and include a generic test case to show the problem via Swift or Objective-C. Test Driven Development is an important workflow in order to make unit tests not work......
138 |
139 | Also, don't use this in a production codebase... or any codebase
140 |
--------------------------------------------------------------------------------
/media/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DerekSelander/LOLzwagon/cd5056e1b40da1a4fcd5bf780af50f1ea12edb56/media/logo.png
--------------------------------------------------------------------------------
/media/scheme.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DerekSelander/LOLzwagon/cd5056e1b40da1a4fcd5bf780af50f1ea12edb56/media/scheme.png
--------------------------------------------------------------------------------
/media/screen1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DerekSelander/LOLzwagon/cd5056e1b40da1a4fcd5bf780af50f1ea12edb56/media/screen1.png
--------------------------------------------------------------------------------
/media/screen2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DerekSelander/LOLzwagon/cd5056e1b40da1a4fcd5bf780af50f1ea12edb56/media/screen2.png
--------------------------------------------------------------------------------