diff options
author | Andrea Shepard <andrea@torproject.org> | 2014-02-04 13:59:53 -0800 |
---|---|---|
committer | Andrea Shepard <andrea@torproject.org> | 2014-09-30 23:14:59 -0700 |
commit | 41cf9f6260d70de8b07740c89fd5273b4f073c95 (patch) | |
tree | 68d53d44a1b52ddf8d7dda3ac32515f45a31fb3b /src/test/test_scheduler.c | |
parent | 99d312c2937c0289eec047a70f626e3976a25895 (diff) | |
download | tor-41cf9f6260d70de8b07740c89fd5273b4f073c95.tar.gz tor-41cf9f6260d70de8b07740c89fd5273b4f073c95.zip |
Add scheduler/loop unit test
Diffstat (limited to 'src/test/test_scheduler.c')
-rw-r--r-- | src/test/test_scheduler.c | 303 |
1 files changed, 303 insertions, 0 deletions
diff --git a/src/test/test_scheduler.c b/src/test/test_scheduler.c index 650d4f1015..79fc453bd3 100644 --- a/src/test/test_scheduler.c +++ b/src/test/test_scheduler.c @@ -44,11 +44,17 @@ static const circuitmux_policy_t *mock_cgp_val_2 = NULL; static int scheduler_compare_channels_mock_ctr = 0; static int scheduler_run_mock_ctr = 0; +static void channel_flush_some_cells_mock_free_all(void); +static void channel_flush_some_cells_mock_set(channel_t *chan, + ssize_t num_cells); + /* Setup for mock event stuff */ static void mock_event_free_all(void); static void mock_event_init(void); /* Mocks used by scheduler tests */ +static ssize_t channel_flush_some_cells_mock(channel_t *chan, + ssize_t num_cells); static int circuitmux_compare_muxes_mock(circuitmux_t *cmux_1, circuitmux_t *cmux_2); static const circuitmux_policy_t * circuitmux_get_policy_mock( @@ -62,6 +68,7 @@ static struct event_base * tor_libevent_get_base_mock(void); static void test_scheduler_channel_states(void *arg); static void test_scheduler_compare_channels(void *arg); static void test_scheduler_initfree(void *arg); +static void test_scheduler_loop(void *arg); static void test_scheduler_queue_heuristic(void *arg); /* Mock event init/free */ @@ -123,6 +130,120 @@ mock_event_init(void) /* Mocks */ +typedef struct { + const channel_t *chan; + ssize_t cells; +} flush_mock_channel_t; + +static smartlist_t *chans_for_flush_mock = NULL; + +static void +channel_flush_some_cells_mock_free_all(void) +{ + if (chans_for_flush_mock) { + SMARTLIST_FOREACH_BEGIN(chans_for_flush_mock, + flush_mock_channel_t *, + flush_mock_ch) { + SMARTLIST_DEL_CURRENT(chans_for_flush_mock, flush_mock_ch); + tor_free(flush_mock_ch); + } SMARTLIST_FOREACH_END(flush_mock_ch); + + smartlist_free(chans_for_flush_mock); + chans_for_flush_mock = NULL; + } +} + +static void +channel_flush_some_cells_mock_set(channel_t *chan, ssize_t num_cells) +{ + flush_mock_channel_t *flush_mock_ch = NULL; + + if (!chan) return; + if (num_cells <= 0) return; + + if (!chans_for_flush_mock) { + chans_for_flush_mock = smartlist_new(); + } + + SMARTLIST_FOREACH_BEGIN(chans_for_flush_mock, + flush_mock_channel_t *, + flush_mock_ch) { + if (flush_mock_ch != NULL && flush_mock_ch->chan != NULL) { + if (flush_mock_ch->chan == chan) { + /* Found it */ + flush_mock_ch->cells = num_cells; + break; + } + } else { + /* That shouldn't be there... */ + SMARTLIST_DEL_CURRENT(chans_for_flush_mock, flush_mock_ch); + tor_free(flush_mock_ch); + } + } SMARTLIST_FOREACH_END(flush_mock_ch); + + if (!flush_mock_ch) { + /* The loop didn't find it */ + flush_mock_ch = tor_malloc_zero(sizeof(*flush_mock_ch)); + flush_mock_ch->chan = chan; + flush_mock_ch->cells = num_cells; + smartlist_add(chans_for_flush_mock, flush_mock_ch); + } +} + +static ssize_t +channel_flush_some_cells_mock(channel_t *chan, ssize_t num_cells) +{ + ssize_t flushed = 0, max; + char unlimited = 0; + flush_mock_channel_t *found = NULL; + + test_assert(chan != NULL); + if (chan) { + if (num_cells < 0) { + num_cells = 0; + unlimited = 1; + } + + /* Check if we have it */ + if (chans_for_flush_mock != NULL) { + SMARTLIST_FOREACH_BEGIN(chans_for_flush_mock, + flush_mock_channel_t *, + flush_mock_ch) { + if (flush_mock_ch != NULL && flush_mock_ch->chan != NULL) { + if (flush_mock_ch->chan == chan) { + /* Found it */ + found = flush_mock_ch; + break; + } + } else { + /* That shouldn't be there... */ + SMARTLIST_DEL_CURRENT(chans_for_flush_mock, flush_mock_ch); + tor_free(flush_mock_ch); + } + } SMARTLIST_FOREACH_END(flush_mock_ch); + + if (found) { + /* We found one */ + if (found->cells < 0) found->cells = 0; + + if (unlimited) max = found->cells; + else max = MIN(found->cells, num_cells); + + flushed += max; + found->cells -= max; + + if (found->cells <= 0) { + smartlist_remove(chans_for_flush_mock, found); + tor_free(found); + } + } + } + } + + done: + return flushed; +} + static int circuitmux_compare_muxes_mock(circuitmux_t *cmux_1, circuitmux_t *cmux_2) @@ -415,6 +536,187 @@ test_scheduler_initfree(void *arg) } static void +test_scheduler_loop(void *arg) +{ + channel_t *ch1 = NULL, *ch2 = NULL; + + (void)arg; + + /* Set up libevent and scheduler */ + + mock_event_init(); + MOCK(tor_libevent_get_base, tor_libevent_get_base_mock); + scheduler_init(); + /* + * Install the compare channels mock so we can test + * scheduler_touch_channel(). + */ + MOCK(scheduler_compare_channels, scheduler_compare_channels_mock); + /* + * Disable scheduler_run so we can just check the state transitions + * without having to make everything it might call work too. + */ + MOCK(scheduler_run, scheduler_run_noop_mock); + + test_eq(smartlist_len(channels_pending), 0); + + /* Set up a fake channel */ + ch1 = new_fake_channel(); + test_assert(ch1); + + /* Start it off in OPENING */ + ch1->state = CHANNEL_STATE_OPENING; + /* We'll need a cmux */ + ch1->cmux = circuitmux_alloc(); + /* Try to register it */ + channel_register(ch1); + test_assert(ch1->registered); + /* Finish opening it */ + channel_change_state(ch1, CHANNEL_STATE_OPEN); + + /* It should start off in SCHED_CHAN_IDLE */ + test_eq(ch1->scheduler_state, SCHED_CHAN_IDLE); + + /* Now get another one */ + ch2 = new_fake_channel(); + test_assert(ch2); + ch2->state = CHANNEL_STATE_OPENING; + ch2->cmux = circuitmux_alloc(); + channel_register(ch2); + test_assert(ch2->registered); + /* + * Don't open ch2; then channel_num_cells_writeable() will return + * zero and we'll get coverage of that exception case in scheduler_run() + */ + + test_eq(ch1->state, CHANNEL_STATE_OPEN); + test_eq(ch2->state, CHANNEL_STATE_OPENING); + + /* Send it to SCHED_CHAN_WAITING_TO_WRITE */ + scheduler_channel_has_waiting_cells(ch1); + test_eq(ch1->scheduler_state, SCHED_CHAN_WAITING_TO_WRITE); + + /* This should send it to SCHED_CHAN_PENDING */ + scheduler_channel_wants_writes(ch1); + test_eq(ch1->scheduler_state, SCHED_CHAN_PENDING); + test_eq(smartlist_len(channels_pending), 1); + + /* Now send ch2 to SCHED_CHAN_WAITING_FOR_CELLS */ + scheduler_channel_wants_writes(ch2); + test_eq(ch2->scheduler_state, SCHED_CHAN_WAITING_FOR_CELLS); + + /* Drop ch2 back to idle */ + scheduler_channel_doesnt_want_writes(ch2); + test_eq(ch2->scheduler_state, SCHED_CHAN_IDLE); + + /* ...and back to SCHED_CHAN_WAITING_FOR_CELLS */ + scheduler_channel_wants_writes(ch2); + test_eq(ch2->scheduler_state, SCHED_CHAN_WAITING_FOR_CELLS); + + /* ...and this should kick ch2 into SCHED_CHAN_PENDING */ + scheduler_channel_has_waiting_cells(ch2); + test_eq(ch2->scheduler_state, SCHED_CHAN_PENDING); + test_eq(smartlist_len(channels_pending), 2); + + /* + * Now we've got two pending channels and need to fire off + * scheduler_run(); first, unmock it. + */ + + UNMOCK(scheduler_run); + + scheduler_run(); + + /* Now re-mock it */ + MOCK(scheduler_run, scheduler_run_noop_mock); + + /* + * Assert that they're still in the states we left and aren't still + * pending + */ + test_eq(ch1->state, CHANNEL_STATE_OPEN); + test_eq(ch2->state, CHANNEL_STATE_OPENING); + test_assert(ch1->scheduler_state != SCHED_CHAN_PENDING); + test_assert(ch2->scheduler_state != SCHED_CHAN_PENDING); + test_eq(smartlist_len(channels_pending), 0); + + /* Now, finish opening ch2, and get both back to pending */ + channel_change_state(ch2, CHANNEL_STATE_OPEN); + scheduler_channel_wants_writes(ch1); + scheduler_channel_wants_writes(ch2); + scheduler_channel_has_waiting_cells(ch1); + scheduler_channel_has_waiting_cells(ch2); + test_eq(ch1->state, CHANNEL_STATE_OPEN); + test_eq(ch2->state, CHANNEL_STATE_OPEN); + test_eq(ch1->scheduler_state, SCHED_CHAN_PENDING); + test_eq(ch2->scheduler_state, SCHED_CHAN_PENDING); + test_eq(smartlist_len(channels_pending), 2); + + /* Now, set up the channel_flush_some_cells() mock */ + MOCK(channel_flush_some_cells, channel_flush_some_cells_mock); + /* + * 16 cells on ch1 means it'll completely drain into the 32 cells + * fakechan's num_cells_writeable() returns. + */ + channel_flush_some_cells_mock_set(ch1, 16); + /* + * This one should get sent back to pending, since num_cells_writeable() + * will still return non-zero. + */ + channel_flush_some_cells_mock_set(ch2, 48); + + /* + * And re-run the scheduler_run() loop with non-zero returns from + * channel_flush_some_cells() this time. + */ + UNMOCK(scheduler_run); + + scheduler_run(); + + /* Now re-mock it */ + MOCK(scheduler_run, scheduler_run_noop_mock); + + /* + * ch1 should have gone to SCHED_CHAN_WAITING_FOR_CELLS, with 16 flushed + * and 32 writeable. + */ + test_eq(ch1->scheduler_state, SCHED_CHAN_WAITING_FOR_CELLS); + /* + * ...ch2 should also have gone to SCHED_CHAN_WAITING_FOR_CELLS, with + * channel_more_to_flush() returning false and channel_num_cells_writeable() + * > 0/ + */ + test_eq(ch2->scheduler_state, SCHED_CHAN_WAITING_FOR_CELLS); + + /* Close */ + channel_mark_for_close(ch1); + test_eq(ch1->state, CHANNEL_STATE_CLOSING); + channel_mark_for_close(ch2); + test_eq(ch2->state, CHANNEL_STATE_CLOSING); + channel_closed(ch1); + test_eq(ch1->state, CHANNEL_STATE_CLOSED); + ch1 = NULL; + channel_closed(ch2); + test_eq(ch2->state, CHANNEL_STATE_CLOSED); + ch2 = NULL; + + /* Shut things down */ + channel_flush_some_cells_mock_free_all(); + channel_free_all(); + scheduler_free_all(); + mock_event_free_all(); + + done: + tor_free(ch1); + tor_free(ch2); + + UNMOCK(channel_flush_some_cells); + UNMOCK(scheduler_compare_channels); + UNMOCK(scheduler_run); + UNMOCK(tor_libevent_get_base); +} + +static void test_scheduler_queue_heuristic(void *arg) { time_t now = approx_time(); @@ -453,6 +755,7 @@ struct testcase_t scheduler_tests[] = { { "compare_channels", test_scheduler_compare_channels, TT_FORK, NULL, NULL }, { "initfree", test_scheduler_initfree, TT_FORK, NULL, NULL }, + { "loop", test_scheduler_loop, TT_FORK, NULL, NULL }, { "queue_heuristic", test_scheduler_queue_heuristic, TT_FORK, NULL, NULL }, END_OF_TESTCASES |