#include "errno.h" #include "globals.h" #include "test/proctest.h" #include "test/usertest.h" #include "util/debug.h" #include "util/printf.h" #include "util/string.h" #include "proc/kthread.h" #include "proc/proc.h" #include "proc/sched.h" /* * Set up a testing function for the process to execute. */ void *test_func(long arg1, void *arg2) { proc_t *proc_as_arg = (proc_t *)arg2; test_assert(arg1 == proc_as_arg->p_pid, "Arguments are not set up correctly"); test_assert(proc_as_arg->p_state == PROC_RUNNING, "Process state is not running"); test_assert(list_empty(&proc_as_arg->p_children), "There should be no child processes"); dbg(DBG_TEST, "Process %s is running\n", proc_as_arg->p_name); return NULL; } void test_termination() { int num_procs_created = 0; proc_t *new_proc1 = proc_create("proc test 1"); kthread_t *new_kthread1 = kthread_create(new_proc1, test_func, 2, new_proc1); num_procs_created++; sched_make_runnable(new_kthread1); int count = 0; int status; while (do_waitpid(-1, &status, 0) != -ECHILD) { dbg(DBG_TEST, "Waiting for child process to terminate\n"); test_assert(status == 0, "Returned status not set correctly"); count++; } test_assert(count == num_procs_created, "Expected: %d, Actual: %d number of processes have been cleaned up\n", num_procs_created, count); } /* Tests the edge cases of do_waitpid. 1. No child processes to wait for 2. Waiting on a specific process id 3. Waiting any child process (-1) */ void test_do_waitpid() { int status; int ret = do_waitpid(-1, &status, 0); test_assert(ret == -ECHILD, "No child processes to wait for"); proc_t *new_proc1 = proc_create("proc test 1"); kthread_t *new_kthread1 = kthread_create(new_proc1, test_func, new_proc1->p_pid, new_proc1); sched_make_runnable(new_kthread1); proc_t *new_proc2 = proc_create("proc test 2"); kthread_t *new_kthread2 = kthread_create(new_proc2, test_func, new_proc2->p_pid, new_proc2); sched_make_runnable(new_kthread2); proc_t *new_proc3 = proc_create("proc test 3"); kthread_t *new_kthread3 = kthread_create(new_proc3, test_func, new_proc3->p_pid, new_proc3); sched_make_runnable(new_kthread3); // wait for a specific process id ret = do_waitpid(new_proc2->p_pid, &status, 0); test_assert(ret == new_proc2->p_pid, "Waiting for a specific process id"); dbg(DBG_TEST, "Successfully removed proc 2 with pid: %d\n", new_proc2->p_pid); // wait for any child process ret = do_waitpid(-1, &status, 0); test_assert(ret == new_proc1->p_pid, "Waiting for any child process"); dbg(DBG_TEST, "Successfully removed proc 1 with pid: %d\n", new_proc1->p_pid); ret = do_waitpid(-1, &status, 0); test_assert(ret == new_proc3->p_pid, "Waiting for any child process"); dbg(DBG_TEST, "Successfully removed proc 3 with pid: %d\n", new_proc3->p_pid); ret = do_waitpid(-1, &status, 0); test_assert(ret == -ECHILD, "No child processes to wait for"); } /* Tests proc_kill_all on a single process. */ void *test_func_kill(long arg1, void *arg2) { // function to be called by the process proc_t *proc_as_arg = (proc_t *)arg2; test_assert(arg1 == proc_as_arg->p_pid, "Arguments are not set up correctly"); test_assert(proc_as_arg->p_state == PROC_RUNNING, "Process state is not running"); test_assert(list_empty(&proc_as_arg->p_children), "There should be no child processes"); dbg(DBG_TEST, "Process with pid=%d is running and calling proc_kill_all\n", proc_as_arg->p_pid); proc_kill_all(); return NULL; } void test_proc_kill_all() { proc_t *new_proc_kill = proc_create("proc test kill"); kthread_t *new_kthread_kill = kthread_create(new_proc_kill, test_func_kill, new_proc_kill->p_pid, new_proc_kill); sched_make_runnable(new_kthread_kill); proc_t *new_proc1 = proc_create("proc test 1"); kthread_t *new_kthread1 = kthread_create(new_proc1, test_func, new_proc1->p_pid, new_proc1); sched_make_runnable(new_kthread1); proc_t *new_proc2 = proc_create("proc test 2"); kthread_t *new_kthread2 = kthread_create(new_proc2, test_func, new_proc2->p_pid, new_proc2); sched_make_runnable(new_kthread2); // wait on the process that calls proc_kill_all int status; int ret = do_waitpid(new_proc_kill->p_pid, &status, 0); test_assert(ret == new_proc_kill->p_pid, "Waiting for a specific process id"); dbg(DBG_TEST, "Successfully removed proc_kill with pid: %d\n", new_proc_kill->p_pid); // wait for all the threads to finish int count = 0; while (do_waitpid(-1, &status, 0) != -ECHILD) { dbg(DBG_TEST, "Waiting for child process to terminate\n"); test_assert(status == 0, "Returned status not set correctly"); count++; } test_assert(count == 2, "Expected: %d, Actual: %d number of processes have been cleaned up\n", 2, count); } /* Test to run several threads and processes concurrently. */ void test_multiple() { int num_procs_created = 0; proc_t *new_proc1 = proc_create("proc test 1"); kthread_t *new_kthread1 = kthread_create(new_proc1, test_func, new_proc1->p_pid, new_proc1); num_procs_created++; sched_make_runnable(new_kthread1); proc_t *new_proc2 = proc_create("proc test 2"); kthread_t *new_kthread2 = kthread_create(new_proc2, test_func, new_proc2->p_pid, new_proc2); num_procs_created++; sched_make_runnable(new_kthread2); proc_t *new_proc3 = proc_create("proc test 3"); kthread_t *new_kthread3 = kthread_create(new_proc3, test_func, new_proc3->p_pid, new_proc3); num_procs_created++; sched_make_runnable(new_kthread3); // print all of the threads in the system list_iterate(&curproc->p_threads, thread, kthread_t, kt_plink) { dbg(DBG_THR, "Thread: %s\n", thread->kt_proc->p_name); } // wait for all the threads to finish int count = 0; int status; while (do_waitpid(-1, &status, 0) != -ECHILD) { dbg(DBG_TEST, "Waiting for child process to terminate\n"); test_assert(status == 0, "Returned status not set correctly"); count++; } test_assert(count == num_procs_created, "Expected: %d, Actual: %d number of processes have been cleaned up\n", num_procs_created, count); } /* Test that creates several child processes and forces them to terminate out of order. Then it checks to see if the processes are cleaned up correctly. */ void *func_forever_yielding(long arg1, void *arg2) { // function that always just yields // goal is that it is cleaned up by a different process while (1) { // have a way to stop the thread int *to_stop = (int *)arg2; if (*to_stop) { dbg(DBG_TEST, "Thread with pid=%d is stopping yield loop\n", curproc->p_pid); do_exit(1); // return different status code break; } // wait a bit for (int i = 0; i < 1000000; i++) { ; } dbg(DBG_TEST, "Thread with pid=%d is yielding on it's parent\n", curproc->p_pid); sched_yield(); } return NULL; } void test_out_of_order_termination() { // create a yielding proc that will last for a long time proc_t *new_proc1 = proc_create("yieling proc 1"); int stop_func_1 = 0; kthread_t *new_kthread1 = kthread_create(new_proc1, func_forever_yielding, new_proc1->p_pid, &stop_func_1); sched_make_runnable(new_kthread1); proc_t *new_proc2 = proc_create("yielding proc 2"); int stop_func_2 = 0; kthread_t *new_kthread2 = kthread_create(new_proc2, func_forever_yielding, new_proc2->p_pid, &stop_func_2); sched_make_runnable(new_kthread2); proc_t *new_proc3 = proc_create("proc test 3"); kthread_t *new_kthread3 = kthread_create(new_proc3, test_func, new_proc3->p_pid, new_proc3); sched_make_runnable(new_kthread3); proc_t *new_proc4 = proc_create("proc test 4"); kthread_t *new_kthread4 = kthread_create(new_proc4, test_func, new_proc4->p_pid, new_proc4); sched_make_runnable(new_kthread4); // let to first and second procs go and yield sched_yield(); // the first and second proc should yield to the third proc and fourth proc // which will return here to the init proc test_assert(curproc->p_pid == PID_INIT, "Should have returned to init proc"); test_assert(new_proc3->p_state == PROC_DEAD, "Third proc should be dead"); test_assert(new_proc4->p_state == PROC_DEAD, "Fourth proc should be dead"); // destroy procs 3 and 4 // terminate the second proc stop_func_2 = 1; sched_yield(); // we should return back to the init proc test_assert(curproc->p_pid == PID_INIT, "Should have returned to init proc"); test_assert(new_proc2->p_state == PROC_DEAD, "Second proc should be dead"); // terminate the first proc stop_func_1 = 1; sched_yield(); // we should return back to the init proc test_assert(curproc->p_pid == PID_INIT, "Should have returned to init proc"); test_assert(new_proc1->p_state == PROC_DEAD, "First proc should be dead"); // clean up proc 2 using do_waitpid, with specific status 1 int status; int ret = do_waitpid(new_proc2->p_pid, &status, 0); test_assert(ret == new_proc2->p_pid, "wrong specific process id"); test_assert(status == 1, "Returned status not set correctly"); test_assert(new_proc2->p_status == 1, "Status not set correctly"); // clean up the rest of the dead procs using do_waitpid int count = 0; while ((ret = do_waitpid(-1, &status, 0)) != -ECHILD) { dbg(DBG_TEST, "found child with pid=%d that needs to be cleaned up\n", ret); if (ret == new_proc1->p_pid) { test_assert(status == 1, "Returned status not set correctly for proc 1 with pid=%d", new_proc1->p_pid); } else { test_assert(status == 0, "Returned status not set correctly"); } count++; } test_assert(count == 3, "Expected: %d, Actual: %d number of processes have been cleaned up\n", 3, count); } long proctest_main(long arg1, void *arg2) { dbg(DBG_TEST, "\nStarting Procs tests\n"); test_init(); test_termination(); // Add more tests here! // We highly recommend looking at section 3.8 on the handout for help! dbg(DBG_TEST, "\nStarting test_multiple\n"); test_multiple(); dbg(DBG_TEST, "\nStarting test_do_waitpid\n"); test_do_waitpid(); dbg(DBG_TEST, "\nStarting test_proc_kill_all\n"); test_proc_kill_all(); dbg(DBG_TEST, "\nStarting test_out_of_order_termination\n"); test_out_of_order_termination(); test_fini(); return 0; }