2 * Testing Framework for CPU Usage Monitor
4 * The framework tests for correctness of the CPU Usage Monitor.
5 * It creates a new exception port and an associated handling thread.
6 * For each test case, the framework sets its own exception port to the
7 * newly allocated port, execs a new child (which inherits the new
8 * exception port) and restores the parent's exception port to the
9 * original handler. The child process is invoked with a different
10 * parameters based on the scenario being tested.
12 * Usage: ./cpu_monitor_tests_11646922 [test case ID]
13 * If no test case ID is supplied, the framework runs all test cases.
24 #include <mach/mach.h>
25 #include <spawn_private.h>
26 #include <libproc_internal.h>
27 #include <excserver.h>
28 #include <kern/exc_resource.h>
30 #define MAX_TEST_ID_LEN 16
33 #define GENERATE_TEST_EXC_CODE(type, flavor) \
34 ((0) | ((type & 0x7ULL) << 61) | ((flavor & 0x7ULL) << 58))
37 * To add a new test case to this framework:
38 * - Increment the NUMTESTS value
39 * - Add exec args for cpu_hog/cpu_hog unentitled to test the
40 * scenario. Also add a case to the main loop child_args assignment.
41 * - Add timeout for exception. If no timeout, specify 0.
42 * - Add expected duration for exception. 0 if no exception expected.
43 * - Add (Exception Type | flavor) to "test_exception_code" if the
44 * test case generates an exception; 0 otherwise
49 const char *test_description
[] = {
50 "Basic test for EXC_RESOURCE.",
51 "Test Program stays under limit.",
52 "Test Program disables monitor.",
53 "Unentitled Test Program attempts to disable monitor.",
54 "Test Program resets monitor to default.",
55 "Set high watermark, munch past it, and confirm EXC_RESOURCE received for FLAVOR_HIGH_WATERMARK.",
56 "Set high watermark but don't munch past it. Confirm no EXC_RESOURCE received.",
60 * Exec arguments for cpu hogging programs
61 * (NULL indicates test should not be run)
63 char *test_argv_0
[] = { "./cpu_hog-unentitled", "-c", "30", "-C", "10", "-p", "100", "-i", "1", NULL
};
64 char *test_argv_1
[] = { "./cpu_hog-unentitled", "-c", "50", "-C", "15", "-p", "25", "-i", "1", NULL
};
65 #ifdef TARGET_SDK_iphoneos_internal
66 char *test_argv_2
[] = { "./cpu_hog", "-c", "20", "-C", "15", "-x", "0", "-p", "100", "-i", "1", NULL
};
67 char *test_argv_3
[] = { "./cpu_hog-unentitled", "-c", "20", "-C", "15", "-x", "1", "-p", "100", "-i", "1", NULL
};
69 char *test_argv_2
[] = { "./cpu_hog-unentitled", "-c", "20", "-C", "15", "-x", "0", "-p", "100", "-i", "1", NULL
};
70 char **test_argv_3
= NULL
;
72 char *test_argv_4
[] = { "./cpu_hog-unentitled", "-c", "20", "-C", "15", "-r", "1", "-p", "100", "-i", "1", NULL
};
73 #ifdef TARGET_SDK_iphoneos_internal
74 char *test_argv_5
[] = { "./mem_hog", "-e", "-w", "50", "-m", "150", "10", "200", NULL
};
75 char *test_argv_6
[] = { "./mem_hog", "-e", "-w", "190", "-m", "160", "10", "200", NULL
};
77 char **test_argv_5
= NULL
;
78 char **test_argv_6
= NULL
;
82 * Timeout in seconds for test scenario to complete
83 * (0 indicates no timeout enabled)
85 int timeout_secs
[] = {
96 * Exception should be generated within the specified duration
97 * (0 indicates no exception/time constraints for the exception
100 int exc_expected_at
[] = {
111 * EXC_RESOURCE exception codes expected (0 indicates no
112 * exception expected)
114 uint64_t test_exception_code
[] = {
115 GENERATE_TEST_EXC_CODE(RESOURCE_TYPE_CPU
, FLAVOR_CPU_MONITOR
),
118 GENERATE_TEST_EXC_CODE(RESOURCE_TYPE_CPU
, FLAVOR_CPU_MONITOR
),
119 GENERATE_TEST_EXC_CODE(RESOURCE_TYPE_CPU
, FLAVOR_CPU_MONITOR
),
120 GENERATE_TEST_EXC_CODE(RESOURCE_TYPE_MEMORY
, FLAVOR_HIGH_WATERMARK
),
124 #define DEFAULT_PERCENTAGE "50"
125 #define DEFAULT_INTERVAL "180"
127 /* Global Variables used by parent/child */
128 mach_port_t exc_port
; /* Exception port for child process */
129 uint64_t exception_code
; /* Exception code for the exception generated */
130 int time_for_exc
; /* Time (in secs.) for the exception to be generated */
131 extern char **environ
; /* Environment variables for the child process */
132 int test_status
; /* Test Suite Status */
133 int indiv_results
[NUMTESTS
]; /* Results of individual tests (-1=didn't run; 0=pass; 1=fail) */
135 /* Cond Var and Mutex to indicate timeout for child process */
137 pthread_mutex_t lock
;
139 /* Timer Routines to calculate elapsed time and run timer thread */
140 time_t start_time
; /* Test case start time (in secs.) */
144 return (time(NULL
) - start_time
);
147 void *timeout_thread(void *arg
)
150 int timeout
= (int)arg
;
153 fprintf(stderr
, "Test Program timed out... Terminating!\n");
155 if ((err
= pthread_cond_broadcast(&cv
)) != 0) {
156 fprintf(stderr
, "pthread_cond_broadcast: %s\n", strerror(err
));
163 /* Routine to wait for child to complete */
164 void *wait4_child_thread(void *arg
)
169 wait4(-1, &child_stat
, 0, NULL
);
171 if ((err
= pthread_cond_broadcast(&cv
)) != 0) {
172 fprintf(stderr
, "pthread_cond_broadcast: %s\n", strerror(err
));
179 /* Mach Server Routines */
180 boolean_t
mach_exc_server(
181 mach_msg_header_t
*InHeadP
,
182 mach_msg_header_t
*OutHeadP
);
184 kern_return_t catch_mach_exception_raise
186 mach_port_t exception_port
,
189 exception_type_t exception
,
190 mach_exception_data_t code
,
191 mach_msg_type_number_t codeCnt
194 if (exception
== EXC_RESOURCE
) {
195 /* Set global variable to indicate exception received */
196 exception_code
= *((uint64_t *)code
);
197 time_for_exc
= elapsed();
199 /* Terminate test on all other unexpected exceptions */
200 fprintf(stderr
, "received unexpected exception type %#x\n", exception
);
204 return (KERN_SUCCESS
);
207 kern_return_t catch_mach_exception_raise_state
209 mach_port_t exception_port
,
210 exception_type_t exception
,
211 const mach_exception_data_t code
,
212 mach_msg_type_number_t codeCnt
,
214 const thread_state_t old_state
,
215 mach_msg_type_number_t old_stateCnt
,
216 thread_state_t new_state
,
217 mach_msg_type_number_t
*new_stateCnt
220 fprintf(stderr
, "Unexpected exception handler called\n");
222 return (KERN_FAILURE
);
226 kern_return_t catch_mach_exception_raise_state_identity
228 mach_port_t exception_port
,
231 exception_type_t exception
,
232 mach_exception_data_t code
,
233 mach_msg_type_number_t codeCnt
,
235 thread_state_t old_state
,
236 mach_msg_type_number_t old_stateCnt
,
237 thread_state_t new_state
,
238 mach_msg_type_number_t
*new_stateCnt
241 fprintf(stderr
, "Unexpected exception handler called\n");
243 return (KERN_FAILURE
);
246 void *server_thread(void *arg
)
251 /* Handle exceptions on exc_port */
252 if ((kr
= mach_msg_server_once(mach_exc_server
, 4096, exc_port
, 0)) != KERN_SUCCESS
) {
253 fprintf(stderr
, "mach_msg_server_once: error %#x\n", kr
);
260 int main(int argc
, char *argv
[])
262 posix_spawnattr_t attrs
;
263 uint64_t percent
, interval
;
267 mach_port_t task
= mach_task_self();
268 mach_port_t child_task
;
271 pthread_t exception_thread
;
272 pthread_t timer_thread
;
273 pthread_t wait_thread
;
275 mach_msg_type_number_t maskCount
= 1;
276 exception_mask_t mask
;
277 exception_handler_t handler
;
278 exception_behavior_t behavior
;
279 thread_state_flavor_t flavor
;
282 int test_case_id
= -1;
285 test_case_id
= atoi(argv
[1]);
287 /* Initialize mutex and condition variable */
288 if ((err
= pthread_mutex_init(&lock
, NULL
)) != 0) {
289 fprintf(stderr
,"pthread_mutex_init: %s\n", strerror(err
));
293 if ((err
= pthread_cond_init(&cv
, NULL
)) != 0) {
294 fprintf(stderr
, "pthread_cond_init: %s\n", strerror(err
));
298 /* Allocate and initialize new exception port */
299 if ((kr
= mach_port_allocate(task
, MACH_PORT_RIGHT_RECEIVE
, &exc_port
)) != KERN_SUCCESS
) {
300 fprintf(stderr
, "mach_port_allocate: %s\n", mach_error_string(kr
));
304 if ((kr
= mach_port_insert_right(task
, exc_port
,
305 exc_port
, MACH_MSG_TYPE_MAKE_SEND
)) != KERN_SUCCESS
) {
306 fprintf(stderr
, "mach_port_allocate: %s\n", mach_error_string(kr
));
310 /* Get Current exception ports */
311 if ((kr
= task_get_exception_ports(task
, EXC_MASK_RESOURCE
, &mask
,
312 &maskCount
, &handler
, &behavior
, &flavor
)) != KERN_SUCCESS
) {
313 fprintf(stderr
,"task_get_exception_ports: %s\n", mach_error_string(kr
));
317 /* Create exception serving thread */
318 if ((err
= pthread_create(&exception_thread
, NULL
, server_thread
, 0)) != 0) {
319 fprintf(stderr
, "pthread_create server_thread: %s\n", strerror(err
));
323 fprintf(stderr
, "---------------System Configuration------------------------------------------\n");
324 fprintf(stderr
, "System Kernel Version: ");
326 fprintf(stderr
, "System SDK Version: ");
329 for (i
= 0; i
< NUMTESTS
; i
++) {
330 indiv_results
[i
] = -1;
334 for(i
=0; i
<NUMTESTS
; i
++) {
337 if (test_case_id
!= -1 && test_case_id
!= i
)
340 fprintf(stderr
, "---------------Test [%d] Configuration------------------------------------------\n", i
);
341 fprintf(stderr
, "Test Case ID: %d\n", i
);
342 fprintf(stderr
, "Description: %s\n", test_description
[i
]);
346 child_args
= test_argv_0
;
349 child_args
= test_argv_1
;
352 child_args
= test_argv_2
;
355 child_args
= test_argv_3
;
358 child_args
= test_argv_4
;
361 child_args
= test_argv_5
;
364 child_args
= test_argv_6
;
367 fprintf(stderr
, "no test argv found\n");
371 /* Test cases which do not need to run for certain platforms */
372 if (child_args
== NULL
) {
373 fprintf(stderr
, "Test case unimplemented for current platform.\n");
374 fprintf(stderr
, "[PASSED]\n");
375 fprintf(stderr
, "-------------------------------------------------------------------------------\n");
379 fprintf(stderr
, "Helper args: ");
380 for (j
= 0; child_args
[j
] != NULL
; j
++) {
381 fprintf(stderr
, "%s ", child_args
[j
]);
383 fprintf(stderr
, "\n");
385 /* Print Test Case Configuration */
386 fprintf(stderr
, "Test Case expects EXC_RESOURCE?: %s\n", test_exception_code
[i
] ? "Yes":"No");
387 if (test_exception_code
[i
])
388 fprintf(stderr
, "Expected EXC_RESOURCE code: 0x%llx\n", test_exception_code
[i
]);
390 fprintf(stderr
, "Timeout for Test Program: %d secs\n", timeout_secs
[i
]);
391 if (exc_expected_at
[i
])
392 fprintf(stderr
, "Exception Expected After: %d secs\n", exc_expected_at
[i
]);
394 /* Initialize posix_spawn attributes */
395 posix_spawnattr_init(&attrs
);
397 if ((err
= posix_spawnattr_setflags(&attrs
, POSIX_SPAWN_SETEXEC
)) != 0) {
398 fprintf(stderr
, "posix_spawnattr_setflags: %s\n", strerror(err
));
402 /* Use high values so the system defaults take effect (spawn attrs are capped) */
406 /* Enable CPU Monitor */
407 if ((err
= posix_spawnattr_setcpumonitor(&attrs
, percent
, interval
)) != 0) {
408 fprintf(stderr
, "posix_spawnattr_setcpumonitor: %s\n", strerror(err
));
416 /* Set Exception Ports for Current Task */
417 if ((kr
= task_set_exception_ports(task
, EXC_MASK_RESOURCE
, exc_port
,
418 EXCEPTION_DEFAULT
| MACH_EXCEPTION_CODES
, flavor
)) != KERN_SUCCESS
) {
419 fprintf(stderr
, "task_set_exception_ports: %#x\n", kr
);
424 * Note the time at start of test.
426 start_time
= time(NULL
);
428 fprintf(stderr
, "---------------Test [%d] Runtime------------------------------------------------\n", i
);
430 /* Fork and exec child */
431 if ((child_pid
= fork()) == 0) {
432 if ((err
= posix_spawn(NULL
, child_args
[0], NULL
, &attrs
, &child_args
[0], environ
)) != 0) {
433 fprintf(stderr
, "posix_spawn: %s\n", strerror(err
));
438 /* Restore exception ports for parent */
439 if ((kr
= task_set_exception_ports(task
, EXC_MASK_RESOURCE
, handler
,
440 EXCEPTION_DEFAULT
| MACH_EXCEPTION_CODES
, flavor
)) != KERN_SUCCESS
) {
441 fprintf(stderr
, "task_set_exception_ports: %#x\n", kr
);
445 /* Create Timer Thread if timeout specified */
446 if (timeout_secs
[i
]) {
447 if ((err
= pthread_create(&timer_thread
, NULL
, timeout_thread
, (void *)timeout_secs
[i
])) != 0) {
448 fprintf(stderr
, "pthread_create timeout_thread: %s\n", strerror(err
));
454 /* Create waiting for child thread */
455 if ((err
= pthread_create(&wait_thread
, NULL
, wait4_child_thread
, NULL
)) != 0) {
456 fprintf(stderr
, "pthread_create wait4_child_thread: %s\n", strerror(err
));
461 pthread_mutex_lock(&lock
);
462 pthread_cond_wait(&cv
, &lock
);
463 pthread_mutex_unlock(&lock
);
465 kill(child_pid
, SIGKILL
);
466 pthread_join(timer_thread
, NULL
);
467 pthread_join(wait_thread
, NULL
);
469 int test_case_status
= 0;
470 indiv_results
[i
] = 0;
472 fprintf(stderr
, "---------------Test [%d] Results------------------------------------------------\n", i
);
475 fprintf(stderr
, "EXC_RESOURCE Received with Code: 0x%llx\n", exception_code
);
477 fprintf(stderr
, "No EXC_RESOURCE Received!\n");
479 if (time_for_exc
> 0)
480 fprintf(stderr
, "EXC_RESOURCE Received after %d secs\n", time_for_exc
);
482 if (!!exception_code
!= !!test_exception_code
[i
]) {
484 test_case_status
= 1;
485 indiv_results
[i
] = 1;
488 if (exception_code
) {
489 /* Validate test success by checking code and expected time */
490 if ((exception_code
& test_exception_code
[i
]) != test_exception_code
[i
]) {
491 fprintf(stderr
, "Test Failure Reason: EXC_RESOURCE code did not match expected exception code!\n");
492 fprintf(stderr
, "Expected: 0x%llx Found: 0x%llx\n", test_exception_code
[i
], exception_code
);
494 test_case_status
= 1;
495 indiv_results
[i
] = 1;
497 if(exc_expected_at
[i
] &&
498 (time_for_exc
< (exc_expected_at
[i
] - 10) ||
499 time_for_exc
> (exc_expected_at
[i
] + 10))) {
500 fprintf(stderr
, "Test Failure Reason: Test case did not receive EXC_RESOURCE within expected time!\n");
502 test_case_status
= 1;
503 indiv_results
[i
] = 1;
508 fprintf(stderr
, "[FAILED]\n");
510 fprintf(stderr
, "[PASSED]\n");
511 fprintf(stderr
, "-------------------------------------------------------------------------------\n");
515 if (test_case_id
== -1) {
516 fprintf(stderr
, "--------------- Results Summary -----------------------------------------------\n");
518 for (i
= 0; i
< NUMTESTS
; i
++) {
519 fprintf(stderr
, "%2d: %s\n", i
, (indiv_results
[i
] < 0) ? "N/A" :
520 (indiv_results
[i
] == 0) ? "PASSED" : "FAILED");
525 kill(child_pid
, SIGKILL
);