Michal Hrusecky 00108750be Better testing, so far only for request reviews
To make testing more reliable and nice and generally better, we need to address
few issues. First of all, currently it is hard to figure out what was test
trying to access if accessing it fails. Second issue was that we mixed both
tests and OBS simulation logic. In perfect world, we should write some state
machine simulating OBS in some limited way and checking that scripts behaviour
corresponds to it.

This commit attempts to solve it at one example - review handling test.
2014-02-19 10:49:10 +01:00

408 lines
16 KiB
Python

#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# (C) 2014 tchvatal@suse.cz, openSUSE.org
# Distribute under GPLv2 or later
import os
import sys
import contextlib
import unittest
import httpretty
import difflib
import subprocess
import tempfile
# mock is part of python3.3
try:
import unittest.mock
except ImportError:
import mock
from string import Template
import oscs
import osc
import operator
import re
import pprint
PY3 = sys.version_info[0] == 3
if PY3:
string_types = str,
else:
string_types = basestring,
class TestApiCalls(unittest.TestCase):
"""
Tests for various api calls to ensure we return expected content
"""
responses = { 'GET': {}, 'PUT': {}, 'POST': {}, 'ALL': {} }
def _clear_responses(self):
"""
Reset predefined responses
"""
self.responses = { 'GET': {}, 'PUT': {}, 'POST': {}, 'ALL': {} }
def _pretty_callback(self, request, uri, headers):
"""
Custom callback for HTTPretty.
It mocks requests and replaces calls with either xml, content of file,
function call or first item in array of those.
:param request: request as provided to callback function by HTTPretty
:param uri: uri as provided to callback function by HTTPretty
:param headers: headers as provided to callback function by HTTPretty
"""
path = re.match( r'.*localhost([^?]*)(\?.*)?',uri).group(1)
reply = None
if self.responses['ALL'].has_key(path):
reply = self.responses['ALL'][path]
if self.responses[request.method].has_key(path):
reply = self.responses[request.method][path]
if reply:
if isinstance(reply, list):
reply = reply.pop(0)
if isinstance(reply, string_types):
return (200, headers, reply)
else:
return (200, headers, reply(self.responses, request, uri))
else:
if len(path) == 0:
path = uri
raise BaseException("No response for {0} on {1} provided".format(request.method,path))
def _register_pretty(self):
"""
Register custom callback for HTTPretty
"""
httpretty.register_uri(httpretty.GET,re.compile(r'/.*localhost.*/'),body=self._pretty_callback)
httpretty.register_uri(httpretty.PUT,re.compile(r'/.*localhost.*/'),body=self._pretty_callback)
httpretty.register_uri(httpretty.POST,re.compile(r'/.*localhost.*/'),body=self._pretty_callback)
def _get_fixtures_dir(self):
"""
Return path for fixtures
"""
return os.path.join(os.getcwd(), 'tests/fixtures')
def _get_fixture_path(self, filename):
return os.path.join(self._get_fixtures_dir(), filename)
def _get_fixture_content(self, filename):
response = open(self._get_fixture_path(filename), 'r')
content = response.read()
response.close()
return content
def _register_pretty_url_get(self, url, filename):
"""
Register specified get url with specific filename in fixtures
:param url: url address to "open"
:param filename: name of the fixtures file
"""
content = self._get_fixture_content(filename)
httpretty.register_uri(httpretty.GET,
url,
body=content)
def _register_pretty_url_post(self, url, filename):
"""
Register specified post url with specific filename in fixtures
:param url: url address to "open"
:param filename: name of the fixtures file
"""
response = open(os.path.join(self._get_fixtures_dir(), filename), 'r')
content = response.read()
response.close()
httpretty.register_uri(httpretty.POST,
url,
body=content)
def setUp(self):
"""
Initialize the configuration so the osc is happy
"""
oscrc = os.path.join(self._get_fixtures_dir(), 'oscrc')
osc.core.conf.get_config(override_conffile=oscrc,
override_no_keyring=True,
override_no_gnome_keyring=True)
os.environ['OSC_CONFIG'] = oscrc
@httpretty.activate
def test_ring_packages(self):
"""
Validate the creation of the rings.
"""
# our content in the XML files
ring_packages = {
'elem-ring-0': 'openSUSE:Factory:Rings:0-Bootstrap',
'elem-ring-1': 'openSUSE:Factory:Rings:1-MinimalX',
}
# Initiate the pretty overrides
self._register_pretty_url_get('http://localhost/source/openSUSE:Factory:Rings:0-Bootstrap',
'ring-0-project.xml')
self._register_pretty_url_get('http://localhost/source/openSUSE:Factory:Core',
'ring-1-project.xml')
# Create the api object
with mock_generate_ring_packages():
api = oscs.StagingAPI('http://localhost')
self.assertEqual(ring_packages, api.ring_packages)
@httpretty.activate
def test_dispatch_open_requests(self):
"""
Test dispatching and closure of non-ring packages
"""
# Initiate the pretty overrides
self._register_pretty_url_get('http://localhost/search/request?match=state/@name=\'review\'+and+review[@by_group=\'factory-staging\'+and+@state=\'new\']',
'open-requests.xml')
# There should be just one request that gets closed
# We don't care about the return so just reuse the above :P
# If there is bug in the function we get assertion about closing more issues than we should
self._register_pretty_url_post('http://localhost/request/220956?comment=No+need+for+staging%2C+not+in+tested+ring+project.&newstate=accepted&by_group=factory-staging&cmd=changereviewstate',
'open-requests.xml')
# Initiate the api with mocked rings
with mock_generate_ring_packages():
api = oscs.StagingAPI('http://localhost')
# get the open requests
requests = api.dispatch_open_requests()
@httpretty.activate
def test_pseudometa_get_prj(self):
"""
Test getting project metadata from YAML in project description
"""
rq = { 'id': '123', 'package': 'test-package' }
# Initiate the pretty overrides
self._register_pretty_url_get('http://localhost/source/openSUSE:Factory:Staging:test1/_meta',
'staging-project-meta.xml')
self._register_pretty_url_get('http://localhost/source/openSUSE:Factory:Staging:test2/_meta',
'staging-project-broken-meta.xml')
# Initiate the api with mocked rings
with mock_generate_ring_packages():
api = oscs.StagingAPI('http://localhost')
# Ensure the output is equal to what we expect
data = api.get_prj_pseudometa('openSUSE:Factory:Staging:test1')
for i in rq.keys():
self.assertEqual(rq[i],data['requests'][0][i])
data = api.get_prj_pseudometa('openSUSE:Factory:Staging:test2')
self.assertEqual(len(data['requests']),0)
@httpretty.activate
def test_list_projects(self):
"""
List projects and their content
"""
prjlist = [
'openSUSE:Factory:Staging:A',
'openSUSE:Factory:Staging:B',
'openSUSE:Factory:Staging:C',
'openSUSE:Factory:Staging:D'
]
# Initiate the pretty overrides
self._register_pretty_url_get('http://localhost/search/project/id?match=starts-with(@name,\'openSUSE:Factory:Staging:\')',
'staging-project-list.xml')
# Initiate the api with mocked rings
with mock_generate_ring_packages():
api = oscs.StagingAPI('http://localhost')
# Compare the results
self.assertEqual(prjlist,
api.get_staging_projects())
@httpretty.activate
def test_open_requests(self):
"""
Test searching for open requests
"""
requests = []
# Initiate the pretty overrides
self._register_pretty_url_get('http://localhost/search/request?match=state/@name=\'review\'+and+review[@by_group=\'factory-staging\'+and+@state=\'new\']',
'open-requests.xml')
# Initiate the api with mocked rings
with mock_generate_ring_packages():
api = oscs.StagingAPI('http://localhost')
# get the open requests
requests = api.get_open_requests()
count = len(requests)
# Compare the results, we only care now that we got 2 of them not the content
self.assertEqual(2, count)
@httpretty.activate
def test_get_package_information(self):
"""
Test if we get proper project, name and revision from the staging informations
"""
package_info = {'project': 'devel:wine',
'rev': '7b98ac01b8071d63a402fa99dc79331c',
'srcmd5': '7b98ac01b8071d63a402fa99dc79331c',
'package': 'wine'}
# Initiate the pretty overrides
self._register_pretty_url_get('http://localhost/source/openSUSE:Factory:Staging:B/wine',
'linksource.xml')
# Initiate the api with mocked rings
with mock_generate_ring_packages():
api = oscs.StagingAPI('http://localhost')
# Compare the results, we only care now that we got 2 of them not the content
self.assertEqual(package_info,
api.get_package_information('openSUSE:Factory:Staging:B', 'wine'))
@httpretty.activate
def test_create_package_container(self):
"""
Test if the uploaded _meta is correct
"""
with mock_generate_ring_packages():
api = oscs.StagingAPI('http://localhost')
httpretty.register_uri(
httpretty.PUT, "http://localhost/source/openSUSE:Factory:Staging:B/wine/_meta")
api.create_package_container('openSUSE:Factory:Staging:B', 'wine')
self.assertEqual(httpretty.last_request().method, 'PUT')
self.assertEqual(httpretty.last_request().body, '<package name="wine"><title/><description/></package>')
self.assertEqual(httpretty.last_request().path, '/source/openSUSE:Factory:Staging:B/wine/_meta')
api.create_package_container('openSUSE:Factory:Staging:B', 'wine', disable_build=True)
self.assertEqual(httpretty.last_request().method, 'PUT')
self.assertEqual(httpretty.last_request().body, '<package name="wine"><title /><description /><build><disable /></build></package>')
self.assertEqual(httpretty.last_request().path, '/source/openSUSE:Factory:Staging:B/wine/_meta')
@httpretty.activate
def test_review_handling(self):
"""
Test whether accepting/creating reviews behaves correctly
"""
with mock_generate_ring_packages():
api = oscs.StagingAPI('http://localhost')
tmpl = Template(self._get_fixture_content('request_review.xml'))
self._clear_responses()
self.responses['GET']['/request/123'] = tmpl.substitute(request='new', review='accepted', review_project="openSUSE:Factory")
def review_change(responses, request, uri):
if request.querystring.has_key(u'cmd') and request.querystring[u'cmd'] == [u'addreview']:
responses['GET']['/request/123'] = tmpl.substitute(request='review', review='new', review_project=request.querystring[u'by_project'][0])
if request.querystring.has_key(u'cmd') and request.querystring[u'cmd'] == [u'changereviewstate']:
responses['GET']['/request/123'] = tmpl.substitute(request='new', review=request.querystring[u'newstate'][0], review_project=request.querystring[u'by_project'][0])
return responses['GET']['/request/123']
self.responses['ALL']['/request/123'] = review_change
self._register_pretty()
# Add review
api.add_review('123', 'openSUSE:Factory:Staging:A')
self.assertEqual(httpretty.last_request().method, 'POST')
self.assertEqual(httpretty.last_request().querystring[u'cmd'], [u'addreview'])
# Try to readd, should do anything
api.add_review('123', 'openSUSE:Factory:Staging:A')
self.assertEqual(httpretty.last_request().method, 'GET')
# Accept review
api.set_review('123', 'openSUSE:Factory:Staging:A')
self.assertEqual(httpretty.last_request().method, 'POST')
self.assertEqual(httpretty.last_request().querystring[u'cmd'], [u'changereviewstate'])
# Try to accept it again should do anything
api.set_review('123', 'openSUSE:Factory:Staging:A')
self.assertEqual(httpretty.last_request().method, 'GET')
# But we should be able to reopen it
api.add_review('123', 'openSUSE:Factory:Staging:A')
self.assertEqual(httpretty.last_request().method, 'POST')
self.assertEqual(httpretty.last_request().querystring[u'cmd'], [u'addreview'])
@httpretty.activate
def test_check_project_status_green(self):
"""
Test checking project status
"""
# Initiate the pretty overrides
self._register_pretty_url_get('http://localhost/build/green/_result',
'build-results-green.xml')
# Initiate the api with mocked rings
with mock_generate_ring_packages():
api = oscs.StagingAPI('http://localhost')
# Check print output
self.assertEqual(api.gather_build_status("green"), None)
@httpretty.activate
def test_check_project_status_red(self):
"""
Test checking project status
"""
# Initiate the pretty overrides
self._register_pretty_url_get('http://localhost/build/red/_result',
'build-results-red.xml')
# Initiate the api with mocked rings
with mock_generate_ring_packages():
api = oscs.StagingAPI('http://localhost')
# Check print output
self.assertEqual(api.gather_build_status('red'), ['red', [{'path': 'standard/x86_64', 'state': 'building'}],
[{'path': 'standard/i586', 'pkg': 'glibc', 'state': 'broken'},
{'path': 'standard/i586', 'pkg': 'openSUSE-images', 'state': 'failed'}]])
def test_bootstrap_copy(self):
import osclib.freeze_command
fc = osclib.freeze_command.FreezeCommand('http://localhost')
fp = self._get_fixture_path('staging-meta-for-bootstrap-copy.xml')
fixture = subprocess.check_output('/usr/bin/xmllint --format %s' % fp, shell=True)
f = tempfile.NamedTemporaryFile(delete=False)
f.write(fc.prj_meta_for_bootstrap_copy('openSUSE:Factory:Staging:A'))
f.close()
output = subprocess.check_output('/usr/bin/xmllint --format %s' % f.name, shell=True)
for line in difflib.unified_diff(fixture.split("\n"), output.split("\n")):
print(line)
self.assertEqual(output, fixture)
# Here place all mockable functions
@contextlib.contextmanager
def mock_generate_ring_packages():
with mock.patch('oscs.StagingAPI._generate_ring_packages', return_value={
'elem-ring-0': 'openSUSE:Factory:Rings:0-Bootstrap',
'elem-ring-1': 'openSUSE:Factory:Rings:1-MinimalX'}):
yield