Exemple #1
0
    def read(self, path, load_files=True):
        """Read a YAML file into ``self.config_all``.

        Raises:
            ConfigurationError
        """
        check_arg(load_files, bool)

        self.config_all = None

        if not path.lower().endswith(".yaml"):
            path += ".yaml"
        path = os.path.abspath(path)
        if not os.path.isfile(path):
            raise ConfigurationError("File not found: {}".format(path))
        self.path = path
        self.root_folder = os.path.dirname(path)
        self.name = os.path.splitext(os.path.basename(path))[0]

        with open(path, "rt") as f:
            try:
                res = yaml.safe_load(f)
            except yaml.parser.ParserError as e:
                raise ConfigurationError("Could not parse YAML: {}".format(e)) from None

        if not isinstance(res, dict) or not res.get("file_version", "").startswith(
            "stressor#"
        ):
            raise ConfigurationError(
                "Not a `stressor` file (missing 'stressor#VERSION' tag)."
            )

        self._compile(res)

        self.file_version = self.validate_config(res)

        if self.results["warning"]:
            logger.error("Compiler warnings:")
            for m in self.results["warning"]:
                logger.warning("  - {}: {}".format(m["path"], m["msg"]))

        if self.results["error"]:
            logger.error("Compiler errors:")
            for m in self.results["error"]:
                logger.error("  - {}: {}".format(m["path"], m["msg"]))
            raise ConfigurationError("Config file had compile errors.")

        self.config_all = res

        # Copy values from `config.*` to `context.*`
        if self.config_all.get("context") is None:
            self.config_all["context"] = {}
        for k, v in self.config.items():
            self.context.setdefault(k, v)

        return self.config_all
Exemple #2
0
    def _push(self, context):
        """
        Raises:
            RuntimeError if queue length exceeds ContextStack.MAX_DEPTH
        """
        check_arg(context, RunContext)

        if len(self.ctx_stack) >= self.MAX_DEPTH:
            raise RuntimeError("Max depth exceeded ({})".format(
                self.MAX_DEPTH))
        self.ctx_stack.append(context)
        return context
Exemple #3
0
def match_value(pattern, value, info):
    """Return ."""
    check_arg(pattern, str)

    value = str(value)
    if "." in pattern or "*" in pattern:
        match = re.match(pattern, value)  # , re.MULTILINE)
    else:
        match = pattern == value

    if not match:
        msg = "`{}` value {!r} does not match pattern {!r}".format(
            info, value, pattern)
        return False, msg
    return True, None
Exemple #4
0
    def push(self, name, attributes=None, copy_data=False):
        """Push `name` to the stack and optionally update or copy context.
        Args:
            name (str):
        Raises:
            RuntimeError if queue length exceeds ContextStack.MAX_DEPTH
        """
        check_arg(name, str)
        check_arg(attributes, dict, or_none=True)

        try:
            parent = self.peek()
        except IndexError:
            parent = None

        ctx = RunContext(parent, name, attributes, copy_data)
        return self._push(ctx)
Exemple #5
0
    def report_error(self, msg, level="error", exc=None, stack=None):
        """Called by activity and macro constructors to signal errors or warnings.

        The compiler also calls this when a constructor raises an exception.
        """
        check_arg(level, str, level in ("error", "warning"))
        path = stack if stack else str(self.stack)

        hint = "{}: {}".format(path, msg)
        if exc:
            logger.exception(hint)
        # No need to log, since self.results are also summarized later
        # elif level == "warning":
        #     logger.warning(hint)
        # else:
        #     logger.error(hint)

        self.results[level].append({"msg": msg, "path": path})
Exemple #6
0
    def update_config(self, extra_config, context_only=False):
        """Override self.config (and self.context) with new items.

        self.config was already copied to self.context, so normally we want to
        update both.

        Args:
            extra_config (dict): new values
            context_only (bool): pass true to only set the shadow-copy (i.e. context)
        """
        check_arg(extra_config, dict, or_none=True)
        if not extra_config:
            return
        config = self.config
        context = self.context
        for k, v in extra_config.items():
            if not context_only:
                logger.info("Set config.{}: {!r} -> {!r}".format(k, config.get(k), v))
                config[k] = v
            else:
                logger.info("Set context.{}: {!r} -> {!r}".format(k, context.get(k), v))
            context[k] = v
        return
Exemple #7
0
    def __init__(self, parent, name, update_attributes=None, copy_data=False):
        """
        Args:
            parent (:class:`RunContext`):
                The stack frame parent or None if this is the root.
            name (str):
                A short name for this context. The context manager uses it to concatenate
                a path string for the current scope.
            update_attributes (dict):
                A dict of attributes that are explicitly defined by this instance.
            copy_data (bool):

        """
        check_arg(parent, RunContext, or_none=True)
        check_arg(name, str, name != "")
        check_arg(update_attributes, dict, or_none=True)
        check_arg(copy_data, bool)

        self.parent = parent
        self.name = name
        if update_attributes is None:
            self.own_attributes = {}
        else:
            self.own_attributes = deepcopy(update_attributes)

        if parent is None:
            self.all_attributes = self.own_attributes
        elif copy_data:
            # Create a copy of the parent's context, so the original state is
            # restored on pull
            self.all_attributes = deepcopy(parent.all_attributes)
            if self.own_attributes:
                self.all_attributes.update(self.own_attributes)
        else:
            # We only reference the parent context instance, so change will persist
            # after a popping from this stack
            self.all_attributes = parent.all_attributes

        return
Exemple #8
0
    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
Exemple #9
0
    def run(self, options, extra_context=None):
        """Run the current

        Args:
            options (dict): see RunManager.DEFAULT_OPTS
            extra_context (dict, optional):
        Returns:
            (int) Exit code 0 if no errors occurred
        """
        check_arg(options, dict)
        check_arg(extra_context, dict, or_none=True)

        self.options.update(options)

        if extra_context:
            self.config_manager.update_config(extra_context)

        context = self.config_manager.context
        sessions = self.config_manager.sessions

        count = int(sessions.get("count", 1))
        if count > 1 and self.config_manager.config.get("force_single"):
            logger.info("force_single: restricting sessions count to one.")
            count = 1

        # Construct a `User` with at least 'name', 'password', and optional
        # custom attributes
        user_list = []
        for user_dict in sessions["users"]:
            user = User(**user_dict)
            user_list.append(user)
        # We have N users and want `count` sessions: re-use round-robin
        user_list = itertools.islice(itertools.cycle(user_list), 0, count)
        user_list = list(user_list)

        monitor = None
        if self.options.get("monitor"):
            monitor = MonitorServer(self)
            monitor.start()
            time.sleep(0.5)
            monitor.open_browser()

        self.start_stamp = time.monotonic()
        self.start_dt = datetime.now()
        self.end_dt = None
        self.end_stamp = None
        try:
            try:
                res = False
                res = self.run_in_threads(user_list, context)
            except KeyboardInterrupt:
                # if not self.stop_request.is_set():
                logger.warning("Caught Ctrl-C: terminating...")
                self.stop()
            finally:
                self.end_dt = datetime.now()
                self.end_stamp = time.monotonic()

            if self.options.get("log_summary", True):
                logger.important(self.get_cli_summary())

            if monitor:
                self.set_stage("waiting")
                logger.important("Waiting for monitor... Press Ctrl+C to quit.")
                self.stop_request.wait()
        finally:
            if monitor:
                monitor.shutdown()

            # print("RES", res, self.has_errors(), self.stats.format_result())
            self.set_stage("stopped")
        return res
Exemple #10
0
 def set_stage(self, stage):
     check_arg(stage, str, stage in self.STAGES)
     logger.info("Enter stage '{}'".format(stage.upper()))
     self.stage = stage
Exemple #11
0
    def __init__(self, config_manager, **activity_args):
        super().__init__(config_manager, **activity_args)

        check_arg(activity_args.get("method"), str)
        check_arg(activity_args.get("url"), str)
        check_arg(activity_args.get("params"), dict, or_none=True)
Exemple #12
0
    def __init__(self, run_manager, context, session_id, user):
        # check_arg(run_manager, RunManager)
        check_arg(context, dict)
        check_arg(session_id, str)
        check_arg(user, User, or_none=True)

        #: The :class:`RunManager` object that holds global settings and definitions
        self.run_manager = run_manager
        config = run_manager.config_manager.config

        # (dict) Global variables for this session. Initialized from the
        # run configuration, but not shared between sessions.
        # (`self.context` is accessible by the respective property below.)
        context = context.copy()
        #: (str) Unique ID string for this session
        self.session_id = session_id
        #: The :class:`User` object that is assigned to this session
        self.user = user or User("anonymous", "")
        #: (dict) Copy of `run_config.sessions` configuration
        self.sessions = run_manager.config_manager.sessions.copy()
        #: (dict) Activities can store per-session data here.
        #: Note that the activity objects are instintiated only once and shared
        #: by all sessions.
        self.data = {}
        #: (bool) True: only simulate activities
        self.dry_run = bool(context.get("dry_run"))
        #: (int) Verbosity 0..5
        self.verbose = context.get("verbose", 3)
        #: (:class:`threading.Event`)
        self.stop_request = run_manager.stop_request

        # Set some default entries in context dict
        # context.setdefault("timeout", self.DEFAULT_REQUEST_TIMEOUT)
        context.setdefault("session_id", self.session_id)
        context.setdefault("user", self.user)

        #: The :class:`~stressor.context_stack.ContextStack` object that reflects the current execution path
        self.context_stack = ContextStack(run_manager.host_id, context)
        self.context_stack.push(run_manager.process_id)
        self.context_stack.push(session_id)
        #: :class:`~stressor.statistic_manager.StatisticManager` object that containscurrent execution path
        self.stats = run_manager.stats
        # Lazy initialization using a property
        self._browser_session = None

        #: (int) Stop session if global error count > X
        #: Passing `--max-errors` will override this.
        self.max_errors = int(config.get("max_errors", 0))

        #: (float) Stop session if total run time  > X seconds.
        #: Passing `--max-time` will override this.
        self.max_time = float(config.get("max_time", 0.0))
        self._cancelled_seq = None

        # Used by StatisticsManager
        self.pending_sequence = None
        self.sequence_start = None
        self.pending_activity = None
        self.activity_start = None

        self.stats.register_session(self)
Exemple #13
0
    def __init__(self, config_manager, **activity_args):
        check_arg(activity_args.get("duration"), (str, int, float))
        check_arg(activity_args.get("duration_2"), (str, int, float),
                  or_none=True)

        super().__init__(config_manager, **activity_args)
Exemple #14
0
 def get_attr(self, key_path, context=None):
     check_arg(key_path, str)
     check_arg(context, RunContext, or_none=True)
     if context is None:
         context = self.peek()
     return get_dict_attr(self.as_dict(), key_path)
Exemple #15
0
 def foo(name, amount, options=None):
     check_arg(name, str)
     check_arg(amount, (int, float), amount > 0)
     check_arg(options, dict, or_none=True)
     return name