Merge pull request #2626 from ancorgs/obslocal_refactor

Small OBSLocal refactoring
This commit is contained in:
Ancor Gonzalez Sosa 2021-09-16 15:32:49 +02:00 committed by GitHub
commit 57abe5728b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 275 additions and 153 deletions

View File

@ -29,6 +29,8 @@ from osclib.memoize import memoize_session_reset
from urllib.error import HTTPError, URLError from urllib.error import HTTPError, URLError
from abc import ABC, abstractmethod
# pointing to other docker container # pointing to other docker container
APIURL = 'http://api:3000' APIURL = 'http://api:3000'
PROJECT = 'openSUSE:Factory' PROJECT = 'openSUSE:Factory'
@ -41,6 +43,7 @@ class TestCase(unittest.TestCase):
script_apiurl = True script_apiurl = True
script_debug = True script_debug = True
script_debug_osc = True script_debug_osc = True
review_bots = {}
def setUp(self): def setUp(self):
if os.path.exists(OSCCOOKIEJAR): if os.path.exists(OSCCOOKIEJAR):
@ -105,6 +108,14 @@ class TestCase(unittest.TestCase):
os.environ['OSRT_DISABLE_CACHE'] = 'true' os.environ['OSRT_DISABLE_CACHE'] = 'true'
def execute_script(self, args): def execute_script(self, args):
"""Executes the script stored in the ``script`` attribute of the current test.
If the attributes ``script_debug`` or ``script_debug_osc`` are set to true for the current
test, the function will add the corresponding ``--debug`` and/or ``--osc-debug`` argument
when invoking the script.
This function ensures the executed code is taken into account for the coverage calculation.
"""
if self.script: if self.script:
args.insert(0, self.script) args.insert(0, self.script)
if self.script_debug: if self.script_debug:
@ -117,6 +128,18 @@ class TestCase(unittest.TestCase):
self.execute(args) self.execute(args)
def execute_review_script(self, request_id, user):
"""Executes the review bot that corresponds to the script pointed by the ``script``
attribute, targeting the given request and as the given user.
See :func:`execute_script`.
The script must follow the commandline syntax of a review bot.
"""
self.osc_user(user)
self.execute_script(['id', request_id])
self.osc_user_pop()
def execute_osc(self, args): def execute_osc(self, args):
# The wrapper allows this to work properly when osc installed via pip. # The wrapper allows this to work properly when osc installed via pip.
args.insert(0, 'osc-wrapper.py') args.insert(0, 'osc-wrapper.py')
@ -137,6 +160,16 @@ class TestCase(unittest.TestCase):
self.assertTrue(text in self.output, '[MISSING] ' + text) self.assertTrue(text in self.output, '[MISSING] ' + text)
def assertReview(self, rid, **kwargs): def assertReview(self, rid, **kwargs):
"""Asserts there is a review for the given request that is assigned to the given target
(user, group or project) and that is in the expected state.
For example, this asserts there is a new review for the user 'jdoe' in the request 20:
``assertReview(20, by_user=('jdoe', 'new'))``
:return: the found review, if the assertion succeeds
:rtype: Review or None
"""
request = get_request(self.apiurl, rid) request = get_request(self.apiurl, rid)
for review in request.reviews: for review in request.reviews:
for key, value in kwargs.items(): for key, value in kwargs.items():
@ -146,12 +179,30 @@ class TestCase(unittest.TestCase):
self.fail('{} not found'.format(kwargs)) self.fail('{} not found'.format(kwargs))
def assertReviewBot(self, request_id, user, before, after, comment=None): def assertReviewScript(self, request_id, user, before, after, comment=None):
"""Asserts the review script pointed by the ``script`` attribute of the current test can
be executed and it produces the expected change in the reviews of a request.
For this assertion to succeed the request must contain initially a review in the original
state targeting the given user, then the script will be executed and it will be asserted
that the request then has the final expected state (and, optionally, the expected comment).
See :func:`execute_review_script`.
:param request_id: request for which the script will be executed
:type request_id: int
:param user: target of the review, it will also be used to execute the script
:type user: str
:param before: expected state of the review before executing the script
:type before: str
:param before: expected state of the review after executing the script
:type before: str
:param comment: expected message for the review after executing the script
:type comment: str
"""
self.assertReview(request_id, by_user=(user, before)) self.assertReview(request_id, by_user=(user, before))
self.osc_user(user) self.execute_review_script(request_id, user)
self.execute_script(['id', request_id])
self.osc_user_pop()
review = self.assertReview(request_id, by_user=(user, after)) review = self.assertReview(request_id, by_user=(user, after))
if comment: if comment:
@ -169,11 +220,48 @@ class TestCase(unittest.TestCase):
length = 2 length = 2
return prefix + ''.join([random.choice(string.ascii_letters) for i in range(length)]) return prefix + ''.join([random.choice(string.ascii_letters) for i in range(length)])
def setup_review_bot(self, wf, project, user, bot_class):
"""Instantiates a bot for the given project, adding the associated user as reviewer.
class StagingWorkflow(object): :param wf: workflow containing the project, users, etc.
"""This class is intended to setup and manipulate the environment (projects, users, etc.) in :type wf: StagingWorkflow
the local OBS instance used to tests the release tools. It makes easy to setup scenarios similar :param project: name of the project the bot will act on
to the ones used during the real (open)SUSE development, with staging projects, rings, etc. :type project: str
:param user: user to create for the bot
:type user: str
:param bot_class: type of bot to setup
"""
wf.create_user(user)
prj = wf.projects[project]
prj.add_reviewers(users = [user])
bot_name = self.generate_bot_name(user)
bot = bot_class(wf.apiurl, user=user, logger=logging.getLogger(bot_name))
bot.bot_name = bot_name
self.review_bots[user] = bot
def execute_review_bot(self, requests, user):
"""Checks the given requests using the bot associated to the given user.
The bot must have been previously configured via :func:`setup_review_bot`.
"""
bot = self.review_bots[user]
bot.set_request_ids(requests)
self.osc_user(user)
bot.check_requests()
self.osc_user_pop()
def generate_bot_name(self, user):
"""Used to ensure different test runs operate in unique namespace."""
return '::'.join([type(self).__name__, user, str(random.getrandbits(8))])
class StagingWorkflow(ABC):
"""This abstract base class is intended to setup and manipulate the environment (projects,
users, etc.) in the local OBS instance used to tests the release tools. Thus, the derivative
classes make easy to setup scenarios similar to the ones used during the real (open)SUSE
development.
""" """
def __init__(self, project=PROJECT): def __init__(self, project=PROJECT):
"""Initializes the configuration """Initializes the configuration
@ -195,7 +283,7 @@ class StagingWorkflow(object):
self.requests = [] self.requests = []
self.groups = [] self.groups = []
self.users = [] self.users = []
self.attributes = {} self.attr_types = {}
logging.basicConfig() logging.basicConfig()
# clear cache from other tests - otherwise the VCR is replayed depending # clear cache from other tests - otherwise the VCR is replayed depending
@ -215,15 +303,27 @@ class StagingWorkflow(object):
Cache.CACHE_DIR = None Cache.CACHE_DIR = None
Cache.PATTERNS = {} Cache.PATTERNS = {}
Cache.init() Cache.init()
# Note this implicitly calls create_target()
self.setup_remote_config() self.setup_remote_config()
self.load_config() self.load_config()
self.api = StagingAPI(APIURL, project) self.api = StagingAPI(APIURL, project)
# The ProductVersion is required for some actions, for example, when a request is accepted
self.create_attribute_type('OSRT', 'ProductVersion', 1) @abstractmethod
def initial_config(self):
"""Values to use to initialize the 'Config' attribute at :func:`setup_remote_config`"""
pass
@abstractmethod
def staging_group_name(self):
"""Name of the group in charge of the staging workflow"""
pass
def load_config(self, project=None): def load_config(self, project=None):
"""Loads the corresponding :class:`osclib.Config` object into the attribute ``config`` """Loads the corresponding :class:`osclib.Config` object into the attribute ``config``
Such an object represents the set of values stored on the attribute 'Config' of the
target project. See :func:`remote_config_set`.
:param project: target project name :param project: target project name
:type project: str :type project: str
""" """
@ -233,9 +333,11 @@ class StagingWorkflow(object):
self.config = Config(APIURL, project) self.config = Config(APIURL, project)
def create_attribute_type(self, namespace, name, values=None): def create_attribute_type(self, namespace, name, values=None):
if not namespace in self.attributes: self.attributes[namespace] = [] """Creates a new attribute type in the OBS instance."""
if not name in self.attributes[namespace]: self.attributes[namespace].append(name) if not namespace in self.attr_types: self.attr_types[namespace] = []
if not name in self.attr_types[namespace]: self.attr_types[namespace].append(name)
meta = """ meta = """
<namespace name='{}'> <namespace name='{}'>
@ -252,17 +354,31 @@ class StagingWorkflow(object):
osc.core.http_PUT(url, data=meta) osc.core.http_PUT(url, data=meta)
def setup_remote_config(self): def setup_remote_config(self):
"""Creates the attribute 'Config' for the target project, with proper initial content.
See :func:`remote_config_set` for more information about that attribute.
Note this calls :func:`create_target` to ensure the target project exists.
"""
# First ensure the existence of both the target project and the 'Config' attribute type
self.create_target() self.create_target()
self.create_attribute_type('OSRT', 'Config', 1) self.create_attribute_type('OSRT', 'Config', 1)
config = { self.remote_config_set(self.initial_config(), replace_all=True)
'overridden-by-local': 'remote-nope',
'staging-group': 'factory-staging',
'remote-only': 'remote-indeed',
}
self.remote_config_set(config, replace_all=True)
def remote_config_set(self, config, replace_all=False): def remote_config_set(self, config, replace_all=False):
"""Sets the values of the 'Config' attribute for the target project.
That attribute stores a set of values that are useful to influence the behavior of several
tools and bots in the context of the given project. For convenience, such a collection of
values is usually accessed using a :class:`osclib.Config` object. See :func:`load_config`.
:param config: values to write into the attribute
:type config: dict[str, str]
:param replace_all: whether the previous content of 'Config' should be cleared up
:type replace_all: bool
"""
if not replace_all: if not replace_all:
config_existing = Config.get(self.apiurl, self.project) config_existing = Config.get(self.apiurl, self.project)
config_existing.update(config) config_existing.update(config)
@ -331,50 +447,31 @@ class StagingWorkflow(object):
self.projects[home_project] = Project(home_project, create=False) self.projects[home_project] = Project(home_project, create=False)
def create_target(self): def create_target(self):
"""Creates """Creates the main project that represents the product being developed and, as such, is
expected to be the target for requests. It also creates all the associated projects, users
and groups involved in the development workflow.
- target project In the base implementation, that includes:
- "staging-bot" user
- "factory-staging" group
setup staging and also ``*:Staging:A`` and ``*:Staging:B`` projects. - The target project (see :func:`create_target_project`)
- A group of staging managers including the "staging-bot" user
(see :func:`create_staging_users`)
- A couple of staging projects for the target one
- The ProductVersion attribute type, that is used by the staging tools
After the execution, the target project is indexed in the projects dictionary twice, After the execution, the target project is indexed in the projects dictionary twice,
by its name and as 'target'. by its name and as 'target'.
""" """
if self.projects.get('target'): return if self.projects.get('target'): return
self.create_user('staging-bot')
self.create_group('factory-staging', users=['staging-bot'])
p = Project(name=self.project, reviewer={'groups': ['factory-staging']})
self.projects['target'] = p
self.projects[self.project] = p
url = osc.core.makeurl(APIURL, ['staging', self.project, 'workflow']) self.create_target_project()
data = "<workflow managers='factory-staging'/>" self.create_staging_users()
osc.core.http_POST(url, data=data)
# creates A and B as well
self.projects['staging:A'] = Project(self.project + ':Staging:A', create=False) self.projects['staging:A'] = Project(self.project + ':Staging:A', create=False)
self.projects['staging:B'] = Project(self.project + ':Staging:B', create=False) self.projects['staging:B'] = Project(self.project + ':Staging:B', create=False)
def setup_rings(self, devel_project=None): # The ProductVersion is required for some actions, like accepting a staging project
"""Creates a typical Factory setup with rings. self.create_attribute_type('OSRT', 'ProductVersion', 1)
It creates three projects: 'ring0', 'ring1' and the target (see :func:`create_target`).
It also creates a 'wine' package in the target project and a link from it to ring1.
It sets the devel project for the package if ``devel_project`` is given.
:param devel_project: name of devel project. It must exist and contain a 'wine' package,
otherwise OBS returns an error code.
:type devel_project: str or None
"""
self.create_target()
self.projects['ring0'] = Project(name=self.project + ':Rings:0-Bootstrap')
self.projects['ring1'] = Project(name=self.project + ':Rings:1-MinimalX')
target_wine = Package(
name='wine', project=self.projects['target'], devel_project=devel_project
)
target_wine.create_commit()
self.create_link(target_wine, self.projects['ring1'])
def create_package(self, project, package): def create_package(self, project, package):
project = self.create_project(project) project = self.create_project(project)
@ -452,32 +549,6 @@ class StagingWorkflow(object):
package.create_commit(text=text) package.create_commit(text=text)
return self.submit_package(package) return self.submit_package(package)
def create_staging(self, suffix, freeze=False, rings=None, with_repo=False):
staging_key = 'staging:{}'.format(suffix)
# do not reattach if already present
if not staging_key in self.projects:
staging_name = self.project + ':Staging:' + suffix
staging = Project(staging_name, create=False, with_repo=with_repo)
url = osc.core.makeurl(APIURL, ['staging', self.project, 'staging_projects'])
data = '<workflow><staging_project>{}</staging_project></workflow>'
osc.core.http_POST(url, data=data.format(staging_name))
self.projects[staging_key] = staging
else:
staging = self.projects[staging_key]
project_links = []
if rings == 0:
project_links.append(self.project + ":Rings:0-Bootstrap")
if rings == 1 or rings == 0:
project_links.append(self.project + ":Rings:1-MinimalX")
staging.update_meta(project_links=project_links, maintainer={'groups': ['factory-staging']},
with_repo=with_repo)
if freeze:
FreezeCommand(self.api).perform(staging.name)
return staging
def __del__(self): def __del__(self):
if not self.api: if not self.api:
return return
@ -498,8 +569,8 @@ class StagingWorkflow(object):
request.revoke() request.revoke()
for group in self.groups: for group in self.groups:
self.remove_group(group) self.remove_group(group)
for namespace in self.attributes: for namespace in self.attr_types:
self.remove_attributes(namespace) self.remove_attribute_types(namespace)
print('done') print('done')
@ -516,14 +587,14 @@ class StagingWorkflow(object):
url = osc.core.makeurl(APIURL, ['group', group]) url = osc.core.makeurl(APIURL, ['group', group])
self._safe_delete(url) self._safe_delete(url)
def remove_attributes(self, namespace): def remove_attribute_types(self, namespace):
"""Removes an attributes namespace and all the attributes it contains """Removes an attributes namespace and all the attribute types it contains
:param namespace: attributes namespace to remove :param namespace: attributes namespace to remove
:type namespace: str :type namespace: str
""" """
for name in self.attributes[namespace]: for name in self.attr_types[namespace]:
print('deleting attribute {}:{}'.format(namespace, name)) print('deleting attribute type {}:{}'.format(namespace, name))
url = osc.core.makeurl(APIURL, ['attribute', namespace, name, '_meta']) url = osc.core.makeurl(APIURL, ['attribute', namespace, name, '_meta'])
self._safe_delete(url) self._safe_delete(url)
print('deleting namespace', namespace) print('deleting namespace', namespace)
@ -541,6 +612,88 @@ class StagingWorkflow(object):
except HTTPError: except HTTPError:
pass pass
def create_target_project(self):
"""Creates the main target project (see :func:`create_target`)"""
p = Project(name=self.project)
self.projects['target'] = p
self.projects[self.project] = p
def create_staging_users(self):
"""Creates users and groups for the staging workflow for the target project
(see :func:`create_target`)
"""
group = self.staging_group_name()
self.create_user('staging-bot')
self.create_group(group, users=['staging-bot'])
self.projects['target'].add_reviewers(groups = [group])
url = osc.core.makeurl(APIURL, ['staging', self.project, 'workflow'])
data = f"<workflow managers='{group}'/>"
osc.core.http_POST(url, data=data)
class FactoryWorkflow(StagingWorkflow):
"""A class that makes easy to setup scenarios similar to the one used during the real
openSUSE Factory development, with staging projects, rings, etc.
"""
def staging_group_name(self):
return 'factory-staging'
def initial_config(self):
return {
'overridden-by-local': 'remote-nope',
'staging-group': 'factory-staging',
'remote-only': 'remote-indeed',
}
def setup_rings(self, devel_project=None):
"""Creates a typical Factory setup with rings.
It creates three projects: 'ring0', 'ring1' and the target (see :func:`create_target`).
It also creates a 'wine' package in the target project and a link from it to ring1.
It sets the devel project for the package if ``devel_project`` is given.
:param devel_project: name of devel project. It must exist and contain a 'wine' package,
otherwise OBS returns an error code.
:type devel_project: str or None
"""
self.create_target()
self.projects['ring0'] = Project(name=self.project + ':Rings:0-Bootstrap')
self.projects['ring1'] = Project(name=self.project + ':Rings:1-MinimalX')
target_wine = Package(
name='wine', project=self.projects['target'], devel_project=devel_project
)
target_wine.create_commit()
self.create_link(target_wine, self.projects['ring1'])
def create_staging(self, suffix, freeze=False, rings=None, with_repo=False):
staging_key = 'staging:{}'.format(suffix)
# do not reattach if already present
if not staging_key in self.projects:
staging_name = self.project + ':Staging:' + suffix
staging = Project(staging_name, create=False, with_repo=with_repo)
url = osc.core.makeurl(APIURL, ['staging', self.project, 'staging_projects'])
data = '<workflow><staging_project>{}</staging_project></workflow>'
osc.core.http_POST(url, data=data.format(staging_name))
self.projects[staging_key] = staging
else:
staging = self.projects[staging_key]
project_links = []
if rings == 0:
project_links.append(self.project + ":Rings:0-Bootstrap")
if rings == 1 or rings == 0:
project_links.append(self.project + ":Rings:1-MinimalX")
group = self.staging_group_name()
staging.update_meta(project_links=project_links, maintainer={'groups': [group]},
with_repo=with_repo)
if freeze:
FreezeCommand(self.api).perform(staging.name)
return staging
class Project(object): class Project(object):
"""This class represents a project in the testing environment of the release tools. It usually """This class represents a project in the testing environment of the release tools. It usually
corresponds to a project in the local OBS instance that is used by the tests. corresponds to a project in the local OBS instance that is used by the tests.

View File

@ -11,7 +11,7 @@ class TestReviewBotComment(OBSLocal.TestCase):
def setUp(self): def setUp(self):
super(TestReviewBotComment, self).setUp() super(TestReviewBotComment, self).setUp()
self.api = CommentAPI(self.apiurl) self.api = CommentAPI(self.apiurl)
self.wf = OBSLocal.StagingWorkflow() self.wf = OBSLocal.FactoryWorkflow()
self.wf.create_user('factory-auto') self.wf.create_user('factory-auto')
self.project = self.wf.create_project(PROJECT) self.project = self.wf.create_project(PROJECT)

View File

@ -13,7 +13,7 @@ from . import OBSLocal
class TestAccept(unittest.TestCase): class TestAccept(unittest.TestCase):
def setup_wf(self): def setup_wf(self):
wf = OBSLocal.StagingWorkflow() wf = OBSLocal.FactoryWorkflow()
wf.setup_rings() wf.setup_rings()
self.c_api = CommentAPI(wf.api.apiurl) self.c_api = CommentAPI(wf.api.apiurl)

View File

@ -17,7 +17,7 @@ class TestApiCalls(OBSLocal.TestCase):
def setUp(self): def setUp(self):
super(TestApiCalls, self).setUp() super(TestApiCalls, self).setUp()
self.wf = OBSLocal.StagingWorkflow() self.wf = OBSLocal.FactoryWorkflow()
self.wf.setup_rings() self.wf.setup_rings()
self.staging_b = self.wf.create_staging('B') self.staging_b = self.wf.create_staging('B')
prj = self.staging_b.name prj = self.staging_b.name

View File

@ -5,7 +5,7 @@ class TestBuildFailReminder(OBSLocal.TestCase):
script = './build-fail-reminder.py' script = './build-fail-reminder.py'
def test_basic(self): def test_basic(self):
self.wf = OBSLocal.StagingWorkflow() self.wf = OBSLocal.FactoryWorkflow()
self.wf.create_target() self.wf.create_target()
self.execute_script(['--relay', 'smtp', '--sender', 'Tester']) self.execute_script(['--relay', 'smtp', '--sender', 'Tester'])

View File

@ -23,8 +23,8 @@ class TestCheckSource(OBSLocal.TestCase):
def setUp(self): def setUp(self):
super(TestCheckSource, self).setUp() super(TestCheckSource, self).setUp()
# Using OBSLocal.StagingWorkflow makes it easier to setup testing scenarios # Using OBSLocal.FactoryWorkflow makes it easier to setup testing scenarios
self.wf = OBSLocal.StagingWorkflow(PROJECT) self.wf = OBSLocal.FactoryWorkflow(PROJECT)
self.project = self.wf.projects[PROJECT] self.project = self.wf.projects[PROJECT]
# Set up the reviewers team # Set up the reviewers team

View File

@ -24,7 +24,7 @@ class TestCheckCommand(unittest.TestCase):
def test_check_command_single(self): def test_check_command_single(self):
"""Validate json conversion for a single project.""" """Validate json conversion for a single project."""
wf = OBSLocal.StagingWorkflow() wf = OBSLocal.FactoryWorkflow()
wf.create_staging('H') wf.create_staging('H')
self.checkcommand = CheckCommand(wf.api) self.checkcommand = CheckCommand(wf.api)

View File

@ -93,7 +93,7 @@ handle
class TestCommentOBS(OBSLocal.TestCase): class TestCommentOBS(OBSLocal.TestCase):
def setUp(self): def setUp(self):
super(TestCommentOBS, self).setUp() super(TestCommentOBS, self).setUp()
self.wf = OBSLocal.StagingWorkflow() self.wf = OBSLocal.FactoryWorkflow()
self.wf.create_user('factory-auto') self.wf.create_user('factory-auto')
self.wf.create_user('repo-checker') self.wf.create_user('repo-checker')
self.wf.create_user('staging-bot') self.wf.create_user('staging-bot')

View File

@ -9,7 +9,7 @@ from . import OBSLocal
class TestConfig(unittest.TestCase): class TestConfig(unittest.TestCase):
def setup_vcr(self): def setup_vcr(self):
return OBSLocal.StagingWorkflow() return OBSLocal.FactoryWorkflow()
def test_basic(self): def test_basic(self):
wf = self.setup_vcr() wf = self.setup_vcr()

View File

@ -7,7 +7,7 @@ class TestDevelProject(OBSLocal.TestCase):
def setUp(self): def setUp(self):
super().setUp() super().setUp()
self.wf = OBSLocal.StagingWorkflow() self.wf = OBSLocal.FactoryWorkflow()
spa = self.wf.create_project('server:php:applications') spa = self.wf.create_project('server:php:applications')
OBSLocal.Package('drush', project=spa) OBSLocal.Package('drush', project=spa)
OBSLocal.Package('drush', self.wf.projects['target'], devel_project='server:php:applications') OBSLocal.Package('drush', self.wf.projects['target'], devel_project='server:php:applications')

View File

@ -1,4 +1,3 @@
import logging
from . import OBSLocal from . import OBSLocal
import random import random
import os import os
@ -17,7 +16,6 @@ from check_source import CheckSource
legal_auto = __import__("legal-auto") # Needed because of the dash in the filename legal_auto = __import__("legal-auto") # Needed because of the dash in the filename
LegalAuto = legal_auto.LegalAuto LegalAuto = legal_auto.LegalAuto
PROJECT = 'openSUSE:Factory' PROJECT = 'openSUSE:Factory'
DEVEL_PROJECT = 'devel:drinking' DEVEL_PROJECT = 'devel:drinking'
STAGING_PROJECT_NAME = 'openSUSE:Factory:Staging:A' STAGING_PROJECT_NAME = 'openSUSE:Factory:Staging:A'
@ -41,8 +39,8 @@ class TestFactorySubmitRequest(OBSLocal.TestCase):
super(TestFactorySubmitRequest, self).setUp() super(TestFactorySubmitRequest, self).setUp()
# Setup the basic scenario, with manual reviewers, staging projects, rings and wine as # Setup the basic scenario, with manual reviewers, staging projects, rings and wine as
# example package (wine is in ring1, see OBSLocal.StagingWorkflow.setup_rings) # example package (wine is in ring1, see OBSLocal.FactoryWorkflow.setup_rings)
self.wf = OBSLocal.StagingWorkflow(PROJECT) self.wf = OBSLocal.FactoryWorkflow(PROJECT)
self.__setup_review_team() self.__setup_review_team()
self.__setup_devel_package('wine') self.__setup_devel_package('wine')
self.wf.setup_rings(devel_project=DEVEL_PROJECT) self.wf.setup_rings(devel_project=DEVEL_PROJECT)
@ -52,9 +50,8 @@ class TestFactorySubmitRequest(OBSLocal.TestCase):
self.wf.remote_config_set({'required-source-maintainer': ''}) self.wf.remote_config_set({'required-source-maintainer': ''})
# Setup the different bots typically used for Factory # Setup the different bots typically used for Factory
self.review_bots = {} self.setup_review_bot(self.wf, PROJECT, 'factory-auto', CheckSource)
self.__setup_review_bot('factory-auto', CheckSource) self.setup_review_bot(self.wf, PROJECT, 'licensedigger', LegalAuto)
self.__setup_review_bot('licensedigger', LegalAuto)
# Sorry, but LegalAuto is simply too hard to test while keeping this test readable, # Sorry, but LegalAuto is simply too hard to test while keeping this test readable,
# see the description of __mock_licendigger for more rationale # see the description of __mock_licendigger for more rationale
@ -70,12 +67,6 @@ class TestFactorySubmitRequest(OBSLocal.TestCase):
super().tearDown() super().tearDown()
del self.wf del self.wf
def project(self):
return self.wf.projects[PROJECT]
def devel_project(self):
return self.wf.projects[DEVEL_PROJECT]
def test_happy_path(self): def test_happy_path(self):
"""Tests the ideal case in which all bots are happy and the request successfully goes """Tests the ideal case in which all bots are happy and the request successfully goes
through staging""" through staging"""
@ -86,8 +77,8 @@ class TestFactorySubmitRequest(OBSLocal.TestCase):
self.assertReview(reqid, by_group=('factory-staging', 'new')) self.assertReview(reqid, by_group=('factory-staging', 'new'))
# Let bots come into play # Let bots come into play
self.__execute_review_bot('factory-auto', [reqid]) self.execute_review_bot([reqid], 'factory-auto')
self.__execute_review_bot('licensedigger', [reqid]) self.execute_review_bot([reqid], 'licensedigger')
# Bots are happy, now it's time for manual review (requested by the bots) and # Bots are happy, now it's time for manual review (requested by the bots) and
# for the staging work # for the staging work
@ -112,6 +103,10 @@ class TestFactorySubmitRequest(OBSLocal.TestCase):
self.assertReview(reqid, by_group=('opensuse-review-team', 'accepted')) self.assertReview(reqid, by_group=('opensuse-review-team', 'accepted'))
self.assertReview(reqid, by_group=('factory-staging', 'new')) self.assertReview(reqid, by_group=('factory-staging', 'new'))
# Before using the staging plugin, we need to force a reload of the configuration
# because execute_review_bot temporarily switches the user and that causes problems
self.wf.load_config()
# The Staging Manager puts the request into a staging project # The Staging Manager puts the request into a staging project
SelectCommand(self.wf.api, STAGING_PROJECT_NAME).perform(['wine']) SelectCommand(self.wf.api, STAGING_PROJECT_NAME).perform(['wine'])
@ -152,32 +147,6 @@ class TestFactorySubmitRequest(OBSLocal.TestCase):
self.wf.create_user(HUMAN_REVIEWER) self.wf.create_user(HUMAN_REVIEWER)
self.wf.create_group('opensuse-review-team', users=[HUMAN_REVIEWER]) self.wf.create_group('opensuse-review-team', users=[HUMAN_REVIEWER])
def __setup_review_bot(self, user, bot_class):
"""Instantiates a bot and adds the associated user as reviewer of PROJECT
:param user: user to create for the bot
:type user: str
:param bot_class: type of bot to setup
"""
self.wf.create_user(user)
self.project().add_reviewers(users = [user])
bot_name = self.__generate_bot_name(user)
bot = bot_class(self.wf.apiurl, user=user, logger=logging.getLogger(bot_name))
bot.bot_name = bot_name
self.review_bots[user] = bot
def __execute_review_bot(self, user, requests):
bot = self.review_bots[user]
bot.set_request_ids(requests)
bot.check_requests()
def __generate_bot_name(self, user):
"""Used to ensure different test runs operate in unique namespace."""
return '::'.join([type(self).__name__, user, str(random.getrandbits(8))])
def __mock_licensedigger(self): def __mock_licensedigger(self):
"""Mocks the execution of the LegalAuto bot, so it always succeeds and accepts the review """Mocks the execution of the LegalAuto bot, so it always succeeds and accepts the review

View File

@ -24,7 +24,7 @@ class TestFreeze(OBSLocal.TestCase):
return os.path.join(os.getcwd(), 'tests/fixtures') return os.path.join(os.getcwd(), 'tests/fixtures')
def test_bootstrap_copy(self): def test_bootstrap_copy(self):
wf = OBSLocal.StagingWorkflow() wf = OBSLocal.FactoryWorkflow()
fc = FreezeCommand(wf.api) fc = FreezeCommand(wf.api)

View File

@ -119,7 +119,7 @@ class TestOBSLock(unittest.TestCase):
self.assertEqual(reason_sub, None, 'does not inherit hold') self.assertEqual(reason_sub, None, 'does not inherit hold')
def setup_vcr(self): def setup_vcr(self):
wf = OBSLocal.StagingWorkflow() wf = OBSLocal.FactoryWorkflow()
wf.create_target() wf.create_target()
# we should most likely create this as part of create_target, but # we should most likely create this as part of create_target, but
# it just slows down all other tests # it just slows down all other tests

View File

@ -28,7 +28,7 @@ class TestOrigin(OBSLocal.TestCase):
super().setUp() super().setUp()
self.target_project = self.randomString('target') self.target_project = self.randomString('target')
self.wf = OBSLocal.StagingWorkflow(self.target_project) self.wf = OBSLocal.FactoryWorkflow(self.target_project)
self.wf.create_attribute_type('OSRT', 'OriginConfig', 1) self.wf.create_attribute_type('OSRT', 'OriginConfig', 1)
@ -111,7 +111,7 @@ class TestOrigin(OBSLocal.TestCase):
self.origin_config_write([]) self.origin_config_write([])
request = self.wf.create_submit_request(self.randomString('devel'), self.randomString('package')) request = self.wf.create_submit_request(self.randomString('devel'), self.randomString('package'))
self.assertReviewBot(request.reqid, self.bot_user, 'new', 'new') self.assertReviewScript(request.reqid, self.bot_user, 'new', 'new')
self.assertOutput(f'skipping {request.reqid} of age') self.assertOutput(f'skipping {request.reqid} of age')
self.assertOutput('since it is younger than 1800s') self.assertOutput('since it is younger than 1800s')
@ -130,14 +130,14 @@ class TestOrigin(OBSLocal.TestCase):
def test_no_config(self): def test_no_config(self):
request = self.wf.create_submit_request(self.randomString('devel'), self.randomString('package')) request = self.wf.create_submit_request(self.randomString('devel'), self.randomString('package'))
self.assertReviewBot(request.reqid, self.bot_user, 'new', 'accepted', 'skipping since no OSRT:OriginConfig') self.assertReviewScript(request.reqid, self.bot_user, 'new', 'accepted', 'skipping since no OSRT:OriginConfig')
def test_not_allowed_origin(self): def test_not_allowed_origin(self):
self.remote_config_set_age_minimum() self.remote_config_set_age_minimum()
self.origin_config_write([{'fakeProject': {}}], {'unknown_origin_wait': True}) self.origin_config_write([{'fakeProject': {}}], {'unknown_origin_wait': True})
request = self.wf.create_submit_request(self.randomString('devel'), self.randomString('package')) request = self.wf.create_submit_request(self.randomString('devel'), self.randomString('package'))
self.assertReviewBot(request.reqid, self.bot_user, 'new', 'new') self.assertReviewScript(request.reqid, self.bot_user, 'new', 'new')
comment = [ comment = [
'<!-- OriginManager state=seen result=None -->', '<!-- OriginManager state=seen result=None -->',
@ -148,7 +148,7 @@ class TestOrigin(OBSLocal.TestCase):
self.assertComment(request.reqid, comment) self.assertComment(request.reqid, comment)
self.origin_config_write([{'fakeProject': {}}], {'unknown_origin_wait': False}) self.origin_config_write([{'fakeProject': {}}], {'unknown_origin_wait': False})
self.assertReviewBot(request.reqid, self.bot_user, 'new', 'declined', 'review failed') self.assertReviewScript(request.reqid, self.bot_user, 'new', 'declined', 'review failed')
comment.pop() comment.pop()
self.assertComment(request.reqid, comment) self.assertComment(request.reqid, comment)
@ -173,7 +173,7 @@ class TestOrigin(OBSLocal.TestCase):
attribute_value_save(self.wf.apiurl, devel_project, 'ApprovedRequestSource', '', 'OBS') attribute_value_save(self.wf.apiurl, devel_project, 'ApprovedRequestSource', '', 'OBS')
if not only_devel: if not only_devel:
self.assertReviewBot(request.reqid, self.bot_user, 'new', 'new') self.assertReviewScript(request.reqid, self.bot_user, 'new', 'new')
comment = [ comment = [
'<!-- OriginManager state=seen result=None -->', '<!-- OriginManager state=seen result=None -->',
@ -190,7 +190,7 @@ class TestOrigin(OBSLocal.TestCase):
else: else:
comment = 'only devel origin allowed' comment = 'only devel origin allowed'
self.assertReviewBot(request.reqid, self.bot_user, 'new', 'accepted') self.assertReviewScript(request.reqid, self.bot_user, 'new', 'accepted')
self.assertAnnotation(request.reqid, { self.assertAnnotation(request.reqid, {
'comment': comment, 'comment': comment,
'origin': devel_project, 'origin': devel_project,
@ -214,7 +214,7 @@ class TestOrigin(OBSLocal.TestCase):
origin_info = origin_find(self.wf.apiurl, self.wf.project, package) origin_info = origin_find(self.wf.apiurl, self.wf.project, package)
self.assertEqual(origin_info, None) self.assertEqual(origin_info, None)
self.assertReviewBot(request_id_change_devel, self.bot_user, 'new', 'accepted') self.assertReviewScript(request_id_change_devel, self.bot_user, 'new', 'accepted')
self.assertAnnotation(request_id_change_devel, { self.assertAnnotation(request_id_change_devel, {
'origin': devel_project, 'origin': devel_project,
}) })
@ -237,7 +237,7 @@ class TestOrigin(OBSLocal.TestCase):
self.assertEqual(request_future, False) self.assertEqual(request_future, False)
self.osc_user_pop() self.osc_user_pop()
self.assertReviewBot(request_id_update, self.bot_user, 'new', 'accepted') self.assertReviewScript(request_id_update, self.bot_user, 'new', 'accepted')
self.assertAnnotation(request_id_update, { self.assertAnnotation(request_id_update, {
'origin': devel_project, 'origin': devel_project,
}) })
@ -271,7 +271,7 @@ class TestOrigin(OBSLocal.TestCase):
if request_future: if request_future:
request_id_change_devel_new = request_future.print_and_create() request_id_change_devel_new = request_future.print_and_create()
self.assertReviewBot(request_id_change_devel_new, self.bot_user, 'new', 'accepted') self.assertReviewScript(request_id_change_devel_new, self.bot_user, 'new', 'accepted')
self.assertAnnotation(request_id_change_devel_new, { self.assertAnnotation(request_id_change_devel_new, {
'origin': devel_project_new, 'origin': devel_project_new,
'origin_old': devel_project, 'origin_old': devel_project,
@ -324,7 +324,7 @@ class TestOrigin(OBSLocal.TestCase):
request_upstream2 = self.wf.submit_package(devel_package, upstream2_project) request_upstream2 = self.wf.submit_package(devel_package, upstream2_project)
request_target = self.wf.submit_package(devel_package, self.target_project) request_target = self.wf.submit_package(devel_package, self.target_project)
self.assertReviewBot(request_target.reqid, self.bot_user, 'new', 'new') self.assertReviewScript(request_target.reqid, self.bot_user, 'new', 'new')
comment = [ comment = [
'<!-- OriginManager state=seen result=None -->', '<!-- OriginManager state=seen result=None -->',
f'Waiting on acceptance of request#{request_upstream2.reqid}.', f'Waiting on acceptance of request#{request_upstream2.reqid}.',
@ -333,7 +333,7 @@ class TestOrigin(OBSLocal.TestCase):
request_upstream2.change_state('accepted') request_upstream2.change_state('accepted')
self.assertReviewBot(request_target.reqid, self.bot_user, 'new', 'accepted') self.assertReviewScript(request_target.reqid, self.bot_user, 'new', 'accepted')
self.assertAnnotation(request_target.reqid, { self.assertAnnotation(request_target.reqid, {
'origin': upstream2_project, 'origin': upstream2_project,
'origin_old': upstream1_project, 'origin_old': upstream1_project,
@ -490,7 +490,7 @@ class TestOrigin(OBSLocal.TestCase):
self.assertNoUpdate(package1) self.assertNoUpdate(package1)
# Accept request and ensure update since no request to supersede. # Accept request and ensure update since no request to supersede.
self.assertReviewBot(request_id_package1_1, self.bot_user, 'new', 'accepted') self.assertReviewScript(request_id_package1_1, self.bot_user, 'new', 'accepted')
request_state_change(self.wf.apiurl, request_id_package1_1, 'accepted') request_state_change(self.wf.apiurl, request_id_package1_1, 'accepted')
request_id_package1_2 = self.assertUpdate(package1) request_id_package1_2 = self.assertUpdate(package1)

View File

@ -11,7 +11,7 @@ class TestRepository(unittest.TestCase):
def setUp(self): def setUp(self):
super(TestRepository, self).setUp() super(TestRepository, self).setUp()
self.wf = OBSLocal.StagingWorkflow() self.wf = OBSLocal.FactoryWorkflow()
def tearDown(self): def tearDown(self):
del self.wf del self.wf

View File

@ -25,7 +25,7 @@ class TestSelect(OBSLocal.TestCase):
def setUp(self): def setUp(self):
super().setUp() super().setUp()
super(TestSelect, self).setUp() super(TestSelect, self).setUp()
self.wf = OBSLocal.StagingWorkflow() self.wf = OBSLocal.FactoryWorkflow()
def tearDown(self): def tearDown(self):
super(TestSelect, self).tearDown() super(TestSelect, self).tearDown()

View File

@ -7,7 +7,7 @@ from . import OBSLocal
class TestUnselect(OBSLocal.TestCase): class TestUnselect(OBSLocal.TestCase):
def test_cleanup_filter(self): def test_cleanup_filter(self):
wf = OBSLocal.StagingWorkflow() wf = OBSLocal.FactoryWorkflow()
UnselectCommand.config_init(wf.api) UnselectCommand.config_init(wf.api)
UnselectCommand.cleanup_days = 1 UnselectCommand.cleanup_days = 1
obsolete = wf.api.project_status_requests('obsolete', UnselectCommand.filter_obsolete) obsolete = wf.api.project_status_requests('obsolete', UnselectCommand.filter_obsolete)