Browse Source

[TESTS] Start the test suite.

This is still not complete enough, but should be a good starting point
for others to add more tests or request changes by adapting the tests.
Evilham 1 year ago
7 changed files with 308 additions and 8 deletions
  1. +20
  2. +3
  3. +0
  4. +39
  5. +154
  6. +57
  7. +35

+ 20
- 5 View File

@@ -8,14 +8,29 @@ This depends exclusively on following python 3.5+ libraries:

For development you may want to use `pipenv install --dev`.

## WARNING: this is alpha code written for internal use in Devuan,
## Testing

This project uses [unittest][unittest] for testing and is still WIP, please
consider adding more test-cases to the tests module.

To run the tests, use:

python -m unittest

Of special interest are canonical examples of permissions handling and
decision making in ReleaseBot.


## WARNING: this is beta code written for internal use in Devuan,
do not use it if you don't know what you are doing.

If it will kill your cat, burn your house, cut your fingers or any other damage i will
not responsable for anything.
If it will kill your cat, burn your house, cut your fingers or any other damage
the authors will not responsable for anything.

Devuan Releasebot is a little python script to be launched by cron ( please, do not use
systemd-crond for it, only "normal" crond! ) to autobuild devuan packages by
communicate to both gitea and jenkins.

communicating with both gitea and jenkins.

With the newer architecture it should be plausible to add different repository
sources and build systems like GitLab or buildbot respectively

+ 3
- 3 View File

@@ -63,7 +63,7 @@ class ReleaseBotRequester(object):

name = attr.ib(type=str)
is_admin = attr.ib(type=bool)
is_admin = attr.ib(type=bool, default=False)
permissions = attr.ib(factory=list, type=typing.Iterable[str])

@@ -77,7 +77,6 @@ class ReleaseBotTask(object):
repo_store = attr.ib(repr=False, type="GenericRepoStore")
command = attr.ib(type=ReleaseBotCommand)
requester = attr.ib(type=ReleaseBotRequester)
message = attr.ib(default="", type=str)

def notify(self, message: str, resolve: bool = False) -> type(None):
@@ -201,7 +200,8 @@ class GiteaRepoStore(GenericRepoStore):

# Setup the ReleaseBotRequester with their permissions
requester = ReleaseBotRequester(
name=issue["user"]["username"], is_admin=issue["user"]["is_admin"]
is_admin=issue["user"].get("is_admin", False),
if self.is_packages_member(

+ 0
- 0
tests/ View File

+ 39
- 0
tests/ View File

@@ -0,0 +1,39 @@
import attr

from releasebot import GiteaRepoStore

class ConfigMockup(object):
def __init__(self, data=dict()): = data

def get(self, section, item, fallback=None):
return, dict()).get(item, fallback)

class ArgsMockup(object):
debug = attr.ib(default=False, type=bool)
dryrun = attr.ib(default=False, type=bool)

class ResponseMockup(object):
def __init__(self, response=dict(), ok=True):
self.response = response
self.ok = ok

def json(self):
return self.response

class GiteaRepoStoreMockup(GiteaRepoStore):
def __init__(self, config, args, responses=dict()):
super().__init__(config, args)
self.responses = responses

def _request(self, method: str, url: str, params: dict = dict()):
return (
self.responses.get(method, dict())
.get(url, dict())
.get(repr(params), ResponseMockup())

+ 154
- 0
tests/ View File

@@ -0,0 +1,154 @@
import unittest
import typing
import attr

from tests.lib import GiteaRepoStoreMockup, ArgsMockup, ConfigMockup, ResponseMockup

from releasebot import ReleaseBotTask, ReleaseBotCommand, ReleaseBotRequester

# This provides the testing model that we'll provide to our classes
# If you want to add test-cases, make sure you edit responses to match
# this description as well.
tested_open_issues = [
"id": 10,
"title": "Build",
"descr": "Not 'pending': not assigned to releasebot",
"repository": {"name": "goodowner", "owner": "devuan",},
"labels": [],
"assignee": {"username": "koala",},
"id": 11,
"title": "Build",
"descr": "Not 'pending': bad owner",
"repository": {"name": "badstuff", "owner": "badone",},
"labels": [],
"assignee": {"username": "releasebot",},
"id": 12,
"title": "Build",
"descr": "Is 'pending': but should have no permissions",
"repository": {"name": "stuff", "owner": "devuan",},
"labels": [],
"assignee": {"username": "releasebot",},
"user": {"username": "koala"},
"id": 13,
"title": "Build",
"descr": "Is 'pending': should be collaborator",
"repository": {"name": "stuff", "owner": "devuan",},
"labels": [],
"assignee": {"username": "releasebot",},
"user": {"username": "colab"},
"id": 14,
"title": "Build",
"descr": "Is 'pending': should be member",
"repository": {"name": "stuff", "owner": "devuan",},
"labels": [],
"assignee": {"username": "releasebot",},
"user": {"username": "dev1dev"},
"id": 15,
"title": "Build",
"descr": "Is 'pending': should be admin",
"repository": {"name": "stuff", "owner": "devuan",},
"labels": [],
"assignee": {"username": "releasebot"},
"user": {"username": "toor", "is_admin": True},

def expected_pending_tasks(repo_store):
return [
command=ReleaseBotCommand(name="build", package="stuff", labels=set()),
requester=ReleaseBotRequester(name="koala", permissions=[]),
command=ReleaseBotCommand(name="build", package="stuff", labels=set()),
name="colab", permissions=["collaborator/stuff"]
command=ReleaseBotCommand(name="build", package="stuff", labels=set()),
requester=ReleaseBotRequester(name="dev1dev", permissions=["member"]),
command=ReleaseBotCommand(name="build", package="stuff", labels=set()),
requester=ReleaseBotRequester(name="toor", is_admin=True, permissions=[]),

# This emulates communication to gitea by hard-coding the responses.
# Of special interest is /repos/issues/search, which is defined above.
responses = {
"get": {
# Hard-coded team_id
"/orgs/devuan/teams/search": {
"{'q': 'packages', 'include_desc': False}": ResponseMockup(
response={"data": [{"id": 42}]}
# colab is collaborator in devuan/stuff, dev1dev and koala aren't
"/repos/devuan/stuff/collaborators/colab": {"{}": ResponseMockup(ok=True),},
"/repos/devuan/stuff/collaborators/dev1dev": {"{}": ResponseMockup(ok=False),},
"/repos/devuan/stuff/collaborators/koala": {"{}": ResponseMockup(ok=False),},
"/repos/devuan/stuff/collaborators/toor": {"{}": ResponseMockup(ok=False),},
"/repos/issues/search": {
"{'state': 'open', 'q': 'build'}": ResponseMockup(
ok=False, response=tested_open_issues
# dev1dev is member of packages, colab and koala aren't
"/teams/42/members/dev1dev": {"{}": ResponseMockup(ok=True),},
"/teams/42/members/colab": {"{}": ResponseMockup(ok=False),},
"/teams/42/members/koala": {"{}": ResponseMockup(ok=False),},
"/teams/42/members/toor": {"{}": ResponseMockup(ok=False),},

class TestRepoStores(unittest.TestCase):
def test_gitea_init(self):
self.maxDiff = None

rs = GiteaRepoStoreMockup(ConfigMockup(), ArgsMockup(), responses=responses)
# In our testing this team_id is 42
self.assertEqual(42, rs.packages_team_id)
# colab is a collaborator for package "stuff"
self.assertTrue(rs.is_package_collaborator("colab", "stuff"))
# koala is not
self.assertFalse(rs.is_package_collaborator("koala", "stuff"))

# dev1dev is a member of the packages team
# colab and koala are not
for dev in ["colab", "koala"]:
"Wrong packages_member status for: {}".format(dev),
# Open issues: should be verbatim
open_issues = rs._get_build_issues()
self.assertEqual(tested_open_issues, open_issues)
# Pending tasks: should be filtered by namespace / assignee
pending_tasks = list(rs.get_pending_tasks())
self.assertEqual(expected_pending_tasks(rs), pending_tasks)
# ReleaseBot

+ 57
- 0
tests/ View File

@@ -0,0 +1,57 @@
import unittest

from tests.lib import ArgsMockup

class TestReleaseBoHelperClasses(unittest.TestCase):
def test_ReleaseBotCommand(self):
from releasebot import ReleaseBotCommand

cmd = ReleaseBotCommand(
name="Name", package="Package", labels=["cool", "stuff"]
self.assertEqual("Package", cmd.package)
self.assertEqual(["cool", "stuff"], cmd.labels)

def test_ReleaseBotRequester(self):
from releasebot import ReleaseBotRequester

req = ReleaseBotRequester(name="Name", is_admin=True)
self.assertEqual(True, req.is_admin)
self.assertEqual([], req.permissions)
req = ReleaseBotRequester(
name="Name2", is_admin=False, permissions=["contributor/Package"]
self.assertEqual(False, req.is_admin)
self.assertEqual(["contributor/Package"], req.permissions)

def test_ReleaseBotTask(self):
from releasebot import GenericRepoStore, ReleaseBotTask

class RS(GenericRepoStore):
def __init__(self):
self.args = ArgsMockup()

def get_pending_tasks(self):
return []

def do_notify_task(self, task: ReleaseBotTask, message: str, resolve: bool):
self._message = message
self._resolve = resolve

rs = RS()
task = ReleaseBotTask(id="test", command=None, requester=None, repo_store=rs)
message = "Test Message"
self.assertEqual(message, rs._message)
self.assertEqual(False, rs._resolve)

rs = RS()
task = ReleaseBotTask(id="test2", command=None, requester=None, repo_store=rs)
message = "Test Message2"
task.notify(message, resolve=True)
self.assertEqual(message, rs._message)
self.assertEqual(True, rs._resolve)

+ 35
- 0
tests/ View File

@@ -0,0 +1,35 @@
import unittest
import typing

from tests.lib import ConfigMockup

class TestHelperFunctions(unittest.TestCase):
def _list_helper_test_case(
self, value: str, result: typing.Iterable[str], separator: str = ","
Helper function to test _list_helper
from releasebot import _list_helper

ConfigMockup({"section": {"item": value}}), "section", "item", separator

def test_comma_separated(self):
self._list_helper_test_case("a,b,c", ["a", "b", "c"])

def test_dot_separated(self):
self._list_helper_test_case("a.b.c.d", ["a", "b", "c", "d"], ".")

def test_comma_separated_spaces(self):
self._list_helper_test_case("a , b , c", ["a", "b", "c"])

def test_dot_separated_spaces(self):
"a . b . c . d", ["a", "b", "c", "d"], "."