def test_shorten_string(self): s = ( "Do you see any Teletubbies in here?" "Do you see a slender plastic tag clipped to my shirt with my name printed on it?" ) assert shorten_string(None, 10) is None assert len(shorten_string(s, 20)) == 20 assert len(shorten_string(s, 20, min_tail_chars=7)) == 20 assert shorten_string(s, 20) == "Do you see any [...]" assert shorten_string(s, 20, min_tail_chars=7) == "Do you s[...] on it?"
def _format_response(cls, resp, short=False, add_headers=False, ruler=True): try: s = resp.json() s = pformat(s) except ValueError: s = resp.text lines = [] for s in s.split("\n"): s = s.rstrip() if s: lines.append(s) s = "\n".join(lines) # s = fill(s) res = [] res.append("") res.append("--- Response status: {}, len: {:,} bytes: >>>".format( resp.status_code, len(resp.content))) for k, v in resp.headers.items(): res.append("{}: {}".format(k, v)) res.append("- " * 20) if short: s = shorten_string(s, 500, 100, place_holder="\n[...]\n") res.append(s) res.append("<<< ---") return "\n".join(res)
def report_activity_error(self, sequence, activity, activity_args, exc): """Called session runner when activity `execute()` or assertions raise an error.""" # self.stats.inc("errors") if isinstance(exc, SkippedError): logger.warning(yellow("Skipped {}".format(activity))) self.pending_activity = None return # Create a copy of the current context, so we can shorten values context = self.context_stack.context.copy() context["last_result"] = shorten_string(context.get("last_result"), 500, 100) msg = [] # msg.append("{} {}: {!r}:".format(self.context_stack, activity, exc)) # msg.append("{!r}:".format(exc)) msg.append("{!r}:".format(exc)) if isinstance(exc, ActivityAssertionError): msg.append("Failed assertions:") for err in exc.assertion_list: msg.append(" - {}".format(err)) msg.append("Execution path: {}".format(self.context_stack)) msg.append("Activity: {}".format(activity)) msg.append("Activity args: {}".format(activity_args)) msg.append("Context: {}".format(context)) msg = "\n ".join(msg) logger.error(red(msg)) self.stats.report_error(self, sequence, activity, error=msg) return
def __init__(self, config_manager, **activity_args): """""" super().__init__(config_manager, **activity_args) path = activity_args.get("path") script = activity_args.get("script") # Allow to pass `export: null` to define 'no export wanted' # (omitting the argumet is considered 'undefined' and will emit a # warning if the script produces variables) export = activity_args.get("export", NO_DEFAULT) if export in (None, False): export = tuple() elif export is NO_DEFAULT: export = None check_arg(path, str, or_none=True) check_arg(script, str, or_none=True) check_arg(export, (str, list, tuple), or_none=True) if path: if script: raise ActivityCompileError( "`path` and `script` args are mutually exclusive" ) path = config_manager.resolve_path(path) with open(path, "rt") as f: script = f.read() #: self.script = compile(script, path, "exec") elif script: # script = dedent(self.script) self.script = compile(script, "<string>", "exec") else: raise ActivityCompileError("Either `path` or `script` expected") #: Store a shortened code snippet for debug output self.source = shorten_string(dedent(script), 500, 100) # print(self.source) if export is None: self.export = None elif isinstance(export, str): self.export = set((export,)) else: self.export = set(export) return
def _process_activity_result(self, activity, activity_args, result, elap): """Perform common checks. Raises: ActivityAssertionError """ context = self.context_stack.context errors = [] # warnings = [] arg = float(activity_args.get("assert_max_time", 0)) if arg and elap > arg: errors.append( "Execution time limit of {} seconds exceeded: {:.3} sec.". format(arg, elap)) arg = activity_args.get("assert_match") if arg: text = str(result) # Note: use re.search (not .match)! if not re.search(arg, text, re.MULTILINE): errors.append("Result does not match `{}`: {!r}".format( arg, shorten_string(text, 500, 100))) arg = activity_args.get("store_json") if arg: for var_name, key_path in arg.items(): try: val = get_dict_attr(result, key_path) context[var_name] = val except Exception: errors.append( "store_json could not find `{}` in activity result {!r}" .format(key_path, result)) if errors: raise ActivityAssertionError(errors) return
def _add_error(self, d, error): d.setdefault("errors", 0) d["errors"] += 1 d["last_error"] = shorten_string("{}".format(error), 500, 100)
def execute(self, session, **expanded_args): """""" global_vars = { # "foo": 41, # "__builtins__": {}, } # local_vars = session.context local_vars = session.context.copy() assert "result" not in local_vars assert "session" not in local_vars local_vars["session"] = session.make_helper() # prev_local_keys = set(locals()) prev_global_keys = set(globals()) prev_context_keys = set(local_vars.keys()) try: exec(self.script, global_vars, local_vars) except ConnectionError as e: # TODO: more requests-exceptions? msg = "Script failed: {!r}: {}".format(e, e) raise ScriptActivityError(msg) except Exception as e: msg = "Script failed: {!r}: {}".format(e, e) if session.verbose >= 4: logger.exception(msg) raise ScriptActivityError(msg) from e raise ScriptActivityError(msg) finally: local_vars.pop("session") result = local_vars.pop("result", None) context_keys = set(local_vars.keys()) new_keys = context_keys.difference(prev_context_keys) if new_keys: if self.export is None: logger.info( "Skript activity has no `export` defined. Ignoring new variables: '{}'".format( "', '".join(new_keys) ) ) else: for k in self.export: v = local_vars.get(k) assert type(v) in (int, float, str, list, dict) session.context[k] = v logger.debug("Set context.{} = {!r}".format(k, v)) # store_keys = new_keys.intersection(self.export) # TODO: this cannot happen? new_globals = set(globals().keys()).difference(prev_global_keys) if new_globals: logger.warning("Script-defined globals: {}".format(new_globals)) raise ScriptActivityError("Script introduced globals") # new_context = context_keys.difference(prev_context_keys) # logger.info("Script-defined context-keys: {}".format(new_context)) # new_locals = set(locals().keys()).difference(prev_local_keys) # if new_locals: # logger.info("Script-defined locals: {}".format(new_locals)) # logger.info("Script locals:\n{}".format(pformat(local_vars))) if expanded_args.get("debug") or session.verbose >= 5: logger.info( "{} {}\n Context after execute:\n {}\n return value: {!r}".format( session.context_stack, self, pformat(session.context, indent=4), result, ) ) elif session.verbose >= 3 and result is not None: logger.info( "{} returnd: {!r}".format( session.context_stack, shorten_string(result, 200) if isinstance(result, str) else result, ) ) return result
def _write_entry(self, fp, entry): opts = self.opts activity = self.activity_map.get(entry["method"], "HTTPRequest") url_list = entry.get("url_list") is_bucket = bool(url_list) and len(url_list) > 1 if is_bucket: activity = "StaticRequests" lines = [] if entry.get("comment"): lines.append("# {}\n".format(shorten_string(entry["comment"], 75))) if is_bucket: lines.append("# Auto-collated {:,} GET requests\n".format( len(url_list))) else: lines.append( "# Response type: {!r}, size: {:,} bytes, time: {}\n".format( entry.get("resp_type"), entry.get("resp_size", -1), format_elap(entry.get("elap"), high_prec=True), )) if entry.get("resp_comment"): lines.append("# {}\n".format( shorten_string(entry["resp_comment"], 75))) url = entry["url"] expand_url_params = False lines.append("- activity: {}\n".format(activity)) if is_bucket: lines.append(" thread_count: {}\n".format( opts["collate_thread_count"])) lines.append(" url_list:\n") for url in url_list: lines.append(" - '{}'\n".format(url)) else: if entry.get("query"): expand_url_params = True url = base_url(url) lines.append(" url: '{}'\n".format(url)) if activity == "HTTPRequest": lines.append(" method: {}\n".format(entry["method"])) # TODO: # We have ?query also as part of the URL # if entry.get("query"): # lines.append(" params: {}\n".format(entry["query"])) if expand_url_params: self._write_args(lines, "params", entry["query"]) # TODO: set headers = {'Content-type': 'content_type_value'} # if entry.dData.mimeType is defined data = entry.get("data") if data: if isinstance(data, (list, tuple)): # Must be a 'params' list of {name: ..., value: ...} objects self._write_args(lines, "data", data) else: assert type(data) is str logger.warning( "Expected list, but got text: {!r}".format(data)) lines.append(" data: {}\n".format(json.dumps(data))) lines.append("\n") fp.writelines(lines)