mirror of
https://pagure.io/fm-orchestrator.git
synced 2026-02-03 05:03:43 +08:00
Merge #1650 Add test_memory
This commit is contained in:
@@ -9,6 +9,7 @@ import os
|
||||
import re
|
||||
import six
|
||||
import time
|
||||
import yaml
|
||||
from traceback import extract_stack
|
||||
|
||||
import koji
|
||||
@@ -55,6 +56,13 @@ def read_staged_data(yaml_name):
|
||||
return to_text_type(mmd.read())
|
||||
|
||||
|
||||
def read_staged_data_as_yaml(yaml_name):
|
||||
filename = staged_data_filename(
|
||||
yaml_name if '.' in yaml_name else "{}.yaml".format(yaml_name))
|
||||
with open(filename, "r") as f:
|
||||
return yaml.safe_load(f)
|
||||
|
||||
|
||||
def patch_config():
|
||||
# add test builders for all resolvers
|
||||
with_test_builders = dict()
|
||||
|
||||
0
tests/test_memory/__init__.py
Normal file
0
tests/test_memory/__init__.py
Normal file
32
tests/test_memory/mbs_configuration.py
Normal file
32
tests/test_memory/mbs_configuration.py
Normal file
@@ -0,0 +1,32 @@
|
||||
import os
|
||||
|
||||
|
||||
class TestConfiguration:
|
||||
# TEST CONFIGURATION ('borrowed' from module_build_service.common.conf)
|
||||
SECRET_KEY = os.urandom(16)
|
||||
SQLALCHEMY_TRACK_MODIFICATIONS = True
|
||||
HOST = "0.0.0.0"
|
||||
PORT = 5000
|
||||
|
||||
LOG_LEVEL = "debug"
|
||||
SQLALCHEMY_DATABASE_URI = os.environ.get("DATABASE_URI", "sqlite:///:memory:")
|
||||
DEBUG = True
|
||||
MESSAGING = "in_memory"
|
||||
PDC_URL = "https://pdc.fedoraproject.org/rest_api/v1"
|
||||
NET_TIMEOUT = 10
|
||||
NET_RETRY_INTERVAL = 1
|
||||
SCM_NET_TIMEOUT = 0.1
|
||||
SCM_NET_RETRY_INTERVAL = 0.1
|
||||
KOJI_CONFIG = "./conf/koji.conf"
|
||||
KOJI_PROFILE = "staging"
|
||||
KOJI_REPOSITORY_URL = "https://kojipkgs.stg.fedoraproject.org/repos"
|
||||
SCMURLS = ["https://src.stg.fedoraproject.org/modules/"]
|
||||
ALLOWED_GROUPS_TO_IMPORT_MODULE = {"mbs-import-module"}
|
||||
GREENWAVE_URL = "https://greenwave.example.local/api/v1.0/"
|
||||
GREENWAVE_DECISION_CONTEXT = "test_dec_context"
|
||||
GREENWAVE_SUBJECT_TYPE = "some-module"
|
||||
STREAM_SUFFIXES = {r"^el\d+\.\d+\.\d+\.z$": 0.1}
|
||||
CELERY_TASK_ALWAYS_EAGER = True
|
||||
|
||||
NO_AUTH = True
|
||||
YAML_SUBMIT_ALLOWED = True
|
||||
70
tests/test_memory/mbs_debug.py
Normal file
70
tests/test_memory/mbs_debug.py
Normal file
@@ -0,0 +1,70 @@
|
||||
from __future__ import absolute_import, print_function
|
||||
|
||||
import koji
|
||||
import mock
|
||||
import signal
|
||||
import threading
|
||||
import types
|
||||
|
||||
from module_build_service import app
|
||||
from module_build_service.common import conf
|
||||
from module_build_service.builder import GenericBuilder
|
||||
|
||||
from tests.test_build.test_build import FakeModuleBuilder
|
||||
from tests.test_build.test_build import main as run_scheduler
|
||||
|
||||
|
||||
def patch_config_system_setter():
|
||||
"""bypass supported builders check"""
|
||||
def set_system(self, system):
|
||||
self._system = system
|
||||
conf._setifok_system = types.MethodType(set_system, conf)
|
||||
|
||||
|
||||
def register_fake_builder():
|
||||
patch_config_system_setter()
|
||||
conf.system = FakeModuleBuilder.backend
|
||||
GenericBuilder.register_backend_class(FakeModuleBuilder)
|
||||
|
||||
# Builder always instantly succeeds
|
||||
def on_get_task_info_cb(cls, task_id):
|
||||
return {"state": koji.TASK_STATES["CLOSED"]}
|
||||
FakeModuleBuilder.on_get_task_info_cb = on_get_task_info_cb
|
||||
|
||||
|
||||
class SimpleMock:
|
||||
"""Dummy callable mock object - we want our memory footprint to be as small as possible"""
|
||||
def __call__(self, *args, **kwargs):
|
||||
return True
|
||||
|
||||
|
||||
@mock.patch("module_build_service.scheduler.handlers.modules.handle_stream_collision_modules",
|
||||
new_callable=SimpleMock)
|
||||
@mock.patch("module_build_service.scheduler.handlers.modules.record_module_build_arches",
|
||||
new_callable=SimpleMock)
|
||||
@mock.patch("module_build_service.scheduler.greenwave.Greenwave.check_gating",
|
||||
new_callable=SimpleMock)
|
||||
def run_debug_instance(mock_1, mock_2, mock_3, host=None, port=None):
|
||||
|
||||
def handle_pdb(sig, frame):
|
||||
import pdb
|
||||
pdb.Pdb().set_trace(frame)
|
||||
|
||||
# kill -10 <PID> to start debugger
|
||||
signal.signal(signal.SIGUSR1, handle_pdb)
|
||||
|
||||
register_fake_builder()
|
||||
|
||||
host = host or conf.host
|
||||
port = port or conf.port
|
||||
|
||||
def run_app():
|
||||
app.run(host=host, port=port, debug=False)
|
||||
threading.Thread(target=run_app, daemon=True).start()
|
||||
|
||||
# run moksha hub and never stop
|
||||
run_scheduler([], stop_condition=lambda msg: False)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
run_debug_instance()
|
||||
103
tests/test_memory/test_memory.py
Normal file
103
tests/test_memory/test_memory.py
Normal file
@@ -0,0 +1,103 @@
|
||||
import json
|
||||
import os
|
||||
import psutil
|
||||
import pytest
|
||||
import requests
|
||||
import time
|
||||
|
||||
from subprocess import Popen
|
||||
from tests import read_staged_data_as_yaml
|
||||
|
||||
|
||||
base_dir = os.path.dirname(__file__)
|
||||
yaml = read_staged_data_as_yaml("testmodule.yaml")
|
||||
|
||||
LOCAL_MBS_URL = "http://localhost:5000/module-build-service/1/module-builds/"
|
||||
|
||||
|
||||
def submit_yaml_build(module_name):
|
||||
"""Submit module build with custom name to get a unique NVR each time."""
|
||||
yaml["data"]["name"] = module_name
|
||||
data = {"modulemd": str(yaml), "module_name": "testmodule"}
|
||||
r = requests.post(LOCAL_MBS_URL, data=json.dumps(data))
|
||||
if r.status_code > 300:
|
||||
pytest.fail(str(r.json()))
|
||||
return r.json()["id"]
|
||||
|
||||
|
||||
def wait_for_module_build(build_id, timeout=60, interval=5):
|
||||
"""Wait for module build to be ready
|
||||
|
||||
:param int build_id: build definition (either id or Build object)
|
||||
:param float timeout: timeout in seconds
|
||||
:param float interval: scan interval in seconds
|
||||
"""
|
||||
start = time.time()
|
||||
|
||||
while (time.time() - start) < timeout:
|
||||
state = requests.get(LOCAL_MBS_URL + str(build_id)).json()["state_name"]
|
||||
if state == "ready":
|
||||
return
|
||||
time.sleep(interval)
|
||||
pytest.skip("Wait for build timed out after {}s".format(timeout))
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def run_debug_mbs_instance():
|
||||
"""Starts a 'debug' MBS instance:
|
||||
* mbs-frontend (no auth, yaml import enabled)
|
||||
* moksha hub (in memory messaging)
|
||||
* tests.test_build.test_build.FakeModuleBuilder as builder backend (always succeeds)
|
||||
|
||||
Optionally:
|
||||
Set MBS_TEST_INSTANCE_PID env variable to run test against your own running instance.
|
||||
If you intend to run a standalone instance, make sure you have these env vars set:
|
||||
* MODULE_BUILD_SERVICE_DEVELOPER_ENV=0
|
||||
* MBS_CONFIG_SECTION=TestConfiguration
|
||||
* MBS_CONFIG_FILE=tests/test_memory/mbs_configuration.py
|
||||
* DATABASE_URI=postgresql+psycopg2://postgres:@127.0.0.1/mbstest
|
||||
|
||||
...then 'python tests/test_memory/mbs_debug.py' (and 'kill -10 <PID>' to start debugger)
|
||||
"""
|
||||
|
||||
process = None
|
||||
try:
|
||||
running_instance_pid = int(os.environ.get("MBS_TEST_INSTANCE_PID"))
|
||||
yield running_instance_pid
|
||||
except TypeError or ValueError:
|
||||
mbs_config_file_path = os.path.join(base_dir, "mbs_configuration.py")
|
||||
env = {
|
||||
"MBS_CONFIG_SECTION": "TestConfiguration",
|
||||
"MBS_CONFIG_FILE": mbs_config_file_path,
|
||||
}
|
||||
# Pass the preset database configuration (if present)
|
||||
if os.environ.get("DATABASE_URI"):
|
||||
env["DATABASE_URI"] = os.environ.get("DATABASE_URI")
|
||||
|
||||
mbs_exec_script = os.path.join(base_dir, "mbs_debug.py")
|
||||
process = Popen(["python", mbs_exec_script], stdin=None, stdout=None, env=env)
|
||||
time.sleep(5) # wait a couple of secs for MBS to start
|
||||
yield process.pid
|
||||
if process:
|
||||
process.terminate()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("num_builds", [20])
|
||||
def test_submit_build(require_platform_and_default_arch, run_debug_mbs_instance, num_builds):
|
||||
pid = run_debug_mbs_instance
|
||||
process = psutil.Process(pid)
|
||||
|
||||
def get_rss(): # resident set size in MB
|
||||
return process.memory_info().rss / 1000000
|
||||
consumed_memory = []
|
||||
|
||||
for i in range(num_builds):
|
||||
build_id = submit_yaml_build("test-module-{}".format(i))
|
||||
# wait for the build to finish, so that the build logger is flushed/closed
|
||||
wait_for_module_build(build_id, interval=0.5, timeout=10)
|
||||
consumed_memory.append(get_rss())
|
||||
|
||||
print("Memory [MB]: {}".format(consumed_memory))
|
||||
|
||||
if (consumed_memory[-1] - consumed_memory[0]) > 0.1:
|
||||
pytest.fail("Memory is leaking, [MB]: {}".format(consumed_memory))
|
||||
10
tox.ini
10
tox.ini
@@ -23,6 +23,7 @@ deps = -r{toxinidir}/test-requirements.txt
|
||||
commands =
|
||||
py.test -v \
|
||||
--ignore tests/integration \
|
||||
--ignore tests/test_memory \
|
||||
--cov module_build_service \
|
||||
--cov-report html \
|
||||
--cov-report term \
|
||||
@@ -91,3 +92,12 @@ commands =
|
||||
--html=report.html \
|
||||
--self-contained-html \
|
||||
{posargs:tests/integration}
|
||||
|
||||
[testenv:memory]
|
||||
basepython = python3
|
||||
deps = {[testenv]deps}
|
||||
passenv =
|
||||
MBS_TEST_INSTANCE_PID
|
||||
DATABASE_URI
|
||||
commands =
|
||||
py.test -rA {posargs:tests/test_memory --show-capture=no}
|
||||
|
||||
Reference in New Issue
Block a user