Skip to content

Commit

Permalink
Optionally report failures in a JUnit-compatible XML.
Browse files Browse the repository at this point in the history
Implements part of #2, allowing better monitoring of devpi-builder invocations on Jenkins.
  • Loading branch information
matthias-bach-by committed Aug 15, 2014
1 parent 21c2293 commit bf557ab
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 12 deletions.
48 changes: 37 additions & 11 deletions devpi_builder/cli.py
Expand Up @@ -7,6 +7,8 @@
import argparse
import logging

from junit_xml import TestSuite, TestCase

from devpi_builder import requirements, wheeler, devpi

logging.basicConfig(level=logging.INFO)
Expand All @@ -15,25 +17,39 @@

class Processor(object):

def __init__(self, builder, devpi_client, blacklist, pure_index_client=None):
def __init__(self, builder, devpi_client, blacklist, pure_index_client=None, junit_xml=None):
self._builder = builder
self._devpi_client = devpi_client
self._blacklist = blacklist
self._pure_index_client = pure_index_client
self._junit_xml = junit_xml

def _log_skip(self, text, package, version):
logger.debug(text, package, version)

def _log_fail(self, exception, package, version):
logger.exception(exception)

log_entry = TestCase('{} {}'.format(package, version))
log_entry.add_failure_info(exception.message)
self._results.append(log_entry)

def should_package_be_build(self, package, version):
def _log_success(self, package, version):
pass

def _should_package_be_build(self, package, version):
if self._devpi_client.package_version_exists(package, version):
logger.debug('Skipping %s %s as is already available on the index.', package, version)
self._log_skip('Skipping %s %s as is already available on the index.', package, version)
return False
elif self._pure_index_client and self._pure_index_client.package_version_exists(package, version):
logger.debug('Skipping %s %s as is already available on the pure index.', package, version)
self._log_skip('Skipping %s %s as is already available on the pure index.', package, version)
return False
elif self._blacklist and requirements.matched_by_file(package, version, self._blacklist):
logger.info('Skipping %s %s as it is matched by the blacklist.', package, version)
self._log_skip('Skipping %s %s as it is matched by the blacklist.', package, version)
return False
return True

def upload_package(self, package, version, wheel_file):
def _upload_package(self, package, version, wheel_file):
if self._pure_index_client and wheeler.is_pure(wheel_file):
logger.debug('Uploading %s %s to pure index %s', package, version, self._pure_index_client.index_url)
self._pure_index_client.upload(wheel_file)
Expand All @@ -42,14 +58,23 @@ def upload_package(self, package, version, wheel_file):
self._devpi_client.upload(wheel_file)

def build_packages(self, packages):
self._results = []

for package, version in packages:
if self.should_package_be_build(package, version):
if self._should_package_be_build(package, version):
logger.info('Building %s %s', package, version)
try:
wheel_file = self._builder(package, version)
self.upload_package(package, version, wheel_file)
self._upload_package(package, version, wheel_file)
self._log_success(package, version)
except wheeler.BuildError as e:
logger.exception(e)
self._log_fail(e, package, version)

if self._junit_xml:
with open(self._junit_xml, 'w') as output:
test_suite = TestSuite('devpi-builder results', self._results)
TestSuite.to_file(output, [test_suite])



def main(args=None):
Expand All @@ -63,15 +88,16 @@ def main(args=None):
'to the index given as positional argument. Packages already found in the'
'pure index will not be built, either.'
)
parser.add_argument('--junit-xml', help='Write information about the build success / failure to a JUnit-compatible XML file.')

args = parser.parse_args(args=args)

packages = requirements.read(args.requirements)
with wheeler.Builder() as builder, devpi.Client(args.index, args.user, args.password) as devpi_client:
if args.pure_index:
with devpi.Client(args.pure_index, args.user, args.password) as pure_index_client:
processor = Processor(builder, devpi_client, args.blacklist, pure_index_client)
processor = Processor(builder, devpi_client, args.blacklist, pure_index_client, junit_xml=args.junit_xml)
processor.build_packages(packages)
else:
processor = Processor(builder, devpi_client, args.blacklist)
processor = Processor(builder, devpi_client, args.blacklist, junit_xml=args.junit_xml)
processor.build_packages(packages)
3 changes: 3 additions & 0 deletions tests/fixture/sample_junit.txt
@@ -0,0 +1,3 @@
package_that_hopefully_not_exists==99.999
progressbar==2.2
test-package==0.1-dev
24 changes: 23 additions & 1 deletion tests/test_cli.py
@@ -1,6 +1,9 @@
# coding=utf-8

import os.path
import shutil
import tempfile
import unittest
import xml.etree.ElementTree as ET

from mock import patch

Expand Down Expand Up @@ -110,5 +113,24 @@ def test_fills_proper_index(self):
self.assertFalse(client.package_version_exists('progressbar', '2.2'))
self.assertTrue(client.package_version_exists('PyYAML', '3.10'))

def test_reports_junit_xml(self):
user = 'test'
with devpi_server() as server_url, devpi_index(server_url, user, 'wheels') as (destination_index, password):
with devpi.Client(destination_index, user, password) as client:
client.upload('tests/fixture/pure_package/dist/test_package-0.1_dev-py2.py3-none-any.whl')

tempdir = tempfile.mkdtemp()
try:
junit_filename = os.path.join(tempdir, 'junit.xml')
main(['tests/fixture/sample_junit.txt', destination_index, user, password, '--junit-xml', junit_filename])

root = ET.parse(junit_filename)

failed_nodes = root.findall('.//testcase/failure/..')
self.assertEqual(1, len(failed_nodes))
self.assertEqual(failed_nodes[0].attrib['name'], 'package-that-hopefully-not-exists 99.999')
finally:
shutil.rmtree(tempdir)

if __name__ == '__main__':
unittest.main()

0 comments on commit bf557ab

Please sign in to comment.