1/* 2 * Testing Framework for CPU Usage Monitor 3 * 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. 11 * 12 * Usage: ./cpu_monitor_tests_11646922 [test case ID] 13 * If no test case ID is supplied, the framework runs all test cases. 14 * 15 */ 16 17#include <stdio.h> 18#include <spawn.h> 19#include <stdlib.h> 20#include <string.h> 21#include <unistd.h> 22#include <signal.h> 23#include <pthread.h> 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> 29 30#define MAX_TEST_ID_LEN 16 31#define MAX_ARGV 8 32 33#define GENERATE_TEST_EXC_CODE(type, flavor) \ 34 ((0) | ((type & 0x7ULL) << 61) | ((flavor & 0x7ULL) << 58)) 35 36/* 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 45 */ 46 47#define NUMTESTS 7 48 49const 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.", 57}; 58 59/* 60 * Exec arguments for cpu hogging programs 61 * (NULL indicates test should not be run) 62 */ 63char *test_argv_0[] = { "./cpu_hog-unentitled", "-c", "30", "-C", "10", "-p", "100", "-i", "1", NULL }; 64char *test_argv_1[] = { "./cpu_hog-unentitled", "-c", "50", "-C", "15", "-p", "25", "-i", "1", NULL }; 65#ifdef TARGET_SDK_iphoneos_internal 66char *test_argv_2[] = { "./cpu_hog", "-c", "20", "-C", "15", "-x", "0", "-p", "100", "-i", "1", NULL }; 67char *test_argv_3[] = { "./cpu_hog-unentitled", "-c", "20", "-C", "15", "-x", "1", "-p", "100", "-i", "1", NULL }; 68#else 69char *test_argv_2[] = { "./cpu_hog-unentitled", "-c", "20", "-C", "15", "-x", "0", "-p", "100", "-i", "1", NULL }; 70char **test_argv_3 = NULL; 71#endif 72char *test_argv_4[] = { "./cpu_hog-unentitled", "-c", "20", "-C", "15", "-r", "1", "-p", "100", "-i", "1", NULL }; 73#ifdef TARGET_SDK_iphoneos_internal 74char *test_argv_5[] = { "./mem_hog", "-e", "-w", "50", "-m", "150", "10", "200", NULL }; 75char *test_argv_6[] = { "./mem_hog", "-e", "-w", "190", "-m", "160", "10", "200", NULL }; 76#else 77char **test_argv_5 = NULL; 78char **test_argv_6 = NULL; 79#endif 80 81/* 82 * Timeout in seconds for test scenario to complete 83 * (0 indicates no timeout enabled) 84 */ 85int timeout_secs[] = { 86 15, 87 20, 88 20, 89 110, 90 110, 91 20, 92 20, 93}; 94 95/* 96 * Exception should be generated within the specified duration 97 * (0 indicates no exception/time constraints for the exception 98 * to occur) 99 */ 100int exc_expected_at[] = { 101 0, 102 0, 103 0, 104 90, 105 90, 106 10, 107 0, 108}; 109 110/* 111 * EXC_RESOURCE exception codes expected (0 indicates no 112 * exception expected) 113 */ 114uint64_t test_exception_code[] = { 115 GENERATE_TEST_EXC_CODE(RESOURCE_TYPE_CPU, FLAVOR_CPU_MONITOR), 116 0, 117 0, 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), 121 0, 122}; 123 124#define DEFAULT_PERCENTAGE "50" 125#define DEFAULT_INTERVAL "180" 126 127/* Global Variables used by parent/child */ 128mach_port_t exc_port; /* Exception port for child process */ 129uint64_t exception_code; /* Exception code for the exception generated */ 130int time_for_exc; /* Time (in secs.) for the exception to be generated */ 131extern char **environ; /* Environment variables for the child process */ 132int test_status; /* Test Suite Status */ 133int indiv_results[NUMTESTS]; /* Results of individual tests (-1=didn't run; 0=pass; 1=fail) */ 134 135/* Cond Var and Mutex to indicate timeout for child process */ 136pthread_cond_t cv; 137pthread_mutex_t lock; 138 139/* Timer Routines to calculate elapsed time and run timer thread */ 140time_t start_time; /* Test case start time (in secs.) */ 141 142int elapsed(void) 143{ 144 return (time(NULL) - start_time); 145} 146 147void *timeout_thread(void *arg) 148{ 149 int err; 150 int timeout = (int)arg; 151 152 sleep(timeout); 153 fprintf(stderr, "Test Program timed out... Terminating!\n"); 154 155 if ((err = pthread_cond_broadcast(&cv)) != 0) { 156 fprintf(stderr, "pthread_cond_broadcast: %s\n", strerror(err)); 157 exit(1); 158 } 159 160 return (NULL); 161} 162 163/* Routine to wait for child to complete */ 164void *wait4_child_thread(void *arg) 165{ 166 int err; 167 int child_stat; 168 169 wait4(-1, &child_stat, 0, NULL); 170 171 if ((err = pthread_cond_broadcast(&cv)) != 0) { 172 fprintf(stderr, "pthread_cond_broadcast: %s\n", strerror(err)); 173 exit(1); 174 } 175 176 return (NULL); 177} 178 179/* Mach Server Routines */ 180boolean_t mach_exc_server( 181 mach_msg_header_t *InHeadP, 182 mach_msg_header_t *OutHeadP); 183 184kern_return_t catch_mach_exception_raise 185( 186 mach_port_t exception_port, 187 mach_port_t thread, 188 mach_port_t task, 189 exception_type_t exception, 190 mach_exception_data_t code, 191 mach_msg_type_number_t codeCnt 192 ) 193{ 194 if (exception == EXC_RESOURCE) { 195 /* Set global variable to indicate exception received */ 196 exception_code = *((uint64_t *)code); 197 time_for_exc = elapsed(); 198 } else { 199 /* Terminate test on all other unexpected exceptions */ 200 fprintf(stderr, "received unexpected exception type %#x\n", exception); 201 exit(1); 202 } 203 204 return (KERN_SUCCESS); 205} 206 207kern_return_t catch_mach_exception_raise_state 208( 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, 213 int *flavor, 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 218 ) 219{ 220 fprintf(stderr, "Unexpected exception handler called\n"); 221 exit(1); 222 return (KERN_FAILURE); 223} 224 225 226kern_return_t catch_mach_exception_raise_state_identity 227( 228 mach_port_t exception_port, 229 mach_port_t thread, 230 mach_port_t task, 231 exception_type_t exception, 232 mach_exception_data_t code, 233 mach_msg_type_number_t codeCnt, 234 int *flavor, 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 239 ) 240{ 241 fprintf(stderr, "Unexpected exception handler called\n"); 242 exit(1); 243 return (KERN_FAILURE); 244} 245 246void *server_thread(void *arg) 247{ 248 kern_return_t kr; 249 250 while(1) { 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); 254 exit(1); 255 } 256 } 257 return (NULL); 258} 259 260int main(int argc, char *argv[]) 261{ 262 posix_spawnattr_t attrs; 263 uint64_t percent, interval; 264 int i, err, ret = 0; 265 266 kern_return_t kr; 267 mach_port_t task = mach_task_self(); 268 mach_port_t child_task; 269 char **child_args; 270 271 pthread_t exception_thread; 272 pthread_t timer_thread; 273 pthread_t wait_thread; 274 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; 280 281 pid_t child_pid; 282 int test_case_id = -1; 283 284 if (argc > 1) 285 test_case_id = atoi(argv[1]); 286 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)); 290 exit(1); 291 } 292 293 if ((err = pthread_cond_init(&cv, NULL)) != 0) { 294 fprintf(stderr, "pthread_cond_init: %s\n", strerror(err)); 295 exit(1); 296 } 297 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)); 301 exit(1); 302 } 303 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)); 307 exit(1); 308 } 309 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)); 314 exit(1); 315 } 316 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)); 320 exit(1); 321 } 322 323 fprintf(stderr, "---------------System Configuration------------------------------------------\n"); 324 fprintf(stderr, "System Kernel Version: "); 325 system("uname -a"); 326 fprintf(stderr, "System SDK Version: "); 327 system("sw_vers"); 328 329 for (i = 0; i < NUMTESTS; i++) { 330 indiv_results[i] = -1; 331 } 332 333 /* Run Tests */ 334 for(i=0; i<NUMTESTS; i++) { 335 int j; 336 337 if (test_case_id != -1 && test_case_id != i) 338 continue; 339 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]); 343 344 switch(i) { 345 case 0: 346 child_args = test_argv_0; 347 break; 348 case 1: 349 child_args = test_argv_1; 350 break; 351 case 2: 352 child_args = test_argv_2; 353 break; 354 case 3: 355 child_args = test_argv_3; 356 break; 357 case 4: 358 child_args = test_argv_4; 359 break; 360 case 5: 361 child_args = test_argv_5; 362 break; 363 case 6: 364 child_args = test_argv_6; 365 break; 366 default: 367 fprintf(stderr, "no test argv found\n"); 368 exit(1); 369 } 370 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"); 376 continue; 377 } 378 379 fprintf(stderr, "Helper args: "); 380 for (j = 0; child_args[j] != NULL; j++) { 381 fprintf(stderr, "%s ", child_args[j]); 382 } 383 fprintf(stderr, "\n"); 384 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]); 389 if (timeout_secs[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]); 393 394 /* Initialize posix_spawn attributes */ 395 posix_spawnattr_init(&attrs); 396 397 if ((err = posix_spawnattr_setflags(&attrs, POSIX_SPAWN_SETEXEC)) != 0) { 398 fprintf(stderr, "posix_spawnattr_setflags: %s\n", strerror(err)); 399 exit(1); 400 } 401 402 /* Use high values so the system defaults take effect (spawn attrs are capped) */ 403 percent = 100; 404 interval = 10000; 405 406 /* Enable CPU Monitor */ 407 if ((err = posix_spawnattr_setcpumonitor(&attrs, percent, interval)) != 0) { 408 fprintf(stderr, "posix_spawnattr_setcpumonitor: %s\n", strerror(err)); 409 exit(1); 410 } 411 412 413 exception_code = 0; 414 time_for_exc = -1; 415 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); 420 exit(1); 421 } 422 423 /* 424 * Note the time at start of test. 425 */ 426 start_time = time(NULL); 427 428 fprintf(stderr, "---------------Test [%d] Runtime------------------------------------------------\n", i); 429 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)); 434 exit(1); 435 } 436 } 437 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); 442 exit(1); 443 } 444 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)); 449 test_status = 1; 450 goto cleanup; 451 } 452 } 453 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)); 457 test_status = 1; 458 goto cleanup; 459 } 460 461 pthread_mutex_lock(&lock); 462 pthread_cond_wait(&cv, &lock); 463 pthread_mutex_unlock(&lock); 464 465 kill(child_pid, SIGKILL); 466 pthread_join(timer_thread, NULL); 467 pthread_join(wait_thread, NULL); 468 469 int test_case_status = 0; 470 indiv_results[i] = 0; 471 472 fprintf(stderr, "---------------Test [%d] Results------------------------------------------------\n", i); 473 474 if (exception_code) 475 fprintf(stderr, "EXC_RESOURCE Received with Code: 0x%llx\n", exception_code); 476 else 477 fprintf(stderr, "No EXC_RESOURCE Received!\n"); 478 479 if (time_for_exc > 0) 480 fprintf(stderr, "EXC_RESOURCE Received after %d secs\n", time_for_exc); 481 482 if (!!exception_code != !!test_exception_code[i]) { 483 test_status = 1; 484 test_case_status = 1; 485 indiv_results[i] = 1; 486 } 487 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); 493 test_status = 1; 494 test_case_status = 1; 495 indiv_results[i] = 1; 496 } 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"); 501 test_status = 1; 502 test_case_status = 1; 503 indiv_results[i] = 1; 504 } 505 } 506 507 if(test_case_status) 508 fprintf(stderr, "[FAILED]\n"); 509 else 510 fprintf(stderr, "[PASSED]\n"); 511 fprintf(stderr, "-------------------------------------------------------------------------------\n"); 512 513 } 514 515 if (test_case_id == -1) { 516 fprintf(stderr, "--------------- Results Summary -----------------------------------------------\n"); 517 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"); 521 } 522 } 523 524cleanup: 525 kill(child_pid, SIGKILL); 526 exit(test_status); 527} 528 529 530