예제 #1
0
        def test_on_dir_raises_error_with_all_incorrect_hars(
                self, tmp_path: Path):
            for i in range(2):
                tmp_path.joinpath(f"{i}.nothar").write_text("not JSON!")

            with pytest.raises(SkippableScenarioError):
                Scenario.from_path(tmp_path)
예제 #2
0
def test_generates_passed_global_code_blocks():
    def mock(name, blocks=None):
        m = MagicMock(spec=Scenario,
                      children=[],
                      global_code_blocks=blocks or {})
        # https://docs.python.org/3/library/unittest.mock.html#mock-names-and-the-name-attribute
        m.name = name
        return m

    sg1 = Scenario(
        "sg1",
        children=[
            mock("a", blocks={"b1": ["ab"]}),
            mock("b", blocks={"b2": ["cd"]})
        ],
        origin=None,
    )
    sg2 = Scenario("sg2", children=[mock("c")], origin=None)
    sg3 = Scenario(
        "sg3",
        children=[mock("d", blocks={
            "b3": ["yz"],
            "b2": ["yyy", "zzz"]
        })],
        origin=None,
    )

    code = locustfile([sg1, sg2, sg3])
    assert code.endswith("\n# b1\nab\n# b2\nyyy\nzzz\n# b3\nyz"
                         ), "the latter b2 block should override the former"
예제 #3
0
    def test_it_renders_a_locustfile_template(self):
        a_name = "some_task"
        a_request = MagicMock(spec=Request)
        a_request.method = HttpMethod.GET
        a_request.url = MagicMock(spec=SplitResult)
        a_request.url.scheme = "some_scheme"
        a_request.url.hostname = "some_hostname"
        a_request.url.path = "some_path"
        a_request.url.geturl()
        a_request.url.geturl.return_value = "some_url"
        a_request.headers = {"a": "b"}
        a_request.name = None
        task = Task(a_name, a_request)
        scenario = Scenario(name="SomeScenario", children=[task], origin=None)
        scenario_group = Scenario(name="ScenarioGroup",
                                  children=[scenario],
                                  weight=2,
                                  origin=None)
        script = locustfile([scenario_group])
        expected = string.Template(f"""
# File automatically generated by Transformer v{__version__}:
# https://github.com/zalando-incubator/Transformer
import re
from distutils.version import LooseVersion
from locust import __version__
LOCUST_MAJOR_VERSION = LooseVersion(__version__).version[0]
if LOCUST_MAJOR_VERSION >= 1:
    from locust import HttpUser
    from locust import SequentialTaskSet
    from locust import TaskSet
    from locust import task
    HttpLocust = HttpUser
    TaskSequence = SequentialTaskSet
    def seq_task(_):
        return task
else:
    from locust import HttpLocust
    from locust import TaskSequence
    from locust import TaskSet
    from locust import seq_task
    from locust import task
class ScenarioGroup(TaskSet):
    @task(1)
    class SomeScenario(TaskSequence):
        @seq_task(1)
        def some_task(self):
            response = self.client.get(url='some_url', name='some_url', timeout=$TIMEOUT, allow_redirects=False, headers={{'a': 'b'}})
class LocustForScenarioGroup(HttpLocust):
    if LOCUST_MAJOR_VERSION >= 1:
        tasks = [ScenarioGroup]
    else:
        task_set = ScenarioGroup
    weight = 2
    min_wait = 0
    max_wait = 10
""").safe_substitute({"TIMEOUT": TIMEOUT})
        assert expected.strip() == script.strip()
예제 #4
0
 def test_group_records_global_code_blocks_from_scenarios(self):
     t1_blocks = {"t1-1": ["abc"], "t1-2": ["def"]}
     t1 = Task("t1", request=MagicMock(), global_code_blocks=t1_blocks)
     t2 = Task("t2", request=MagicMock())
     t3_blocks = {"t3-1": ("xyz",)}
     t3 = Task("t3", request=MagicMock(), global_code_blocks=t3_blocks)
     s1 = Scenario("s1", [t1, t2], origin=None)
     s2 = Scenario("s2", [t3], origin=None)
     sg = Scenario("sg", [s1, s2], origin=None)
     assert sg.global_code_blocks == {**t1_blocks, **t3_blocks}
예제 #5
0
        def test_on_dir_with_dangling_weights_raises_error(
                self, tmp_path: Path, dummy_har_string, caplog):
            (tmp_path / "ok.har").write_text(dummy_har_string)
            (tmp_path / "fail.weight").write_text("7")
            caplog.set_level(logging.INFO)

            with pytest.raises(SkippableScenarioError):
                Scenario.from_path(tmp_path)

            assert "weight file" in caplog.text
            assert any(r.levelname == "ERROR"
                       for r in caplog.records), "at least one ERROR logged"
예제 #6
0
        def test_raises_error_for_colliding_scenario_names_from_har_files(
                self, tmp_path: Path, dummy_har_string, caplog):
            (tmp_path / "good.har").write_text(dummy_har_string)
            (tmp_path / "bad.har").write_text(dummy_har_string)
            (tmp_path / "bad.json").write_text(dummy_har_string)

            caplog.set_level(logging.ERROR)

            with pytest.raises(SkippableScenarioError):
                Scenario.from_path(tmp_path)

            assert "colliding names" in caplog.text
            assert "bad.har" in caplog.text
            assert "bad.json" in caplog.text
예제 #7
0
        def test_uses_full_path_for_scenario_name(self, tmp_path: Path,
                                                  dummy_har_string):
            har_basename = "e3ee4a1ef0817cde0a0a78c056e7cb35"
            har_path = tmp_path / har_basename
            har_path.write_text(dummy_har_string)

            scenario = Scenario.from_path(har_path)

            words_in_scenario_name = {
                m.group()
                for m in re.finditer(r"[A-Za-z0-9]+", scenario.name)
            }
            assert har_basename in words_in_scenario_name

            words_in_parent_path = {
                m.group()
                for m in re.finditer(r"[A-Za-z0-9]+", str(tmp_path))
            }
            words_in_scenario_name_not_from_har_basename = words_in_scenario_name - {
                har_basename
            }
            assert (
                words_in_parent_path <=
                words_in_scenario_name_not_from_har_basename
            ), "all components of the parent path must be in the scenario name"
예제 #8
0
        def test_with_weight_file_has_corresponding_weight(self, tmp_path: Path):
            weight_path = tmp_path / "test.weight"
            weight_path.write_text("74")

            har_path = tmp_path / "test.har"
            har_path.write_text(DUMMY_HAR_STRING)
            assert Scenario.from_path(har_path).weight == 74
예제 #9
0
        def test_with_invalid_weight_raises_error_and_never_skips(
                self, tmp_path: Path, dummy_har_string, weight):
            legit_har_path = tmp_path / "legit.har"
            legit_har_path.write_text(dummy_har_string)

            bad_weight_path = tmp_path / "test.weight"
            bad_weight_path.write_text(str(weight))

            bad_weight_har_path = tmp_path / "test.har"
            bad_weight_har_path.write_text(dummy_har_string)

            with pytest.raises(WeightValueError):
                # If from_path was skipping the bad scenario/weight pair, it
                # would not raise because there is another valid scenario,
                # legit.har.
                Scenario.from_path(tmp_path)
예제 #10
0
        def test_uses_full_path_for_parents_and_basename_for_children(
            self, tmp_path: Path
        ):
            root_basename = "615010a656a5bb29d1898f163619611f"
            root = tmp_path / root_basename
            root.mkdir()
            for i in range(2):
                (root / f"s{i}.har").write_text(DUMMY_HAR_STRING)

            root_scenario = Scenario.from_path(root)

            words_in_root_scenario_name = {
                m.group() for m in re.finditer(r"[A-Za-z0-9]+", root_scenario.name)
            }
            words_in_root_path = {
                m.group() for m in re.finditer(r"[A-Za-z0-9]+", str(root))
            }
            assert (
                words_in_root_path <= words_in_root_scenario_name
            ), "parent scenario's name must come from full path"

            assert len(root_scenario.children) == 2
            child_scenario_names = {c.name for c in root_scenario.children}
            assert child_scenario_names == {
                "s0",
                "s1",
            }, "child scenarios have short names"
예제 #11
0
        def test_with_weight_file_has_corresponding_weight(
                self, tmp_path: Path, dummy_har_string):
            weight_path = tmp_path / "test.weight"
            weight_path.write_text("74")

            har_path = tmp_path / "test.har"
            har_path.write_text(dummy_har_string)
            assert Scenario.from_path(har_path).weight == 74
예제 #12
0
 def test_records_global_code_blocks_from_tasks(self):
     t1_blocks = {"t1-1": ["abc"], "t1-2": ["def"]}
     t1 = Task("t1", request=MagicMock(), global_code_blocks=t1_blocks)
     t2 = Task("t2", request=MagicMock())
     t3_blocks = {"t3-1": ("xyz",)}
     t3 = Task("t3", request=MagicMock(), global_code_blocks=t3_blocks)
     scenario = Scenario("scenario", [t1, t2, t3], origin=None)
     assert scenario.global_code_blocks == {**t1_blocks, **t3_blocks}
예제 #13
0
        def test_raises_error_for_colliding_scenario_names_from_directory_and_file(
                self, tmp_path: Path, dummy_har_string, caplog):
            directory = tmp_path / "x"
            directory.mkdir()
            # directory needs to contain a HAR file, otherwise Transformer will
            # not consider it a scenario.
            (directory / "a.har").write_text(dummy_har_string)

            (tmp_path / "x.har").write_text(dummy_har_string)

            caplog.set_level(logging.ERROR)

            with pytest.raises(SkippableScenarioError):
                Scenario.from_path(tmp_path)

            assert "colliding names" in caplog.text
            assert re.search(r"\bx\b", caplog.text)
            assert re.search(r"\bx.har\b", caplog.text)
예제 #14
0
        def test_on_dir_ignores_some_incorrect_hars(self, tmp_path: Path):
            not_har_path = tmp_path / "not.har"
            not_har_path.write_text("not JSON!")
            har_path = tmp_path / "good.har"
            har_path.write_text(DUMMY_HAR_STRING)

            scenario = Scenario.from_path(tmp_path)
            assert len(scenario.children) == 1
            assert scenario.children[0].origin == har_path
예제 #15
0
 def test_group_records_global_code_blocks_uniquely(self):
     common_blocks = {"x": ["a", "b"]}
     t1 = Task(
         "t1",
         request=MagicMock(),
         global_code_blocks={**common_blocks, "t1b": ["uvw"]},
     )
     t2 = Task(
         "t2",
         request=MagicMock(),
         global_code_blocks={**common_blocks, "t2b": ["xyz"]},
     )
     s1 = Scenario("s1", [t1], origin=None)
     s2 = Scenario("s2", [t2], origin=None)
     sg = Scenario("sg", [s1, s2], origin=None)
     assert sg.global_code_blocks == {
         **common_blocks,
         "t1b": ["uvw"],
         "t2b": ["xyz"],
     }
예제 #16
0
    def test_creation_from_scenario_directory_with_weight_file(self, tmp_path: Path):
        root_path = tmp_path / "some-path"
        root_path.mkdir()
        expected_weight = 7
        root_path.with_suffix(".weight").write_text(str(expected_weight))
        nb_har_files = 2
        for i in range(nb_har_files):
            root_path.joinpath(f"{i}.har").write_text(DUMMY_HAR_STRING)

        result = Scenario.from_path(root_path)
        assert len(result.children) == nb_har_files
        assert result.weight == expected_weight
예제 #17
0
    def test_it_renders_a_locustfile_template(self):
        a_name = "some_task"
        a_request = MagicMock()
        a_request.method = HttpMethod.GET
        a_request.url.scheme = "some_scheme"
        a_request.url.hostname = "some_hostname"
        a_request.url.path = "some_path"
        a_request.url.geturl()
        a_request.url.geturl.return_value = "some_url"
        task = Task(a_name, a_request)
        scenario = Scenario(name="SomeScenario", children=[task], origin=None)
        scenario_group = Scenario(name="ScenarioGroup",
                                  children=[scenario],
                                  weight=2,
                                  origin=None)
        script = locustfile([scenario_group])
        expected = string.Template("""
# File automatically generated by Transformer:
# https://github.bus.zalan.do/TIP/transformer
import re
from locust import HttpLocust
from locust import TaskSequence
from locust import TaskSet
from locust import seq_task
from locust import task
class ScenarioGroup(TaskSet):
    @task(1)
    class SomeScenario(TaskSequence):
        @seq_task(1)
        def some_task(self):
            response = self.client.get(url='some_url', name='some_url', headers={}, timeout=$TIMEOUT, allow_redirects=False)
class LocustForScenarioGroup(HttpLocust):
    task_set = ScenarioGroup
    weight = 2
    min_wait = 0
    max_wait = 10
""").safe_substitute({"TIMEOUT": TIMEOUT})
        assert expected.strip() == script.strip()
예제 #18
0
def test_generates_passed_global_code_blocks():
    sg1 = Scenario(
        "sg1",
        children=[
            MagicMock(spec_set=Scenario,
                      children=[],
                      global_code_blocks={"b1": ["ab"]}),
            MagicMock(spec_set=Scenario,
                      children=[],
                      global_code_blocks={"b2": ["cd"]}),
        ],
        origin=None,
    )
    sg2 = Scenario(
        "sg2",
        children=[
            MagicMock(spec_set=Scenario, children=[], global_code_blocks={})
        ],
        origin=None,
    )
    sg3 = Scenario(
        "sg3",
        children=[
            MagicMock(
                spec_set=Scenario,
                children=[],
                global_code_blocks={
                    "b3": ["yz"],
                    "b2": ["yyy", "zzz"]
                },
            )
        ],
        origin=None,
    )

    code = locustfile([sg1, sg2, sg3])
    assert code.endswith("\n# b1\nab\n# b2\nyyy\nzzz\n# b3\nyz"
                         ), "the latter b2 block should override the former"
예제 #19
0
        def test_with_many_weight_files_selects_weight_based_on_name(
                self, tmp_path: Path, dummy_har_string):
            expected_weight_path = tmp_path / "test.weight"
            expected_weight_path.write_text("7")

            first_wrong_weight_path = tmp_path / "a.weight"
            first_wrong_weight_path.write_text("2")

            second_wrong_weight_path = tmp_path / "1.weight"
            second_wrong_weight_path.write_text("4")

            har_path = tmp_path / "test.har"
            har_path.write_text(dummy_har_string)

            assert Scenario.from_path(har_path).weight == 7
예제 #20
0
def transform(
    scenarios_path: Union[str, Path],
    plugins: Sequence[Plugin] = (),
    with_default_plugins: bool = True,
) -> str:
    """
    This function is deprecated and will be removed in a future version.
    Do not rely on it.
    Reason: It only accepts one scenario path at a time, and requires plugins
    to be already resolved (and therefore that users use
    transformer.plugins.resolve, which is kind of low-level). Both dumps & dump
    lift these constraints and have a more familiar naming
    (see json.dump/s, etc.).
    Deprecated since: v1.0.2.
    """
    warnings.warn(DeprecationWarning("transform: use dump or dumps instead"))
    if with_default_plugins:
        plugins = (*DEFAULT_PLUGINS, *plugins)
    return locustfile([Scenario.from_path(Path(scenarios_path), plugins)])
예제 #21
0
def _dump_as_lines(
    scenario_paths: Iterable[LaxPath],
    plugins: Sequence[PluginName],
    with_default_plugins: bool,
) -> Iterator[str]:
    plugins = [p for name in plugins for p in plug.resolve(name)]
    if with_default_plugins:
        plugins = (*DEFAULT_PLUGINS, *plugins)

    plugins_for = plug.group_by_contract(plugins)

    scenarios = [
        Scenario.from_path(path, plugins_for[Contract.OnTask],
                           plugins_for[Contract.OnTaskSequence]).apply_plugins(
                               plugins_for[Contract.OnScenario])
        for path in scenario_paths
    ]

    yield from locustfile_lines(scenarios,
                                plugins_for[Contract.OnPythonProgram])
예제 #22
0
def test_locust_taskset_raises_on_malformed_scenario():
    bad_child = cast(Scenario, 7)
    bad_scenario = Scenario(name="x", children=[bad_child], origin=None)
    with pytest.raises(TypeError, match=r"unexpected type .*\bchildren"):
        locust_taskset(bad_scenario)
예제 #23
0
        def test_on_har_raises_error_with_incorrect_har(self, tmp_path: Path):
            not_har_path = tmp_path / "not.har"
            not_har_path.write_text("not JSON!")

            with pytest.raises(SkippableScenarioError):
                Scenario.from_path(not_har_path)
예제 #24
0
 def test_names_are_unique(*_, paths: List[Path]):
     scenario_names = [Scenario.from_path(path).name for path in paths]
     assert sorted(set(scenario_names)) == sorted(scenario_names)
     assert len(paths) == len(scenario_names)
예제 #25
0
 def test_without_weight_file_has_weight_1(self, tmp_path: Path,
                                           dummy_har_string):
     har_path = tmp_path / "test.har"
     har_path.write_text(dummy_har_string)
     assert Scenario.from_path(har_path).weight == 1
예제 #26
0
 def test_without_weight_file_has_weight_1(self, tmp_path: Path):
     har_path = tmp_path / "test.har"
     har_path.write_text(DUMMY_HAR_STRING)
     assert Scenario.from_path(har_path).weight == 1