From f4c17f516c3284a287571ba419a87659f92d8163 Mon Sep 17 00:00:00 2001
From: Georgy Moshkin <gmoshkin@picodata.io>
Date: Mon, 19 Aug 2024 17:06:04 +0300
Subject: [PATCH] test: do not move PluginReflection to conftest.py

---
 test/conftest.py             | 246 ---------------------------------
 test/int/test_http_server.py |  41 +++---
 test/int/test_plugin.py      | 258 +++++++++++++++++++++++++++++++++--
 3 files changed, 269 insertions(+), 276 deletions(-)

diff --git a/test/conftest.py b/test/conftest.py
index 3af04bb1bd..780308d8df 100644
--- a/test/conftest.py
+++ b/test/conftest.py
@@ -2253,252 +2253,6 @@ instance:
         return self.cluster.instances[0]
 
 
-_PLUGIN = "testplug"
-_PLUGIN_SERVICES = ["testservice_1", "testservice_2"]
-_PLUGIN_SMALL = "testplug_small"
-_PLUGIN_SMALL_SERVICES = ["testservice_1"]
-_PLUGIN_SMALL_SERVICES_SVC2 = ["testservice_2"]
-_PLUGIN_VERSION_1 = "0.1.0"
-_PLUGIN_VERSION_2 = "0.2.0"
-_DEFAULT_TIER = "default"
-
-
-@dataclass
-class PluginReflection:
-    """PluginReflection used to describe the expected state of the plugin"""
-
-    # plugin name
-    name: str
-    # plugin version
-    version: str
-    # list of plugin services
-    services: List[str]
-    # instances in cluster
-    instances: List[Instance]
-    # plugin topology
-    topology: Dict[Instance, List[str]] = field(default_factory=dict)
-    # if True - assert_synced checks that plugin are installed
-    installed: bool = False
-    # if True - assert_synced checks that plugin are enabled
-    enabled: bool = False
-    # plugin data [table -> tuples] map
-    data: Dict[str, Optional[List[Any]]] = field(default_factory=dict)
-
-    def __post__init__(self):
-        for i in self.instances:
-            self.topology[i] = []
-
-    @staticmethod
-    def default(*instances):
-        """Create reflection for default plugin with default topology"""
-        topology = {}
-        for i in instances:
-            topology[i] = _PLUGIN_SERVICES
-        return PluginReflection(
-            name=_PLUGIN,
-            version="0.1.0",
-            services=_PLUGIN_SERVICES,
-            instances=list(instances),
-        ).set_topology(topology)
-
-    def install(self, installed: bool):
-        self.installed = installed
-        return self
-
-    def enable(self, enabled: bool):
-        self.enabled = enabled
-        return self
-
-    def set_topology(self, topology: dict[Instance, list[str]]):
-        self.topology = topology
-        return self
-
-    def add_instance(self, i):
-        self.instances.append(i)
-        return self
-
-    def set_data(self, data: dict[str, Optional[list[Any]]]):
-        self.data = data
-        return self
-
-    def assert_synced(self):
-        """Assert that plugin reflection and plugin state in cluster are synchronized.
-        This means that system tables `_pico_plugin`, `_pico_service` and `_pico_service_route`
-        contain necessary plugin information."""
-        for i in self.instances:
-            plugins = i.eval(
-                "return box.space._pico_plugin:select({...})", self.name, self.version
-            )
-            if self.installed:
-                assert len(plugins) == 1
-                assert plugins[0][1] == self.enabled
-            else:
-                assert len(plugins) == 0
-
-            for service in self.services:
-                svcs = i.eval(
-                    "return box.space._pico_service:select({...})",
-                    [self.name, service, self.version],
-                )
-                if self.installed:
-                    assert len(svcs) == 1
-                else:
-                    assert len(svcs) == 0
-
-        for i in self.topology:
-            expected_services = []
-            for service in self.topology[i]:
-                expected_services.append(
-                    [i.instance_id, self.name, self.version, service, False]
-                )
-
-            for neighboring_i in self.topology:
-                routes = neighboring_i.eval(
-                    'return box.space._pico_service_route:pairs({...}, {iterator="EQ"}):totable()',
-                    i.instance_id,
-                    self.name,
-                    self.version,
-                )
-                assert routes == expected_services
-
-    def assert_data_synced(self):
-        for table in self.data:
-            data = []
-
-            for i in self.instances:
-                if self.data[table] is None:
-                    with pytest.raises(TarantoolError, match="attempt to index field"):
-                        i.eval(f"return box.space.{table}:select()")
-                else:
-                    data += i.eval(f"return box.space.{table}:select()")
-
-            if self.data[table] is not None:
-                assert data.sort() == self.data[table].sort()
-
-    @staticmethod
-    def assert_cb_called(service, callback, called_times, *instances):
-        for i in instances:
-            cb_calls_number = i.eval(
-                f"if _G['plugin_state'] == nil then _G['plugin_state'] = {{}} end "
-                f"if _G['plugin_state']['{service}'] == nil then _G['plugin_state']['{service}']"
-                f" = {{}} end "
-                f"if _G['plugin_state']['{service}']['{callback}'] == nil then _G['plugin_state']"
-                f"['{service}']['{callback}'] = 0 end "
-                f"return _G['plugin_state']['{service}']['{callback}']"
-            )
-            assert cb_calls_number == called_times
-
-    @staticmethod
-    def assert_persisted_data_exists(data, *instances):
-        for i in instances:
-            data_exists = i.eval(
-                f"return box.space.persisted_data:get({{'{data}'}}) ~= box.NULL"
-            )
-            assert data_exists
-
-    @staticmethod
-    def clear_persisted_data(data, *instances):
-        for i in instances:
-            i.eval("return box.space.persisted_data:drop()")
-
-    @staticmethod
-    def inject_error(service, error, value, instance):
-        instance.eval("if _G['err_inj'] == nil then _G['err_inj'] = {} end")
-        instance.eval(
-            f"if _G['err_inj']['{service}'] == nil then _G['err_inj']['{service}'] "
-            "= {{}} end"
-        )
-        instance.eval(f"_G['err_inj']['{service}']['{error}'] = ...", (value,))
-
-    @staticmethod
-    def remove_error(service, error, instance):
-        instance.eval("if _G['err_inj'] == nil then _G['err_inj'] = {} end")
-        instance.eval(
-            f"if _G['err_inj']['{service}'] == nil then _G['err_inj']['{service}'] "
-            "= {{}} end"
-        )
-        instance.eval(f"_G['err_inj']['{service}']['{error}'] = nil")
-
-    @staticmethod
-    def assert_last_seen_ctx(service, expected_ctx, *instances):
-        for i in instances:
-            ctx = i.eval(f"return _G['plugin_state']['{service}']['last_seen_ctx']")
-            assert ctx == expected_ctx
-
-    def get_config(self, service, instance):
-        config = dict()
-        records = instance.eval(
-            "return box.space._pico_plugin_config:select({...})",
-            [self.name, self.version, service],
-        )
-        for record in records:
-            config[record[3]] = record[4]
-        return config
-
-    @staticmethod
-    def get_seen_config(service, instance):
-        return instance.eval(
-            f"return _G['plugin_state']['{service}']['current_config']"
-        )
-
-    def assert_config(self, service, expected_cfg, *instances):
-        for i in instances:
-            cfg_space = self.get_config(service, i)
-            assert cfg_space == expected_cfg
-            cfg_seen = self.get_seen_config(service, i)
-            assert cfg_seen == expected_cfg
-
-    def assert_route_poisoned(self, poison_instance_id, service, poisoned=True):
-        for i in self.instances:
-            route_poisoned = i.eval(
-                "return box.space._pico_service_route:get({...}).poison",
-                poison_instance_id,
-                self.name,
-                self.version,
-                service,
-            )
-            assert route_poisoned == poisoned
-
-    @staticmethod
-    def assert_data_eq(instance, key, expected):
-        val = instance.eval(f"return _G['plugin_state']['data']['{key}']")
-        assert val == expected
-
-    @staticmethod
-    def assert_int_data_le(instance, key, expected):
-        val = instance.eval(f"return _G['plugin_state']['data']['{key}']")
-        assert int(val) <= expected
-
-
-def install_and_enable_plugin(
-    instance,
-    plugin,
-    services,
-    version="0.1.0",
-    migrate=False,
-    timeout=3,
-    default_config=None,
-    if_not_exist=False,
-):
-    instance.call(
-        "pico.install_plugin",
-        plugin,
-        version,
-        {"migrate": migrate, "if_not_exist": if_not_exist},
-        timeout=timeout,
-    )
-    for s in services:
-        if default_config is not None:
-            for key in default_config:
-                instance.eval(
-                    f"box.space._pico_plugin_config:replace"
-                    f"({{'{plugin}', '0.1.0', '{s}', '{key}', ...}})",
-                    default_config[key],
-                )
-        instance.call("pico.service_append_tier", plugin, version, s, _DEFAULT_TIER)
-    instance.call("pico.enable_plugin", plugin, version, timeout=timeout)
-
-
 @pytest.fixture
 def postgres(cluster: Cluster):
     return Postgres(cluster).install()
diff --git a/test/int/test_http_server.py b/test/int/test_http_server.py
index 856b0e7463..38c7505335 100644
--- a/test/int/test_http_server.py
+++ b/test/int/test_http_server.py
@@ -1,11 +1,6 @@
 from conftest import (
     Cluster,
     Instance,
-    _PLUGIN,
-    _PLUGIN_SERVICES,
-    _PLUGIN_SMALL,
-    _PLUGIN_SMALL_SERVICES,
-    _PLUGIN_VERSION_1,
 )
 from urllib.request import urlopen
 import pytest
@@ -106,31 +101,37 @@ def test_webui_with_plugin(cluster: Cluster):
     """
     cluster.set_config_file(yaml=cluster_cfg)
 
+    plugin_1 = "testplug"
+    plugin_1_services = ["testservice_1", "testservice_2"]
+    plugin_2 = "testplug_small"
+    plugin_2_service = "testservice_1"
+    version_1 = "0.1.0"
+
     i1 = cluster.add_instance(wait_online=True, tier="red", enable_http=True)
     i2 = cluster.add_instance(wait_online=True, tier="blue")
     i3 = cluster.add_instance(wait_online=True, tier="green")
 
-    i1.call("pico.install_plugin", _PLUGIN, _PLUGIN_VERSION_1)
-    i1.call("pico.install_plugin", _PLUGIN_SMALL, _PLUGIN_VERSION_1)
+    i1.call("pico.install_plugin", plugin_1, version_1)
+    i1.call("pico.install_plugin", plugin_2, version_1)
     i1.call(
         "pico.service_append_tier",
-        _PLUGIN,
-        _PLUGIN_VERSION_1,
-        _PLUGIN_SERVICES[0],
+        plugin_1,
+        version_1,
+        plugin_1_services[0],
         "red",
     )
     i1.call(
         "pico.service_append_tier",
-        _PLUGIN,
-        _PLUGIN_VERSION_1,
-        _PLUGIN_SERVICES[1],
+        plugin_1,
+        version_1,
+        plugin_1_services[1],
         "blue",
     )
     i1.call(
         "pico.service_append_tier",
-        _PLUGIN_SMALL,
-        _PLUGIN_VERSION_1,
-        _PLUGIN_SMALL_SERVICES[0],
+        plugin_2,
+        version_1,
+        plugin_2_service,
         "blue",
     )
 
@@ -207,13 +208,13 @@ def test_webui_with_plugin(cluster: Cluster):
     tier_red = {
         **tier_template,
         "name": "red",
-        "services": [_PLUGIN_SERVICES[0]],
+        "services": [plugin_1_services[0]],
         "replicasets": [r1],
     }
     tier_blue = {
         **tier_template,
         "name": "blue",
-        "services": [_PLUGIN_SERVICES[1], _PLUGIN_SMALL_SERVICES[0]],
+        "services": [plugin_1_services[1], plugin_2_service],
         "replicasets": [r2],
     }
     tier_green = {**tier_template, "name": "green", "services": [], "replicasets": [r3]}
@@ -236,8 +237,8 @@ def test_webui_with_plugin(cluster: Cluster):
             "currentInstaceVersion": instance_version,
             "memory": {"usable": 201326592, "used": 100663296},
             "plugins": [
-                _PLUGIN + " " + _PLUGIN_VERSION_1,
-                _PLUGIN_SMALL + " " + _PLUGIN_VERSION_1,
+                plugin_1 + " " + version_1,
+                plugin_2 + " " + version_1,
             ],
         }
 
diff --git a/test/int/test_plugin.py b/test/int/test_plugin.py
index 92eb7b6aa9..58eb0b8a09 100644
--- a/test/int/test_plugin.py
+++ b/test/int/test_plugin.py
@@ -1,5 +1,6 @@
+from dataclasses import dataclass, field
 import time
-from typing import Any
+from typing import Any, Dict, List, Optional
 import pytest
 import uuid
 import msgpack  # type: ignore
@@ -12,15 +13,6 @@ from conftest import (
     Instance,
     TarantoolError,
     log_crawler,
-    PluginReflection,
-    _PLUGIN,
-    _PLUGIN_SERVICES,
-    _PLUGIN_SMALL,
-    _PLUGIN_SMALL_SERVICES,
-    _PLUGIN_VERSION_1,
-    _PLUGIN_VERSION_2,
-    _DEFAULT_TIER,
-    install_and_enable_plugin,
 )
 from decimal import Decimal
 import requests  # type: ignore
@@ -33,6 +25,14 @@ _DEFAULT_CFG = {"foo": True, "bar": 101, "baz": ["one", "two", "three"]}
 _NEW_CFG = {"foo": True, "bar": 102, "baz": ["a", "b"]}
 _NEW_CFG_2 = {"foo": False, "bar": 102, "baz": ["a", "b"]}
 
+_PLUGIN = "testplug"
+_PLUGIN_SERVICES = ["testservice_1", "testservice_2"]
+_PLUGIN_SMALL = "testplug_small"
+_PLUGIN_SMALL_SERVICES = ["testservice_1"]
+_PLUGIN_SMALL_SERVICES_SVC2 = ["testservice_2"]
+_PLUGIN_VERSION_1 = "0.1.0"
+_PLUGIN_VERSION_2 = "0.2.0"
+_DEFAULT_TIER = "default"
 _PLUGIN_WITH_MIGRATION = "testplug_w_migration"
 _PLUGIN_WITH_MIGRATION_SERVICES = ["testservice_2"]
 _PLUGIN_W_SDK = "testplug_sdk"
@@ -44,6 +44,244 @@ PLUGIN_NAME = 2
 SERVICE_NAME = 3
 PLUGIN_VERSION = 4
 
+# ---------------------------------- Test helper classes {-----------------------------------------
+
+
+@dataclass
+class PluginReflection:
+    """PluginReflection used to describe the expected state of the plugin"""
+
+    # plugin name
+    name: str
+    # plugin version
+    version: str
+    # list of plugin services
+    services: List[str]
+    # instances in cluster
+    instances: List[Instance]
+    # plugin topology
+    topology: Dict[Instance, List[str]] = field(default_factory=dict)
+    # if True - assert_synced checks that plugin are installed
+    installed: bool = False
+    # if True - assert_synced checks that plugin are enabled
+    enabled: bool = False
+    # plugin data [table -> tuples] map
+    data: Dict[str, Optional[List[Any]]] = field(default_factory=dict)
+
+    def __post__init__(self):
+        for i in self.instances:
+            self.topology[i] = []
+
+    @staticmethod
+    def default(*instances):
+        """Create reflection for default plugin with default topology"""
+        topology = {}
+        for i in instances:
+            topology[i] = _PLUGIN_SERVICES
+        return PluginReflection(
+            name=_PLUGIN,
+            version="0.1.0",
+            services=_PLUGIN_SERVICES,
+            instances=list(instances),
+        ).set_topology(topology)
+
+    def install(self, installed: bool):
+        self.installed = installed
+        return self
+
+    def enable(self, enabled: bool):
+        self.enabled = enabled
+        return self
+
+    def set_topology(self, topology: dict[Instance, list[str]]):
+        self.topology = topology
+        return self
+
+    def add_instance(self, i):
+        self.instances.append(i)
+        return self
+
+    def set_data(self, data: dict[str, Optional[list[Any]]]):
+        self.data = data
+        return self
+
+    def assert_synced(self):
+        """Assert that plugin reflection and plugin state in cluster are synchronized.
+        This means that system tables `_pico_plugin`, `_pico_service` and `_pico_service_route`
+        contain necessary plugin information."""
+        for i in self.instances:
+            plugins = i.eval(
+                "return box.space._pico_plugin:select({...})", self.name, self.version
+            )
+            if self.installed:
+                assert len(plugins) == 1
+                assert plugins[0][1] == self.enabled
+            else:
+                assert len(plugins) == 0
+
+            for service in self.services:
+                svcs = i.eval(
+                    "return box.space._pico_service:select({...})",
+                    [self.name, service, self.version],
+                )
+                if self.installed:
+                    assert len(svcs) == 1
+                else:
+                    assert len(svcs) == 0
+
+        for i in self.topology:
+            expected_services = []
+            for service in self.topology[i]:
+                expected_services.append(
+                    [i.instance_id, self.name, self.version, service, False]
+                )
+
+            for neighboring_i in self.topology:
+                routes = neighboring_i.eval(
+                    'return box.space._pico_service_route:pairs({...}, {iterator="EQ"}):totable()',
+                    i.instance_id,
+                    self.name,
+                    self.version,
+                )
+                assert routes == expected_services
+
+    def assert_data_synced(self):
+        for table in self.data:
+            data = []
+
+            for i in self.instances:
+                if self.data[table] is None:
+                    with pytest.raises(TarantoolError, match="attempt to index field"):
+                        i.eval(f"return box.space.{table}:select()")
+                else:
+                    data += i.eval(f"return box.space.{table}:select()")
+
+            if self.data[table] is not None:
+                assert data.sort() == self.data[table].sort()
+
+    @staticmethod
+    def assert_cb_called(service, callback, called_times, *instances):
+        for i in instances:
+            cb_calls_number = i.eval(
+                f"if _G['plugin_state'] == nil then _G['plugin_state'] = {{}} end "
+                f"if _G['plugin_state']['{service}'] == nil then _G['plugin_state']['{service}']"
+                f" = {{}} end "
+                f"if _G['plugin_state']['{service}']['{callback}'] == nil then _G['plugin_state']"
+                f"['{service}']['{callback}'] = 0 end "
+                f"return _G['plugin_state']['{service}']['{callback}']"
+            )
+            assert cb_calls_number == called_times
+
+    @staticmethod
+    def assert_persisted_data_exists(data, *instances):
+        for i in instances:
+            data_exists = i.eval(
+                f"return box.space.persisted_data:get({{'{data}'}}) ~= box.NULL"
+            )
+            assert data_exists
+
+    @staticmethod
+    def clear_persisted_data(data, *instances):
+        for i in instances:
+            i.eval("return box.space.persisted_data:drop()")
+
+    @staticmethod
+    def inject_error(service, error, value, instance):
+        instance.eval("if _G['err_inj'] == nil then _G['err_inj'] = {} end")
+        instance.eval(
+            f"if _G['err_inj']['{service}'] == nil then _G['err_inj']['{service}'] "
+            "= {{}} end"
+        )
+        instance.eval(f"_G['err_inj']['{service}']['{error}'] = ...", (value,))
+
+    @staticmethod
+    def remove_error(service, error, instance):
+        instance.eval("if _G['err_inj'] == nil then _G['err_inj'] = {} end")
+        instance.eval(
+            f"if _G['err_inj']['{service}'] == nil then _G['err_inj']['{service}'] "
+            "= {{}} end"
+        )
+        instance.eval(f"_G['err_inj']['{service}']['{error}'] = nil")
+
+    @staticmethod
+    def assert_last_seen_ctx(service, expected_ctx, *instances):
+        for i in instances:
+            ctx = i.eval(f"return _G['plugin_state']['{service}']['last_seen_ctx']")
+            assert ctx == expected_ctx
+
+    def get_config(self, service, instance):
+        config = dict()
+        records = instance.eval(
+            "return box.space._pico_plugin_config:select({...})",
+            [self.name, self.version, service],
+        )
+        for record in records:
+            config[record[3]] = record[4]
+        return config
+
+    @staticmethod
+    def get_seen_config(service, instance):
+        return instance.eval(
+            f"return _G['plugin_state']['{service}']['current_config']"
+        )
+
+    def assert_config(self, service, expected_cfg, *instances):
+        for i in instances:
+            cfg_space = self.get_config(service, i)
+            assert cfg_space == expected_cfg
+            cfg_seen = self.get_seen_config(service, i)
+            assert cfg_seen == expected_cfg
+
+    def assert_route_poisoned(self, poison_instance_id, service, poisoned=True):
+        for i in self.instances:
+            route_poisoned = i.eval(
+                "return box.space._pico_service_route:get({...}).poison",
+                poison_instance_id,
+                self.name,
+                self.version,
+                service,
+            )
+            assert route_poisoned == poisoned
+
+    @staticmethod
+    def assert_data_eq(instance, key, expected):
+        val = instance.eval(f"return _G['plugin_state']['data']['{key}']")
+        assert val == expected
+
+    @staticmethod
+    def assert_int_data_le(instance, key, expected):
+        val = instance.eval(f"return _G['plugin_state']['data']['{key}']")
+        assert int(val) <= expected
+
+
+def install_and_enable_plugin(
+    instance,
+    plugin,
+    services,
+    version="0.1.0",
+    migrate=False,
+    timeout=3,
+    default_config=None,
+    if_not_exist=False,
+):
+    instance.call(
+        "pico.install_plugin",
+        plugin,
+        version,
+        {"migrate": migrate, "if_not_exist": if_not_exist},
+        timeout=timeout,
+    )
+    for s in services:
+        if default_config is not None:
+            for key in default_config:
+                instance.eval(
+                    f"box.space._pico_plugin_config:replace"
+                    f"({{'{plugin}', '0.1.0', '{s}', '{key}', ...}})",
+                    default_config[key],
+                )
+        instance.call("pico.service_append_tier", plugin, version, s, _DEFAULT_TIER)
+    instance.call("pico.enable_plugin", plugin, version, timeout=timeout)
+
 
 def test_invalid_manifest_plugin(cluster: Cluster):
     i1, i2 = cluster.deploy(instance_count=2)
-- 
GitLab