def _process_properties(self): """Process test properties block""" if self.config is None: return if "properties" not in self.config: return properties = self.config["properties"] minversion = properties.get("minversion", ["1.4"]) if not isinstance(minversion, list): minversion = [minversion] self.minversion = [AsteriskVersion(ver) for ver in minversion] maxversion = properties.get("maxversion", []) if not isinstance(maxversion, list): maxversion = [maxversion] self.maxversion = [AsteriskVersion(ver) for ver in maxversion] self.expect_pass = (properties.get("expectedResult", self.expect_pass) and properties.get("expected-result", self.expect_pass)) if "tags" in properties: self.tags = properties["tags"] if "features" in properties: self.features = set(properties["features"]) if "forced-version" in properties: self.forced_version = AsteriskVersion(properties["forced-version"]) for ver in self.minversion: if ver.feature: self.features.add(ver.feature) for feature in self.features: self.feature_check[feature] = False
def _process_properties(self): """Process test properties block""" self.minversion = AsteriskVersion("1.4") if self.config == None: return if "properties" not in self.config: return properties = self.config["properties"] if "minversion" in properties: self.minversion = AsteriskVersion(properties["minversion"]) if self.minversion.feature: self.features.append(self.minversion.feature) self.feature_check[self.minversion.feature] = False if "maxversion" in properties: self.maxversion = AsteriskVersion(properties["maxversion"]) self.expect_pass = (properties.get("expectedResult") or properties.get("expected-result") or True) if "tags" in properties: self.tags = properties["tags"] if "features" in properties: self.features.extend(properties["features"]) for feature in self.features: self.feature_check[feature] = False if "forced-version" in properties: self.forced_version = AsteriskVersion(properties["forced-version"])
def _handle_ami_connect(self, ami): ''' Handle AMI connect events ''' if (ami.id != 0): return ami.registerEvent('Newstate', self._handle_new_state) if AsteriskVersion() >= AsteriskVersion('12'): ami.registerEvent('AttendedTransfer', self._handle_attended_transfer)
def main(argv=None): """Main entry point for the test run Returns: 0 on successful test run 1 on any error """ if argv is None: args = sys.argv if (len(args) < 2): LOGGER.error("test_runner requires the full path to the test " "directory to execute") return 1 test_directory = args[1] if (len(args) < 3): LOGGER.error("test_runner requires the Asterisk version to execute") return 1 ast_version = args[2] try: AsteriskVersion() except OSError: # If there is no Asterisk version on the local system, then we need to # set the version to the one that was passed into the application. AsteriskVersion(default=ast_version) LOGGER.info("Starting test run for %s" % test_directory) test_config = load_test_config(test_directory) if test_config is None: return 1 read_module_paths(test_config, test_directory) test_object = create_test_object(test_directory, test_config, ast_version) if test_object is None: return 1 # Load other modules that may be specified load_test_modules(test_config, test_object, ast_version) # Load global modules as well if test_object.global_config.config: load_test_modules(test_object.global_config.config, test_object, ast_version) # Kick off the twisted reactor reactor.run() LOGGER.info("Test run for %s completed with result %s" % (test_directory, str(test_object.passed))) if test_object.evaluate_results(): return 0 return 1
def ami_connect(self, ami): # We only care about the UUT's AMI here if ami.id != 0: return self.ami = ami self.ami.registerEvent('Hangup', self.log_hangup_time) self.ami.registerEvent('TestEvent', self.log_warnings) if AsteriskVersion() >= AsteriskVersion('12'): self.ami.registerEvent('BridgeEnter', self.log_bridge_enter_time) else: self.ami.registerEvent('Bridge', self.log_bridge_time)
def _create_application_event_instances(self, channel_id, events): for event_config in events: minversion = event_config.get('minversion') maxversion = event_config.get('maxversion') if (minversion is not None and AsteriskVersion() < AsteriskVersion(minversion)): continue if (maxversion is not None and AsteriskVersion() >= AsteriskVersion(maxversion)): continue ae_instance = ApplicationEventInstance(channel_id, event_config, self) self._event_instances.append(ae_instance)
def get_vars(ari, channel_id): resp = ari.get('channels', channel_id, 'variable', variable='DP_SHELL') actual = resp.json()["value"] eq('works', actual) if AsteriskVersion() >= AsteriskVersion('13'): ari.set_allow_errors(True) resp = ari.get('channels', channel_id, 'variable', variable='SHELL(echo -n fail)') if AsteriskVersion() >= AsteriskVersion('13'): ari.set_allow_errors(False) eq(500, resp.status_code) else: eq(200, resp.status_code) eq(resp.json().get('value'), '')
def handle_parkedcall(ami, event): running_version = AsteriskVersion() if running_version >= AsteriskVersion("12.0.0"): parkee = event.get('parkeechannel') else: parkee = event.get('channel') if parkee is None: LOGGER.error("Receved ParkedCall event without a Parkee.\n") return False LOGGER.info("Hanging up channel: %s" % parkee) ami.hangup(parkee) return True
def __init__(self, module_config, test_object): self.ami = None self.alice_ami = None self.parked_channel = None test_object.register_ami_observer(self.ami_connect) self.test_object = test_object running_version = AsteriskVersion() if (running_version < AsteriskVersion("12.0.0")): self.asterisk12Events = False else: self.asterisk12Events = True self.calls = [] self.calls.append({ 'parker': 'SIP/alice', 'lot': 'parkinglot_test1', 'slot': '401', 'status': 'ANSWER', 'post': False }) self.calls.append({ 'parker': 'SIP/alice', 'lot': 'parkinglot_test1', 'slot': '402', 'status': 'NOANSWER', 'post': True }) self.calls.append({ 'parker': 'SIP/alice', 'lot': 'parkinglot_test1', 'slot': '403', 'status': 'BUSY', 'post': True }) self.current_call = None self.current_call_post = False # Automatically fail if we don't remove this token. self.fail_token = \ self.test_object.create_fail_token("This test should fail all the " "time right now.") self.userevents_received = 0 self.failures_logged = 0 self.parks_received = 0
def __send_stop_gracefully(): """Send a core stop gracefully CLI command""" LOGGER.debug('sending stop gracefully') if self.ast_version and self.ast_version < AsteriskVersion("1.6.0"): cli_deferred = self.cli_exec("stop gracefully") else: cli_deferred = self.cli_exec("core stop gracefully") cli_deferred.addCallbacks(__stop_gracefully_callback, __stop_gracefully_error)
def __init__(self, module_config, test_object): ''' Constructor Keyword Arguments: module_config The module configuration test_object The test object. Must be of type BridgeTestCase ''' self.module_config = module_config self.test_object = test_object self._current_feature = None self.test_object.register_feature_start_observer(self._handle_feature_start) if AsteriskVersion() >= AsteriskVersion('12'): self.test_object.register_ami_observer(self._handle_ami_connect) else: self.test_object.register_feature_end_observer(self._handle_feature_end) if (Transfer.__singleton_instance == None): Transfer.__singleton_instance = self
def __init__(self, module_config, test_object): self.ami = None self.parked_channel = None test_object.register_ami_observer(self.ami_connect) self.test_object = test_object running_version = AsteriskVersion() self.calls = [] self.calls.append({'parker': 'alice', 'parkee': 'bob'}) self.calls.append({'parker': 'bob', 'parkee': 'alice'}) # Parking events for this test vary with Asterisk 12 and # up from prior versions. if (running_version < AsteriskVersion("12.0.0")): self.asterisk12Events = False else: self.asterisk12Events = True self.parking_events_received = 0 return
def __init__(self, module_config, test_object): self.ami = None self.parked_channel = None test_object.register_ami_observer(self.ami_connect) self.test_object = test_object running_version = AsteriskVersion() if (running_version < AsteriskVersion("12.0.0")): self.asterisk12Events = False else: self.asterisk12Events = True self.calls = [] self.calls.append({ 'test': '1', 'parker': 'SIP/alice', 'lot': 'parkinglot_test1', 'slot': '401' }) self.calls.append({ 'test': '2', 'parker': 'SIP/alice', 'lot': 'parkinglot_test2', 'slot': '501' }) self.calls.append({ 'test': '3', 'parker': 'SIP/alice', 'lot': 'parkinglot_test3', 'slot': '601' }) self.userevents_received = 0 self.passed_dialplan = 0 self.failures_logged = 0 self.fail_token = \ self.test_object.create_fail_token("No success indicated by " "Executioner.") return
def check_module_version(module_spec, ast_version): """Check the module configuration for minversion and maxversion and check if the Asterisk version meets the version(s) if found Keyword Arguments: module_spec A dictionary of a pluggable module configuration ast_version A string containing the Asterisk version Returns: False if minversion or maxversion are found and do not meet the Asterisk version, True otherwise """ modminversion = module_spec.get('minversion') modmaxversion = module_spec.get('maxversion') if (modminversion is not None and AsteriskVersion(ast_version) < AsteriskVersion(modminversion)): return False if (modmaxversion is not None and AsteriskVersion(ast_version) >= AsteriskVersion(modmaxversion)): return False return True
def check_duration(self, ami_uut, ami_alice, ami_bob): if not self.bridge_time or not self.end_time: LOGGER.error("We didn't get the notifications for duration") self.test_object.set_passed(False) duration = self.end_time - self.bridge_time if (abs(duration - self.current_call['timeout']) > TOLERANCE): LOGGER.error("Call duration was %f but we expected %f (+/- 0.5 sec)" % (duration, self.current_call['timeout'])) self.test_object.set_passed(False) if self.current_call['numwarnings'] != self.num_warnings: LOGGER.error("We expected %d warnings but got %d" % (self.current_call['numwarnings'], self.num_warnings)) self.test_object.set_passed(False) max_triggers = 1 if self.current_call['hangup_style'] == 'BRIDGE_TIMELIMIT' \ and AsteriskVersion() >= AsteriskVersion('12'): max_triggers = 2 if 1 != self.num_hangup_triggers and max_triggers != self.num_hangup_triggers: if max_triggers == 2: LOGGER.error("We expected 1 or 2 hangup triggers but got %d" % (self.num_hangup_triggers)) else: LOGGER.error("We expected 1 hangup trigger but got %d" % (self.num_hangup_triggers)) self.test_object.set_passed(False) # Reset the variables for the next call self.bridge_enters_received = self.num_hangup_triggers = 0 self.bridge_time = self.end_time = self.num_warnings = 0 if len(self.calls) != 0: self.current_call = self.calls.pop(0)
def __init__(self, test_object, accounts): super(Transfer, self).__init__() self.ami = test_object.ami[0] if AsteriskVersion() < AsteriskVersion('12'): self.bridge_state = BridgeStateEleven(test_object, self, self.ami) else: self.bridge_state = BridgeStateTwelve(test_object, self, self.ami) self.bob_call_answered = False self.carol_call_answered = False bob = accounts.get('bob').account bob.set_callback(TransferAccountCallback(bob)) carol = accounts.get('carol').account carol.set_callback(TransferAccountCallback(carol)) self.alice = accounts.get('alice').account self.call_to_bob = None self.call_to_carol = None self.test_object = test_object self.state = INIT
def cli_originate(self, argstr): """Starts a call from the CLI and links it to an application or context. Must pass a valid argument string in the following form: <tech/data> application <appname> appdata <tech/data> extension <exten>@<context> If no context is specified, the 'default' context will be used. If no extension is given, the 's' extension will be used. Keyword Arguments: argstr Arguments to be passed to the originate Returns: A deferred object that can be used to listen for command completion Example Usage: asterisk.originate("Local/a_exten@context extension b_exten@context") """ args = argstr.split() raise_error = False if len(args) != 3 and len(args) != 4: raise_error = True LOGGER.error("Wrong number of arguments.") if args[1] != "extension" and args[1] != "application": raise_error = True LOGGER.error('2nd argument must be "extension" or "application"') if args[0].find("/") == -1: raise_error = True LOGGER.error('Channel dial string must be in the form "tech/data".') if raise_error is True: raise Exception( "Cannot originate call!\n" "Argument string must be in one of these forms:\n" "<tech/data> application <appname> appdata\n" "<tech/data> extension <exten>@<context>") if self.ast_version and self.ast_version < AsteriskVersion("1.6.2"): return self.cli_exec("originate %s" % argstr) else: return self.cli_exec("channel originate %s" % argstr)
class ThreadTestCondition(TestCondition): """Base class for the thread pre-/post-test conditions This class is a base class for the pre- and post-test condition classes that check thread usage in Asterisk. It provides common functionality for parsing out the results of the 'core show threads' Asterisk command """ _ast_version = AsteriskVersion() _ast_version_10 = AsteriskVersion("10") def __init__(self, test_config): """Constructor Keyword Arguments: test_config The condition test configuration """ super(ThreadTestCondition, self).__init__(test_config) # ast_threads is a list of tuples, where the first entry is the host # IP of the Asterisk instance, and the second entry is a list of # tuples of thread ID and thread name self.ast_threads = [] self.ignored_threads = (test_config.config.get('ignoredThreads') or test_config.config.get('ignored-threads') or []) # Core show threads is not available if LOW_MEMORY is turned on self.add_build_option("LOW_MEMORY", "0") def parse_threads(self, ast, threads): """Parse the thread values from the result of the Asterisk command Keyword Arguments: ast The Asterisk instance to check threads The result of 'core show threads' """ ast_host = ast.host thread_list = [] tokens = threads.split('\n') for line in tokens: if 'threads listed' in line or 'Asterisk ending' in line: continue # get the name and thread ID - strip off the cli_exec / pthread ID initial_partition = line.partition(' ') #In v10 and greater, the result of core show threads introduces the # Asterisk thread ID immediately after the pthread ID. Use that if # it's available. if (ThreadTestCondition._ast_version >= ThreadTestCondition._ast_version_10): initial_partition = initial_partition[2].partition(' ') thread_id = initial_partition[0] thread_name = initial_partition[2].partition(' ')[0] if (thread_id != "" and thread_name != "" and thread_name not in self.ignored_threads): LOGGER.debug("Tracking thread %s[%s]" % (thread_name, thread_id)) thread_list.append((thread_id, thread_name)) if (len(thread_list) > 0): self.ast_threads.append((ast_host, thread_list))
def __init__(self, test_path='', test_config=None): """Create a new instance of a TestCase. Must be called by inheriting classes. Keyword Arguments: test_path Optional parameter that specifies the path where this test resides test_config Loaded YAML test configuration """ if not len(test_path): self.test_name = os.path.dirname(sys.argv[0]) else: self.test_name = test_path # We're not using /tmp//full//test//name because it gets so long that # it doesn't fit in AF_UNIX paths (limited to around 108 chars) used # for the rasterisk CLI connection. As a quick fix, we hash the path # using md5, to make it unique enough. self.realbase = self.test_name.replace("tests/", "", 1) self.base = md5(self.realbase).hexdigest() # We provide a symlink to it from a named path. named_dir = os.path.join(Asterisk.test_suite_root, self.realbase) try: os.makedirs(os.path.dirname(named_dir)) except OSError: pass try: join_path = os.path.relpath( os.path.join(Asterisk.test_suite_root, self.base), os.path.dirname(named_dir) ) os.symlink(join_path, named_dir) except OSError: pass self.ast = [] self.ami = [] self.fastagi = [] self.base_config_path = None self.reactor_timeout = 30 self.passed = None self.fail_tokens = [] self.timeout_id = None self.global_config = TestConfig(os.getcwd()) self.test_config = TestConfig(self.test_name, self.global_config) self.condition_controller = None self.pcap = None self.pcapfilename = None self.create_pcap = False self._stopping = False self.testlogdir = self._set_test_log_directory() self.ast_version = AsteriskVersion() self._start_callbacks = [] self._stop_callbacks = [] self._ami_callbacks = [] self._pcap_callbacks = [] self._stop_deferred = None log_full = True log_messages = True if os.getenv("VALGRIND_ENABLE") == "true": self.reactor_timeout *= 20 # Pull additional configuration from YAML config if possible if test_config: if 'config-path' in test_config: self.base_config_path = test_config['config-path'] if 'reactor-timeout' in test_config: self.reactor_timeout = test_config['reactor-timeout'] self.ast_conf_options = test_config.get('ast-config-options') log_full = test_config.get('log-full', True) log_messages = test_config.get('log-messages', True) else: self.ast_conf_options = None os.makedirs(self.testlogdir) # Set up logging setup_logging(self.testlogdir, log_full, log_messages) LOGGER.info("Executing " + self.test_name) if PCAP_AVAILABLE and self.create_pcap: self.pcapfilename = os.path.join(self.testlogdir, "dumpfile.pcap") self.pcap = self.create_pcap_listener(dumpfile=self.pcapfilename) self._setup_conditions() # Enable twisted logging observer = log.PythonLoggingObserver() observer.start() reactor.callWhenRunning(self._run)
def __init__(self, base=None, ast_conf_options=None, host="127.0.0.1", remote_config=None): """Construct an Asterisk instance. Keyword Arguments: base -- This is the root of the files associated with this instance of Asterisk. By default, the base is "/tmp/asterisk-testsuite" directory. Given a base, it will be appended to the default base directory. ast_conf_options -- Configuration overrides for asterisk.conf. host -- The IP address the Asterisk instance runs on remote_config -- Configuration section that defines this as a remote Asterisk instance. If provided, base and ast_conf_options are generally ignored, and the Asterisk instance's configuration is treated as immutable on some remote machine defined by 'host' Example Usage: self.asterisk = Asterisk(base="manager/login") """ self._start_deferred = None self._stop_deferred = None self._stop_cancel_tokens = [] self.directories = {} self.protocol = None self.process = None self.astetcdir = "" self.original_astmoddir = "" self.ast_version = None self.remote_config = remote_config # If the process is remote, don't bother if not self.remote_config: self.ast_version = AsteriskVersion() valgrind_env = os.getenv("VALGRIND_ENABLE") or "" self.valgrind_enabled = True if "true" in valgrind_env else False if base is not None: self.base = base else: self.base = Asterisk.test_suite_root if self.localtest_root: self.ast_binary = self.localtest_root + "/usr/sbin/asterisk" else: ast_binary = test_suite_utils.which("asterisk") self.ast_binary = ast_binary or "/usr/sbin/asterisk" self.host = host self._ast_conf_options = ast_conf_options if self.remote_config: # Pretend as if we made the structure self._directory_structure_made = True self._configs_installed = True self._configs_set_up = True # And assume we've got /etc/asterisk/ where we expect it to be self.astetcdir = "/etc/asterisk" else: self._directory_structure_made = False self._configs_installed = False self._configs_set_up = False # Find the system installed asterisk.conf ast_confs = [ os.path.join(self.default_etc_directory, "asterisk.conf"), "/usr/local/etc/asterisk/asterisk.conf", ] self._ast_conf = None for config in ast_confs: if os.path.exists(config): self._ast_conf = ConfigFile(config) break if self._ast_conf is None: msg = "Unable to locate asterisk.conf in any known location" LOGGER.error(msg) raise Exception(msg) # Set which astxxx this instance will be i = 1 while True: if not os.path.isdir("%s/ast%d" % (self.base, i)): self.base = "%s/ast%d" % (self.base, i) break i += 1 # Get the Asterisk directories from the Asterisk config file for cat in self._ast_conf.categories: if cat.name == "directories": for (var, val) in cat.options: self.directories[var] = val # self.original_astmoddir is for dependency checking only if "astmoddir" in self.directories: if self.localtest_root: self.original_astmoddir = "%s%s" % ( self.localtest_root, self.directories["astmoddir"]) else: self.original_astmoddir = self.directories["astmoddir"]
def __init__(self, base=None, ast_conf_options=None, host="127.0.0.1"): """Construct an Asterisk instance. Keyword Arguments: base -- This is the root of the files associated with this instance of Asterisk. By default, the base is "/tmp/asterisk-testsuite" directory. Given a base, it will be appended to the default base directory. Example Usage: self.asterisk = Asterisk(base="manager/login") """ self._start_deferred = None self._stop_deferred = None self._stop_cancel_tokens = [] self.directories = {} self.ast_version = AsteriskVersion() self.process_protocol = None self.process = None self.astetcdir = "" self.original_astmoddir = "" if base is not None: self.base = base else: self.base = Asterisk.test_suite_root if self.localtest_root: self.ast_binary = self.localtest_root + "/usr/sbin/asterisk" else: ast_binary = test_suite_utils.which("asterisk") self.ast_binary = ast_binary or "/usr/sbin/asterisk" self.host = host self._ast_conf_options = ast_conf_options self._directory_structure_made = False self._configs_installed = False self._configs_set_up = False # Find the system installed asterisk.conf ast_confs = [ os.path.join(self.default_etc_directory, "asterisk.conf"), "/usr/local/etc/asterisk/asterisk.conf", ] self._ast_conf = None for config in ast_confs: if os.path.exists(config): self._ast_conf = ConfigFile(config) break if self._ast_conf is None: msg = "Unable to locate asterisk.conf in any known location" LOGGER.error(msg) raise Exception(msg) # Set which astxxx this instance will be i = 1 while True: if not os.path.isdir("%s/ast%d" % (self.base, i)): self.base = "%s/ast%d" % (self.base, i) break i += 1 # Get the Asterisk directories from the Asterisk config file for cat in self._ast_conf.categories: if cat.name == "directories": for (var, val) in cat.options: self.directories[var] = val # self.original_astmoddir is for dependency checking only if "astmoddir" in self.directories: if self.localtest_root: self.original_astmoddir = "%s%s" % ( self.localtest_root, self.directories["astmoddir"]) else: self.original_astmoddir = self.directories["astmoddir"]