From 98b02d98f389d922da568882cd3f350fe28b473d Mon Sep 17 00:00:00 2001 From: Jonathan Rose Date: Mon, 5 Aug 2013 16:00:01 +0000 Subject: res_parking: Unit tests Adds the following unit tests: * create_lot: tests adding and removal of a new parking lot (baseline) * park_extensions: creates a parking lot that registers extensions and then confirms that all of the expected extensions exist * extensions_conflicts: creates numerous parking lots to test that extension conflicts in parking lots result in parking lot creation failing * dynamic_parking_variables: Tests that the creation of dynamic parking lots respects the related channel variables set on the channel that requests them. * park_call: Tests adding a channel to a parking lot's holding bridge by standard parking functions. * retrieve_call: Tests pulling a channel out of a parking lot's holding bridge via parked call retrieval functions. (closes issue ASTERISK-22138) Reported by: Matt Jordan Review: https://reviewboard.asterisk.org/r/2714/ git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@396175 65c4cc65-6c06-0410-ace0-fbb531ad65f3 --- res/parking/parking_bridge.c | 5 +- res/parking/parking_tests.c | 845 +++++++++++++++++++++++++++++++++++++++++++ res/parking/res_parking.h | 49 ++- 3 files changed, 897 insertions(+), 2 deletions(-) create mode 100644 res/parking/parking_tests.c (limited to 'res/parking') diff --git a/res/parking/parking_bridge.c b/res/parking/parking_bridge.c index 55d853386..d0bdfa098 100644 --- a/res/parking/parking_bridge.c +++ b/res/parking/parking_bridge.c @@ -359,7 +359,10 @@ static void bridge_parking_pull(struct ast_bridge_parking *self, struct ast_brid } ao2_unlock(pu); - parking_notify_metermaids(pu->parking_space, self->lot->cfg->parking_con, AST_DEVICE_NOT_INUSE); + /* Pull can still happen after the bridge starts dissolving, so make sure we still have a lot before trying to notify metermaids. */ + if (self->lot) { + parking_notify_metermaids(pu->parking_space, self->lot->cfg->parking_con, AST_DEVICE_NOT_INUSE); + } switch (pu->resolution) { case PARK_UNSET: diff --git a/res/parking/parking_tests.c b/res/parking/parking_tests.c new file mode 100644 index 000000000..1b1cfc1dd --- /dev/null +++ b/res/parking/parking_tests.c @@ -0,0 +1,845 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * Jonathan Rose + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! \file + * + * \brief Call Parking Unit Tests + * + * \author Jonathan Rose + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include "res_parking.h" +#include "asterisk/utils.h" +#include "asterisk/module.h" +#include "asterisk/astobj2.h" +#include "asterisk/test.h" +#include "asterisk/stringfields.h" +#include "asterisk/time.h" +#include "asterisk/causes.h" +#include "asterisk/pbx.h" + +#if defined(TEST_FRAMEWORK) + +#define TEST_CATEGORY "/res/parking/" + +#define CHANNEL_TECH_NAME "ParkingTestChannel" + +static const struct ast_party_caller alice_callerid = { + .id.name.str = "Alice", + .id.name.valid = 1, + .id.number.str = "100", + .id.number.valid = 1, +}; + +/*! \brief Create a \ref test_cdr_chan_tech for Alice */ +static struct ast_channel *create_alice_channel(void) +{ + struct ast_channel *alice = ast_channel_alloc(0, AST_STATE_DOWN, + "100", "Alice", "100", "100", "default", NULL, 0, + CHANNEL_TECH_NAME "/Alice"); + + if (!alice) { + return NULL; + } + + ast_channel_set_caller(alice, &alice_callerid, NULL); + + return alice; +} + +/*! \brief Hang up a test channel safely */ +static struct ast_channel *hangup_channel(struct ast_channel *chan, int hangup_cause) +{ + ast_channel_hangupcause_set(chan, hangup_cause); + ast_hangup(chan); + return NULL; +} + +static void safe_channel_release(struct ast_channel *chan) +{ + if (!chan) { + return; + } + ast_channel_release(chan); +} + +static int fake_fixup(struct ast_channel *clonechan, struct ast_channel *original) +{ + return 0; +} + +static const struct ast_channel_tech fake_tech = { + .fixup = fake_fixup, /* silence warning from masquerade... though those shouldn't be happening now */ +}; + +#define TEST_LOT_NAME "unit_tests_res_parking_test_lot" + +static struct parking_lot *generate_test_parking_lot(const char *name, int low_space, int high_space, const char *park_exten, const char *park_context, struct ast_test *test) +{ + RAII_VAR(struct parking_lot_cfg *, test_cfg, NULL, ao2_cleanup); + struct parking_lot *test_lot; + + test_cfg = parking_lot_cfg_create(name); + + if (!test_cfg) { + return NULL; + } + + test_cfg->parking_start = low_space; + test_cfg->parking_stop = high_space; + test_cfg->parkingtime = 10; + test_cfg->comebackdialtime = 10; + test_cfg->parkfindnext = 1; + test_cfg->parkext_exclusive = 1; + ast_string_field_set(test_cfg, parkext, park_exten); + ast_string_field_set(test_cfg, parking_con, park_context); + ast_string_field_set(test_cfg, comebackcontext, "unit_test_res_parking_create_lot_comeback"); + + if (parking_lot_cfg_create_extensions(test_cfg)) { + ast_test_status_update(test, "Extensions for parking lot '%s' could not be registered. Extension Creation failed.\n", name); + return NULL; + } + + test_lot = parking_lot_build_or_update(test_cfg, 1); + + if (!test_lot) { + return NULL; + } + + return test_lot; +} + +static int dispose_test_lot(struct parking_lot *test_lot, int expect_destruction) +{ + RAII_VAR(struct parking_lot *, found_lot, NULL, ao2_cleanup); + + test_lot->mode = PARKINGLOT_DISABLED; + parking_lot_remove_if_unused(test_lot); + + found_lot = parking_lot_find_by_name(test_lot->name); + + if ((expect_destruction && !found_lot) || (!expect_destruction && found_lot)) { + return 0; + } + + return -1; +} + +AST_TEST_DEFINE(create_lot) +{ + RAII_VAR(struct parking_lot *, test_lot, NULL, ao2_cleanup); + RAII_VAR(struct parking_lot *, found_copy, NULL, ao2_cleanup); + + switch (cmd) { + case TEST_INIT: + info->name = "create_lot"; + info->category = TEST_CATEGORY; + info->summary = "Parking lot creation"; + info->description = + "Creates a parking lot and then disposes of it."; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + ast_test_status_update(test, "Creating test parking lot '%s'\n", TEST_LOT_NAME); + + test_lot = generate_test_parking_lot(TEST_LOT_NAME, 701, 703, NULL, "unit_test_res_parking_create_lot_con", test); + if (!test_lot) { + ast_test_status_update(test, "Failed to create test parking lot. Test Failed\n"); + return AST_TEST_FAIL; + } + + ast_test_status_update(test, "Successfully created parking lot. Retrieving test parking lot from container.\n"); + + found_copy = parking_lot_find_by_name(TEST_LOT_NAME); + if (!found_copy) { + ast_test_status_update(test, "Failed to find parking lot in the parking lot container. Test failed.\n"); + dispose_test_lot(test_lot, 1); + return AST_TEST_FAIL; + } + + ast_test_status_update(test, "Successfully retrieved parking lot. Removing test parking lot from container.\n"); + + if (dispose_test_lot(found_copy, 1)) { + ast_test_status_update(test, "Found parking lot in container after attempted removal. Test failed.\n"); + } + + ast_test_status_update(test, "Parking lot was successfully removed from the container. Test complete.\n"); + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(park_call) +{ + RAII_VAR(struct parking_lot *, test_lot, NULL, ao2_cleanup); + RAII_VAR(struct ast_channel *, chan_alice, NULL, safe_channel_release); + RAII_VAR(struct ast_bridge *, parking_bridge, NULL, ao2_cleanup); + + struct ast_bridge_features chan_features; + struct timespec to_sleep = {1, 0}; + + switch (cmd) { + case TEST_INIT: + info->name = "park_channel"; + info->category = TEST_CATEGORY; + info->summary = "Park a Channel"; + info->description = + "Creates a parking lot, parks a channel in it, then removes it from the parking lot bridge."; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + ast_test_status_update(test, "Creating test parking lot '%s'\n", TEST_LOT_NAME); + + test_lot = generate_test_parking_lot(TEST_LOT_NAME, 701, 703, NULL, "unit_test_res_parking_create_lot_con", test); + if (!test_lot) { + ast_test_status_update(test, "Failed to create test parking lot. Test failed.\n"); + return AST_TEST_FAIL; + } + + chan_alice = create_alice_channel(); + + if (!chan_alice) { + ast_test_status_update(test, "Failed to create test channel to park. Test failed.\n"); + dispose_test_lot(test_lot, 1); + return AST_TEST_FAIL; + } + + ast_channel_state_set(chan_alice, AST_STATE_UP); + + pbx_builtin_setvar_helper(chan_alice, "BLINDTRANSFER", ast_channel_name(chan_alice)); + + parking_bridge = park_application_setup(chan_alice, chan_alice, TEST_LOT_NAME, NULL); + + if (!parking_bridge) { + ast_test_status_update(test, "Failed to get the parking bridge for '%s'. Test failed.\n", TEST_LOT_NAME); + dispose_test_lot(test_lot, 1); + return AST_TEST_FAIL; + } + + if (ast_bridge_features_init(&chan_features)) { + ast_bridge_features_cleanup(&chan_features); + ast_test_status_update(test, "Failed to initialize bridge features. Test failed.\n"); + dispose_test_lot(test_lot, 1); + return AST_TEST_FAIL; + } + + ast_bridge_impart(parking_bridge, chan_alice, NULL, NULL, 0); + + while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR)); + + ast_bridge_depart(chan_alice); + + chan_alice = hangup_channel(chan_alice, AST_CAUSE_NORMAL); + + if (dispose_test_lot(test_lot, 1)) { + ast_test_status_update(test, "Found parking lot in container after attempted removal. Test failed.\n"); + } + + return AST_TEST_PASS; + +} + +static int parked_users_match(const struct parked_user *actual, const struct parked_user *expected, struct ast_test *test) +{ + if (expected->parking_space != actual->parking_space) { + ast_test_status_update(test, "parking_space expected: %d - got: %d\n", expected->parking_space, actual->parking_space); + return 0; + } + + if (strcmp(expected->parker_dial_string, actual->parker_dial_string)) { + ast_test_status_update(test, "parker_dial_string expected: %s - got: %s\n", expected->parker_dial_string, actual->parker_dial_string); + return 0; + } + + if (expected->time_limit != actual->time_limit) { + ast_test_status_update(test, "time_limit expected: %d - got: %d\n", expected->time_limit, actual->time_limit); + return 0; + } + + if (expected->resolution != actual->resolution) { + ast_test_status_update(test, "resolution expected: %d - got: %d\n", expected->resolution, actual->resolution); + return 0; + } + + return 1; +} + +static int parking_lot_cfgs_match(const struct parking_lot_cfg *actual, const struct parking_lot_cfg *expected, struct ast_test *test) +{ + if (expected->parking_start != actual->parking_start) { + ast_test_status_update(test, "parking_start expected: %d - got: %d\n", expected->parking_start, actual->parking_start); + return 0; + } + + if (expected->parking_stop != actual->parking_stop) { + ast_test_status_update(test, "parking_stop expected: %d - got: %d\n", expected->parking_stop, actual->parking_stop); + return 0; + } + + if (expected->parkingtime != actual->parkingtime) { + ast_test_status_update(test, "parkingtime expected: %d - got: %d\n", expected->parkingtime, actual->parkingtime); + return 0; + } + + if (expected->comebackdialtime != actual->comebackdialtime) { + ast_test_status_update(test, "comebackdialtime expected: %d - got: %d\n", expected->comebackdialtime, actual->comebackdialtime); + return 0; + } + + if (expected->parkfindnext != actual->parkfindnext) { + ast_test_status_update(test, "parkfindnext expected: %d - got: %d\n", expected->parkfindnext, actual->parkfindnext); + return 0; + } + + if (expected->parkext_exclusive != actual->parkext_exclusive) { + ast_test_status_update(test, "parkext_exclusive expected: %d - got: %d\n", expected->parkext_exclusive, actual->parkext_exclusive); + return 0; + } + + if (strcmp(expected->parkext, actual->parkext)) { + ast_test_status_update(test, "parkext expected: %s - got: %s\n", expected->parkext, actual->parkext); + return 0; + } + + if (strcmp(expected->parking_con, actual->parking_con)) { + ast_test_status_update(test, "parking_con expected: %s - got: %s\n", expected->parking_con, actual->parking_con); + return 0; + } + + if (strcmp(expected->comebackcontext, actual->comebackcontext)) { + ast_test_status_update(test, "comebackcontext expected: %s - got: %s\n", expected->comebackcontext, actual->comebackcontext); + return 0; + } + + return 1; +} + +AST_TEST_DEFINE(retrieve_call) +{ + RAII_VAR(struct parking_lot *, test_lot, NULL, ao2_cleanup); + RAII_VAR(struct ast_channel *, chan_alice, NULL, safe_channel_release); + RAII_VAR(struct ast_bridge *, parking_bridge, NULL, ao2_cleanup); + RAII_VAR(struct parked_user *, retrieved_user, NULL, ao2_cleanup); + + struct timespec to_sleep = {1, 0}; + int failure = 0; + + static const struct parked_user expected_user = { + .parking_space = 701, + .parker_dial_string = "ParkingTestChannel/Alice", + .time_limit = 10, + .resolution = PARK_ANSWERED, + }; + + struct ast_bridge_features chan_features; + + switch (cmd) { + case TEST_INIT: + info->name = "park_retrieve"; + info->category = TEST_CATEGORY; + info->summary = "Retrieve a parked channel"; + info->description = + "Creates a parking lot, parks a channel in it, then removes it from the parking lot bridge."; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + ast_test_status_update(test, "Creating test parking lot '%s'\n", TEST_LOT_NAME); + + test_lot = generate_test_parking_lot(TEST_LOT_NAME, 701, 703, NULL, "unit_test_res_parking_create_lot_con", test); + if (!test_lot) { + ast_test_status_update(test, "Failed to create test parking lot. Test failed.\n"); + return AST_TEST_FAIL; + } + + chan_alice = create_alice_channel(); + if (!chan_alice) { + ast_test_status_update(test, "Failed to create test channel to park. Test failed.\n"); + dispose_test_lot(test_lot, 1); + return AST_TEST_FAIL; + } + + ast_channel_state_set(chan_alice, AST_STATE_UP); + + pbx_builtin_setvar_helper(chan_alice, "BLINDTRANSFER", ast_channel_name(chan_alice)); + + parking_bridge = park_application_setup(chan_alice, chan_alice, TEST_LOT_NAME, NULL); + if (!parking_bridge) { + ast_test_status_update(test, "Failed to get the parking bridge for '%s'. Test failed.\n", TEST_LOT_NAME); + dispose_test_lot(test_lot, 1); + return AST_TEST_FAIL; + } + + if (ast_bridge_features_init(&chan_features)) { + ast_bridge_features_cleanup(&chan_features); + ast_test_status_update(test, "Failed to initialize bridge features. Test failed.\n"); + dispose_test_lot(test_lot, 1); + return AST_TEST_FAIL; + } + + ast_bridge_impart(parking_bridge, chan_alice, NULL, NULL, 0); + + while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR)); + + retrieved_user = parking_lot_retrieve_parked_user(test_lot, 701); + if (!retrieved_user) { + ast_test_status_update(test, "Failed to retrieve the parked user from the expected parking space. Test failed.\n"); + + failure = 1; + goto test_cleanup; + } + + ast_test_status_update(test, "Successfully retrieved parked user from the parking lot. Validating user data. Test failed.\n"); + + if (!parked_users_match(retrieved_user, &expected_user, test)) { + ast_test_status_update(test, "Parked user validation failed\n"); + failure = 1; + goto test_cleanup; + } + + if (retrieved_user->chan != chan_alice) { + ast_test_status_update(test, "The retrieved parked channel didn't match the expected channel. Test failed.\n"); + failure = 1; + goto test_cleanup; + } + +test_cleanup: + ast_bridge_depart(chan_alice); + chan_alice = hangup_channel(chan_alice, AST_CAUSE_NORMAL); + if (dispose_test_lot(test_lot, 1)) { + ast_test_status_update(test, "Found parking lot in container after attempted removal. Test failed.\n"); + failure = 1; + } + + return failure ? AST_TEST_FAIL : AST_TEST_PASS; +} + +static int check_retrieve_call_extensions(struct ast_test *test, int expected) +{ + struct ast_exten *check; + struct pbx_find_info find_info = { .stacklen = 0 }; /* the rest is reset in pbx_find_extension */ + int extens; + char search_buffer[4]; + + /* Check the parking extensions */ + check = pbx_find_extension(NULL, NULL, &find_info, "unit_test_res_parking_create_lot_con", "700", 1, NULL, NULL, E_MATCH); + + if (check ? !expected : expected) { + /* extension isn't present when it should be or is present when it shouldn't be. Automatic failure. */ + ast_test_status_update(test, "An extension '700' was %s when it %s have been. Test failed.\n", + expected ? "not present" : "present", + expected ? "should" : "should not"); + return -1; + } else if (check && expected) { + if (strcmp(ast_get_extension_app(check), "Park")) { + ast_test_status_update(test, "An extension '700' has the wrong application associated with it. Got '%s' expected 'Park'.\n", + ast_get_extension_app(check)); + return -1; + } + } + + + /* Check the parking space extensions 701-703 */ + for (extens = 701; extens <= 703; extens++) { + sprintf(search_buffer, "%d", extens); + find_info.stacklen = 0; /* reset for pbx_find_extension */ + + check = pbx_find_extension(NULL, NULL, &find_info, "unit_test_res_parking_create_lot_con", search_buffer, 1, NULL, NULL, E_MATCH); + + if (check ? !expected : expected) { + /* extension isn't present when it should be or is present when it shouldn't be. Automatic failure. */ + ast_test_status_update(test, "An extension '%s' was %s when it %s have been. Test failed.\n", + search_buffer, + expected ? "not present" : "present", + expected ? "should" : "should not"); + return -1; + } else if (check && expected) { + if (strcmp(ast_get_extension_app(check), "ParkedCall")) { + ast_test_status_update(test, "An extension '%s' has the wrong application associated with it. Got '%s', expected 'ParkedCall'.\n", + search_buffer, + ast_get_extension_app(check)); + return -1; + } + } + } + + return 0; + +} + +AST_TEST_DEFINE(park_extensions) +{ + RAII_VAR(struct parking_lot *, test_lot, NULL, ao2_cleanup); + + switch (cmd) { + case TEST_INIT: + info->name = "park_extensions"; + info->category = TEST_CATEGORY; + info->summary = "Parking lot extension creation tests"; + info->description = + "Creates parking lots and checks that they registered the expected extensions, then removes them."; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + test_lot = generate_test_parking_lot(TEST_LOT_NAME, 701, 703, "700", "unit_test_res_parking_create_lot_con", test); + if (!test_lot) { + ast_test_status_update(test, "Failed to create test parking lot. Test Failed.\n"); + return AST_TEST_FAIL; + } + + if (check_retrieve_call_extensions(test, 1)) { + dispose_test_lot(test_lot, 1); + return AST_TEST_FAIL; + } + + ast_test_status_update(test, "Extensions for the test parking lot were verified. Cleaning up and verifying their removal.\n"); + + if (dispose_test_lot(test_lot, 1)) { + ast_test_status_update(test, "Found parking lot in container after attempted removal. Test failed.\n"); + return AST_TEST_FAIL; + } + ao2_cleanup(test_lot); + test_lot = NULL; + + if (check_retrieve_call_extensions(test, 0)) { + ast_log(LOG_ERROR, "Test 'park_extensions' failed to clean up after itself properly.\n"); + return AST_TEST_FAIL; + } + + ast_test_status_update(test, "Extensions for the test parking lot verified as removed. Test completed successfully.\n"); + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(extension_conflicts) +{ + RAII_VAR(struct parking_lot *, base_lot, NULL, ao2_cleanup); + RAII_VAR(struct parking_lot *, expect_fail1, NULL, ao2_cleanup); /* Failure due to overlapping parkexten */ + RAII_VAR(struct parking_lot *, expect_fail2, NULL, ao2_cleanup); /* Failure due to overlapping spaces */ + RAII_VAR(struct parking_lot *, expect_fail3, NULL, ao2_cleanup); /* parkexten overlaps parking spaces */ + RAII_VAR(struct parking_lot *, expect_fail4, NULL, ao2_cleanup); /* parking spaces overlap parkexten */ + RAII_VAR(struct parking_lot *, expect_success1, NULL, ao2_cleanup); /* Success due to being in a different context */ + RAII_VAR(struct parking_lot *, expect_success2, NULL, ao2_cleanup); /* Success due to not having overlapping extensions */ + RAII_VAR(struct parking_lot *, expect_success3, NULL, ao2_cleanup); /* Range of parking spaces differs by one above */ + RAII_VAR(struct parking_lot *, expect_success4, NULL, ao2_cleanup); /* Range of parking spaces differs by one below */ + char *cur_lot_name; + + int failed = 0; + + switch (cmd) { + case TEST_INIT: + info->name = "extension_conflicts"; + info->category = TEST_CATEGORY; + info->summary = "Tests the addition of parking lot extensions to make sure conflicts are detected"; + info->description = + "Creates parking lots with overlapping extensions to test for conflicts"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + ast_test_status_update(test, "Creating the base lot. This should pass.\n"); + base_lot = generate_test_parking_lot(TEST_LOT_NAME, 701, 703, "700", "unit_test_res_parking_create_lot_con", test); + + if (!base_lot) { + ast_test_status_update(test, "Failed to create the base parking lot. Test failed.\n"); + failed = 1; + goto cleanup; + } + + cur_lot_name = "unit_tests_res_parking_test_lot_fail1"; + ast_test_status_update(test, "Creating a test lot which will overlap.\n"); + expect_fail1 = generate_test_parking_lot(cur_lot_name, + 801, 803, "700", "unit_test_res_parking_create_lot_con", /* The parkexten overlaps the parkexten of the base */ + test); + + if (expect_fail1) { + ast_test_status_update(test, "%s was successfully created when it was expected to fail. Test failed.\n", cur_lot_name); + failed = 1; + goto cleanup; + } + + cur_lot_name = "unit_tests_res_parking_test_lot_fail2"; + expect_fail2 = generate_test_parking_lot(cur_lot_name, + 702, 705, "800", "unit_test_res_parking_create_lot_con", /* The range overlaps the range of the base */ + test); + if (expect_fail2) { + ast_test_status_update(test, "%s was successfully created when it was expected to fail. Test failed.\n", cur_lot_name); + failed = 1; + goto cleanup; + } + + cur_lot_name = "unit_tests_res_parking_test_lot_fail3"; + expect_fail3 = generate_test_parking_lot(cur_lot_name, + 698, 700, "testfail3", "unit_test_res_parking_create_lot_con", /* The range overlaps the parkexten of the base */ + test); + if (expect_fail3) { + ast_test_status_update(test, "%s was successfully created when it was expected to fail. Test failed.\n", cur_lot_name); + failed = 1; + goto cleanup; + } + + cur_lot_name = "unit_tests_res_parking_test_lot_fail4"; + expect_fail4 = generate_test_parking_lot(cur_lot_name, + 704, 706, "703", "unit_test_res_parking_create_lot_con", /* The parkexten overlaps the range of the base */ + test); + if (expect_fail4) { + ast_test_status_update(test, "%s was successfully created when it was expected to fail. Test failed.\n", cur_lot_name); + failed = 1; + goto cleanup; + } + + cur_lot_name = "unit_tests_res_parking_test_lot_success1"; + expect_success1 = generate_test_parking_lot(cur_lot_name, + 701, 703, "700", "unit_test_res_parking_create_lot_con_2", /* no overlap due to different context */ + test); + if (!expect_success1) { + ast_test_status_update(test, "%s failed to be created. Success was expected. Test failed.\n", cur_lot_name); + failed = 1; + goto cleanup; + } + + cur_lot_name = "unit_tests_res_parking_test_lot_success2"; + expect_success2 = generate_test_parking_lot(cur_lot_name, + 601, 605, "600", "unit_test_res_parking_create_lot_con", /* no overlap due to different extensions and ranges */ + test); + if (!expect_success2) { + ast_test_status_update(test, "%s failed to be created. Success was expected. Test failed.\n", cur_lot_name); + failed = 1; + goto cleanup; + } + + cur_lot_name = "unit_tests_res_parking_test_lot_success3"; + expect_success3 = generate_test_parking_lot(cur_lot_name, + 704, 706, "testsuccess3", "unit_test_res_parking_create_lot_con", /* no overlap because the parking spaces start 1 above existing ranges */ + test); + if (!expect_success3) { + ast_test_status_update(test, "%s failed to be created. Success was expected. Test failed.\n", cur_lot_name); + failed = 1; + goto cleanup; + } + + cur_lot_name = "unit_tests_res_parking_test_lot_success4"; + expect_success4 = generate_test_parking_lot(cur_lot_name, + 697, 699, "testsuccess4", "unit_test_res_parking_create_lot_con", /* no overlap because the parking spaces end 1 below existing ranges */ + test); + if (!expect_success4) { + failed = 1; + goto cleanup; + } + +cleanup: + if (base_lot && dispose_test_lot(base_lot, 1)) { + ast_test_status_update(test, "Found base parking lot in container after attempted removal. Test failed.\n"); + failed = 1; + } + + if (expect_fail1) { + dispose_test_lot(expect_fail1, 1); + failed = 1; + } + + if (expect_fail2) { + dispose_test_lot(expect_fail2, 1); + failed = 1; + } + + if (expect_fail3) { + dispose_test_lot(expect_fail3, 1); + failed = 1; + } + + if (expect_fail4) { + dispose_test_lot(expect_fail4, 1); + failed = 1; + } + + if (expect_success1 && dispose_test_lot(expect_success1, 1)) { + ast_test_status_update(test, "Found expect_success1 parking lot in container after attempted removal. Test failed.\n"); + failed = 1; + } + + if (expect_success2 && dispose_test_lot(expect_success2, 1)) { + ast_test_status_update(test, "Found expect_success2 parking lot in container after attempted removal. Test failed.\n"); + failed = 1; + } + + if (expect_success3 && dispose_test_lot(expect_success3, 1)) { + ast_test_status_update(test, "Found expect_success3 parking lot in container after attempted removal. Test failed.\n"); + failed = 1; + } + + if (expect_success4 && dispose_test_lot(expect_success4, 1)) { + ast_test_status_update(test, "Found expect_success4 parking lot in container after attempted removal. Test failed.\n"); + } + + return failed ? AST_TEST_FAIL : AST_TEST_PASS; +} + +AST_TEST_DEFINE(dynamic_parking_variables) +{ + RAII_VAR(struct parking_lot *, template_lot, NULL, ao2_cleanup); + RAII_VAR(struct parking_lot *, dynamic_lot, NULL, ao2_cleanup); + RAII_VAR(struct ast_channel *, chan_alice, NULL, safe_channel_release); + RAII_VAR(struct parking_lot_cfg *, expected_cfg, NULL, ao2_cleanup); + + int failed = 0; + + switch (cmd) { + case TEST_INIT: + info->name = "dynamic_parking_variables"; + info->category = TEST_CATEGORY; + info->summary = "Tests whether dynamic parking lot creation respects channel variables"; + info->description = + "Creates a template parking lot, creates a channel, sets dynamic parking variables, and then creates a parking lot for that channel"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + ast_test_status_update(test, "Creating expected configuration for dynamic parking lot\n"); + + expected_cfg = parking_lot_cfg_create("unit_tests_res_parking_test_lot_dynamic"); + + if (!expected_cfg) { + ast_test_status_update(test, "Failed to create expected configuration. Test failed.\n"); + return AST_TEST_FAIL; + } + + expected_cfg->parking_start = 751; + expected_cfg->parking_stop = 760; + expected_cfg->parkingtime = 10; + expected_cfg->comebackdialtime = 10; + expected_cfg->parkfindnext = 1; + expected_cfg->parkext_exclusive = 1; + ast_string_field_set(expected_cfg, parkext, "750"); + ast_string_field_set(expected_cfg, parking_con, "unit_test_res_parking_create_lot_dynamic"); + ast_string_field_set(expected_cfg, comebackcontext, "unit_test_res_parking_create_lot_comeback"); + + ast_test_status_update(test, "Creating template lot\n"); + + template_lot = generate_test_parking_lot(TEST_LOT_NAME, 701, 703, "700", "unit_test_res_parking_create_lot_con", test); + + if (!template_lot) { + ast_test_status_update(test, "Failed to generate template lot. Test failed.\n"); + return AST_TEST_FAIL; + } + + ast_test_status_update(test, "Creating Alice channel to test dynamic parking lot creation.\n"); + + chan_alice = create_alice_channel(); + + if (!chan_alice) { + ast_test_status_update(test, "Failed to create Alice channel. Test failed.\n"); + failed = 1; + goto cleanup; + } + + ast_test_status_update(test, "Setting Dynamic Parking channel variables on Alice.\n"); + + pbx_builtin_setvar_helper(chan_alice, "PARKINGDYNAMIC", TEST_LOT_NAME); + pbx_builtin_setvar_helper(chan_alice, "PARKINGLOT", "unit_test_res_parking_create_lot_dynamic"); + pbx_builtin_setvar_helper(chan_alice, "PARKINGDYNCONTEXT", "unit_test_res_parking_create_lot_dynamic"); + pbx_builtin_setvar_helper(chan_alice, "PARKINGDYNEXTEN", "750"); + pbx_builtin_setvar_helper(chan_alice, "PARKINGDYNPOS", "751-760"); + + ast_test_status_update(test, "Generating dynamic parking lot based on Alice's channel variables."); + + dynamic_lot = parking_create_dynamic_lot_forced("unit_tests_res_parking_test_lot_dynamic", chan_alice); + + if (!dynamic_lot) { + ast_test_status_update(test, "Failed to create dynamic parking lot. Test failed.\n"); + failed = 1; + goto cleanup; + } + + /* Check stats */ + if (!parking_lot_cfgs_match(dynamic_lot->cfg, expected_cfg, test)) { + ast_test_status_update(test, "Dynamic parking lot configuration did not match Expectations.\n"); + failed = 1; + goto cleanup; + } + + ast_test_status_update(test, "Dynamic parking lot created successfully and matches expectations. Test passed.\n"); + +cleanup: + if (template_lot && dispose_test_lot(template_lot, 1)) { + ast_test_status_update(test, "Found template parking lot in container after attempted removal. Test failed.\n"); + failed = 1; + } + + if (dynamic_lot && dispose_test_lot(dynamic_lot, 1)) { + ast_test_status_update(test, "Found dynamic parking lot in container after attempted removal. Test failed.\n"); + failed = 1; + } + + return failed ? AST_TEST_FAIL : AST_TEST_PASS; +} + +#endif /* TEST_FRAMEWORK */ + + +int unload_parking_tests(void) +{ +/* NOOP without test framework */ +#if defined(TEST_FRAMEWORK) + + int res = 0; + + res |= AST_TEST_UNREGISTER(create_lot); + res |= AST_TEST_UNREGISTER(park_call); + res |= AST_TEST_UNREGISTER(retrieve_call); + res |= AST_TEST_UNREGISTER(park_extensions); + res |= AST_TEST_UNREGISTER(extension_conflicts); + res |= AST_TEST_UNREGISTER(dynamic_parking_variables); + + return res; +#endif + + return 0; +} + +int load_parking_tests(void) +{ +/* NOOP without test framework */ +#if defined(TEST_FRAMEWORK) + + int res = 0; + + res |= AST_TEST_REGISTER(create_lot); + res |= AST_TEST_REGISTER(park_call); + res |= AST_TEST_REGISTER(retrieve_call); + res |= AST_TEST_REGISTER(park_extensions); + res |= AST_TEST_REGISTER(extension_conflicts); + res |= AST_TEST_REGISTER(dynamic_parking_variables); + + return res; +#endif + + return 0; +} diff --git a/res/parking/res_parking.h b/res/parking/res_parking.h index b2e56347d..b376a99e1 100644 --- a/res/parking/res_parking.h +++ b/res/parking/res_parking.h @@ -110,6 +110,20 @@ struct parked_user { enum park_call_resolution resolution; /*!< How did the parking session end? If the call is in a bridge, lock parked_user before checking/setting */ }; +#if defined(TEST_FRAMEWORK) +/*! + * \since 12.0.0 + * \brief Create an empty parking lot configuration structure + * useful for unit tests. + * + * \param cat name given to the parking lot + * + * \retval NULL failure + * \retval non-NULL successfully allocated parking lot + */ +struct parking_lot_cfg *parking_lot_cfg_create(const char *cat); +#endif + /*! * \since 12.0.0 * \brief If a parking lot exists in the parking lot list already, update its status to match the provided @@ -268,10 +282,25 @@ struct parking_lot *parking_lot_find_by_name(const char *lot_name); * \retval NULL on error * * \note This should be called only after verifying that the named parking lot doesn't already exist in a non-dynamic way. - * The parking lots container should be locked before verifying and remain locked until after this function is called. */ struct parking_lot *parking_create_dynamic_lot(const char *name, struct ast_channel *chan); +#if defined(TEST_FRAMEWORK) +/*! + * \since 12.0.0 + * \brief Create a dynamic parking lot without respect to whether they are enabled by configuration + * + * \param name Dynamic parking lot name to create + * \param chan Channel parkee to get the dynamic parking lot parameters from + * + * \retval dynamically created parking lot on success + * \retval NULL on error + * + * \note This should be called only after verifying that the named parking lot doesn't already exist in a non-dynamic way. + */ +struct parking_lot *parking_create_dynamic_lot_forced(const char *name, struct ast_channel *chan); +#endif + /*! * \since 12.0.0 * \brief Find parking lot name from channel @@ -525,3 +554,21 @@ int load_parking_devstate(void); * \brief Unregister Parking devstate handler */ void unload_parking_devstate(void); + +/*! + * \since 12.0.0 + * \brief Register parking unit tests + * + * \retval 0 on success + * \retval nonzero on failure + */ +int load_parking_tests(void); + +/*! + * \since 12.0.0 + * \brief Unregister parking unit tests + * + * \retval 0 on success + * \retval nonzero on failure + */ +int unload_parking_tests(void); -- cgit v1.2.3