Ejemplo n.º 1
0
    def test_get_critpath_pdc_with_components(self, session):
        """Test the components argument to get_critpath_components()."""
        session.get.return_value.status_code = 200
        session.get.return_value.json.return_value = {
            'count':
            1,
            'next':
            None,
            'previous':
            None,
            'results': [{
                'active': True,
                'critical_path': True,
                'global_component': 'gcc',
                'id': 6,
                'name': 'f26',
                'slas': [],
                'type': 'rpm'
            }]
        }

        pkgs = util.get_critpath_components('f26', 'rpm', frozenset(['gcc']))

        self.assertEqual(pkgs, ['gcc'])
        self.assertEqual(session.get.mock_calls, [
            mock.call((
                'http://domain.local/rest_api/v1/component-branches/?name=f26'
                '&fields=global_component&global_component=gcc&page_size=100&critical_path=true'
                '&active=true&type=rpm'),
                      timeout=60),
            mock.call().json()
        ])
Ejemplo n.º 2
0
 def test_get_critpath_components_pdc_error(self, session):
     """ Ensure an error is thrown in Bodhi if there is an error in PDC
     getting the critpath packages.
     """
     session.get.return_value.status_code = 500
     session.get.return_value.json.return_value = \
         {'error': 'some error'}
     try:
         util.get_critpath_components('f25')
         assert False, 'Did not raise a RuntimeError'
     except RuntimeError as error:
         actual_error = six.text_type(error)
     # We are not testing the whole error message because there is no
     # guarantee of the ordering of the GET parameters.
     assert 'Bodhi failed to get a resource from PDC' in actual_error
     assert 'The status code was "500".' in actual_error
Ejemplo n.º 3
0
 def test_get_critpath_components_not_pdc_not_rpm(self, mock_log):
     """ Ensure a warning is logged when the critpath system is not pdc
     and the type of components to search for is not rpm.
     """
     pkgs = util.get_critpath_components('f25', 'module')
     assert 'kernel' in pkgs, pkgs
     warning = ('The critpath.type of "module" does not support searching '
                'for non-RPM components')
     mock_log.warning.assert_called_once_with(warning)
Ejemplo n.º 4
0
 def test_get_critpath_components_pkgdb_success(self, mock_get_critpath):
     """ Ensure that critpath packages can be found using PkgDB.
     """
     # A subset of critpath packages
     critpath_pkgs = [
         'pth', 'xorg-x11-server-utils', 'giflib', 'basesystem'
     ]
     mock_get_critpath.return_value = {'pkgs': {'f20': critpath_pkgs}}
     pkgs = util.get_critpath_components('f20')
     assert critpath_pkgs == pkgs, pkgs
Ejemplo n.º 5
0
 def test_get_critpath_components_pdc_success(self, session):
     """ Ensure that critpath packages can be found using PDC.
     """
     pdc_url = \
         'http://domain.local/rest_api/v1/component-branches/?page_size=1'
     pdc_next_url = '{0}&page=2'.format(pdc_url)
     session.get.return_value.status_code = 200
     session.get.return_value.json.side_effect = [{
         'count':
         2,
         'next':
         pdc_next_url,
         'previous':
         None,
         'results': [{
             'active': True,
             'critical_path': True,
             'global_component': 'gcc',
             'id': 6,
             'name': 'f26',
             'slas': [],
             'type': 'rpm'
         }]
     }, {
         'count':
         2,
         'next':
         None,
         'previous':
         pdc_url,
         'results': [{
             'active': True,
             'critical_path': True,
             'global_component': 'python',
             'id': 7,
             'name': 'f26',
             'slas': [],
             'type': 'rpm'
         }]
     }]
     pkgs = util.get_critpath_components('f26')
     assert 'python' in pkgs and 'gcc' in pkgs, pkgs
     # At least make sure it called the next url to cycle through the pages.
     # We can't verify all the calls made because the URL GET parameters
     # in the URL may have different orders based on the system/Python
     # version.
     session.get.assert_called_with(pdc_next_url, timeout=60)
     # Verify there were two GET requests made and two .json() calls
     assert session.get.call_count == 2, session.get.call_count
     assert session.get.return_value.json.call_count == 2, \
         session.get.return_value.json.call_count
Ejemplo n.º 6
0
    def test_get_critpath_components_pdc_paging_exception(self, session):
        """Ensure that an Exception is raised if components are used and the response is paged."""
        pdc_url = 'http://domain.local/rest_api/v1/component-branches/?page_size=1'
        pdc_next_url = '{0}&page=2'.format(pdc_url)
        session.get.return_value.status_code = 200
        session.get.return_value.json.side_effect = [{
            'count':
            2,
            'next':
            pdc_next_url,
            'previous':
            None,
            'results': [{
                'active': True,
                'critical_path': True,
                'global_component': 'gcc',
                'id': 6,
                'name': 'f26',
                'slas': [],
                'type': 'rpm'
            }]
        }]

        with self.assertRaises(Exception) as exc:
            util.get_critpath_components('f26', 'rpm', frozenset(['gcc']))

        self.assertEqual(str(exc.exception),
                         'We got paging when requesting a single component?!')
        self.assertEqual(session.get.mock_calls, [
            mock.call((
                'http://domain.local/rest_api/v1/component-branches/?name=f26'
                '&fields=global_component&global_component=gcc&page_size=100&critical_path=true'
                '&active=true&type=rpm'),
                      timeout=60),
            mock.call().json()
        ])
Ejemplo n.º 7
0
 def test_get_critpath_components_dummy(self):
     """ Ensure that critpath packages can be found using the hardcoded
     list.
     """
     self.assertEqual(util.get_critpath_components(), ['kernel', 'glibc'])
Ejemplo n.º 8
0
def main(releases=None):
    initialize_db(bodhi.server.config)
    db = Session()

    stats = {}  # {release: {'stat': ...}}
    feedback = 0  # total number of updates that received feedback
    karma = defaultdict(int)  # {username: # of karma submissions}
    num_updates = db.query(Update).count()

    for release in db.query(Release).all():
        if releases and release.name not in releases:
            continue
        updates = db.query(Update).filter_by(release=release)
        critpath_pkgs = get_critpath_components(release.name.lower())
        total = updates.count()
        if not total:
            continue
        print(header(release.long_name))
        stats[release.name] = {
            'num_updates':
            total,
            'num_tested':
            0,
            'num_tested_without_karma':
            0,
            'num_feedback':
            0,
            'num_anon_feedback':
            0,
            'critpath_pkgs':
            defaultdict(int),
            'num_critpath':
            0,
            'num_critpath_approved':
            0,
            'num_critpath_unapproved':
            0,
            'num_stablekarma':
            0,
            'num_testingtime':
            0,
            'critpath_without_karma':
            set(),
            'conflicted_proventesters': [],
            'critpath_positive_karma_including_proventesters': [],
            'critpath_positive_karma_negative_proventesters': [],
            'stable_with_negative_karma':
            updates.filter(
                and_(Update.status == UpdateStatus.stable,
                     Update.karma < 0)).count(),
            'bugs':
            set(),
            'karma':
            defaultdict(int),
            'deltas': [],
            'occurrences': {},
            'accumulative':
            timedelta(),
            'packages':
            defaultdict(int),
            'proventesters':
            set(),
            'proventesters_1':
            0,
            'proventesters_0':
            0,
            'proventesters_-1':
            0,
            'submitters':
            defaultdict(int),
            # for tracking number of types of karma
            '1':
            0,
            '0':
            0,
            '-1':
            0,
        }
        data = stats[release.name]

        for status in statuses:
            data['num_%s' % status] = updates.filter(
                and_(Update.status == UpdateStatus.from_string(
                    status))).count()

        for type in types:
            data['num_%s' % type] = updates.filter(
                Update.type == UpdateType.from_string(type)).count()

        for update in updates.all():
            assert update.user, update.title
            data['submitters'][update.user.name] += 1
            for build in update.builds:
                data['packages'][build.package.name] += 1
                if build.package.name in critpath_pkgs:
                    data['critpath_pkgs'][build.package.name] += 1
            for bug in update.bugs:
                data['bugs'].add(bug.bug_id)

            feedback_done = False
            testingtime_done = False
            stablekarma_done = False

            for comment in update.comments:
                if not comment.user:
                    print('Error: None comment for %s' % update.title)
                if comment.user.name == 'autoqa':
                    continue

                # Track the # of +1's, -1's, and +0's.
                if comment.user.name != 'bodhi':
                    data[str(comment.karma)] += 1

                if 'proventesters' in [g.name for g in comment.user.groups]:
                    data['proventesters'].add(comment.user.name)
                    data['proventesters_%d' % comment.karma] += 1

                if update.status == UpdateStatus.stable:
                    if not stablekarma_done:
                        if comment.text == (
                                'This update has reached the stable karma threshold and will be '
                                'pushed to the stable updates repository'):
                            data['num_stablekarma'] += 1
                            stablekarma_done = True
                        elif comment.text and comment.text.endswith(
                                'days in testing and can be pushed to stable now if the maintainer '
                                'wishes'):
                            data['num_testingtime'] += 1
                            stablekarma_done = True

                # For figuring out if an update has received feedback or not
                if not feedback_done:
                    if (not comment.user.name == 'bodhi' and comment.karma != 0
                            and not comment.anonymous):
                        data[
                            'num_feedback'] += 1  # per-release tracking of feedback
                        feedback += 1  # total number of updates that have received feedback
                        feedback_done = True  # so we don't run this for each comment

                # Tracking per-author karma & anonymous feedback
                if not comment.user.name == 'bodhi':
                    if comment.anonymous:
                        # @@: should we track anon +0 comments as "feedback"?
                        if comment.karma != 0:
                            data['num_anon_feedback'] += 1
                    else:
                        author = comment.user.name
                        data['karma'][author] += 1
                        karma[author] += 1

                if (not testingtime_done and comment.text
                        == 'This update has been pushed to testing'):
                    for othercomment in update.comments:
                        if othercomment.text == 'This update has been pushed to stable':
                            delta = othercomment.timestamp - comment.timestamp
                            data['deltas'].append(delta)
                            data['occurrences'][delta.days] = \
                                data['occurrences'].setdefault(
                                    delta.days, 0) + 1
                            data['accumulative'] += delta
                            testingtime_done = True
                            break

            if update.critpath:
                if update.critpath_approved or update.status == UpdateStatus.stable:
                    data['num_critpath_approved'] += 1
                else:
                    if update.status in (UpdateStatus.testing,
                                         UpdateStatus.pending):
                        data['num_critpath_unapproved'] += 1
                data['num_critpath'] += 1
                if update.status == UpdateStatus.stable and update.karma == 0:
                    data['critpath_without_karma'].add(update)

                # Proventester metrics
                proventester_karma = defaultdict(int)  # {username: karma}
                positive_proventesters = 0
                negative_proventesters = 0
                for comment in update.comments:
                    if 'proventesters' in [
                            g.name for g in comment.user.groups
                    ]:
                        proventester_karma[comment.user.name] += comment.karma
                for _karma in proventester_karma.values():
                    if _karma > 0:
                        positive_proventesters += 1
                    elif _karma < 0:
                        negative_proventesters += 1

                # Conflicting proventesters
                if positive_proventesters and negative_proventesters:
                    data['conflicted_proventesters'] += [short_url(update)]

                # Track updates with overall positive karma, including positive
                # karma from a proventester
                if update.karma > 0 and positive_proventesters:
                    data[
                        'critpath_positive_karma_including_proventesters'] += [
                            short_url(update)
                        ]

                # Track updates with overall positive karma, including negative
                # karma from a proventester
                if update.karma > 0 and negative_proventesters:
                    data['critpath_positive_karma_negative_proventesters'] += [
                        short_url(update)
                    ]

            if testingtime_done:
                data['num_tested'] += 1
                if not feedback_done:
                    data['num_tested_without_karma'] += 1

        data['deltas'].sort()

        print(" * %d updates" % data['num_updates'])
        print(" * %d packages updated" % (len(data['packages'])))
        for status in statuses:
            print(" * %d %s updates" % (data['num_%s' % status], status))
        for type in types:
            print(" * %d %s updates (%0.2f%%)" %
                  (data['num_%s' % type], type,
                   float(data['num_%s' % type]) / data['num_updates'] * 100))
        print(" * %d bugs resolved" % len(data['bugs']))
        print(" * %d critical path updates (%0.2f%%)" %
              (data['num_critpath'],
               float(data['num_critpath']) / data['num_updates'] * 100))
        print(" * %d approved critical path updates" %
              (data['num_critpath_approved']))
        print(" * %d unapproved critical path updates" %
              (data['num_critpath_unapproved']))
        print(" * %d updates received feedback (%0.2f%%)" %
              (data['num_feedback'],
               (float(data['num_feedback']) / data['num_updates'] * 100)))
        print(" * %d +0 comments" % data['0'])
        print(" * %d +1 comments" % data['1'])
        print(" * %d -1 comments" % data['-1'])
        print(" * %d unique authenticated karma submitters" %
              (len(data['karma'])))
        print(" * %d proventesters" % len(data['proventesters']))
        print("   * %d +1's from proventesters" % data['proventesters_1'])
        print("   * %d -1's from proventesters" % data['proventesters_-1'])
        if data['num_critpath']:
            print(
                " * %d critpath updates with conflicting proventesters (%0.2f%% of critpath)"
                % (len(data['conflicted_proventesters']),
                   float(len(data['conflicted_proventesters'])) /
                   data['num_critpath'] * 100))
            for u in sorted(data['conflicted_proventesters']):
                print('   <li><a href="%s">%s</a></li>' %
                      (u, u.split('/')[-1]))
            msg = (
                " * %d critpath updates with positive karma and negative proventester feedback "
                "(%0.2f%% of critpath)")
            print(
                msg %
                (len(data['critpath_positive_karma_negative_proventesters']),
                 float(
                     len(data['critpath_positive_karma_negative_proventesters']
                         )) / data['num_critpath'] * 100))
            for u in sorted(
                    data['critpath_positive_karma_negative_proventesters']):
                print('   <li><a href="%s">%s</a></li>' %
                      (u, u.split('/')[-1]))
            msg = (
                " * %d critpath updates with positive karma and positive proventester feedback "
                "(%0.2f%% of critpath)")
            print(msg % (
                len(data['critpath_positive_karma_including_proventesters']),
                float(
                    len(data['critpath_positive_karma_including_proventesters']
                        )) / data['num_critpath'] * 100))
        print(
            " * %d anonymous users gave feedback (%0.2f%%)" %
            (data['num_anon_feedback'], float(data['num_anon_feedback']) /
             (data['num_anon_feedback'] + sum(data['karma'].values())) * 100))
        print(
            " * %d stable updates reached the stable karma threshold (%0.2f%%)"
            % (data['num_stablekarma'],
               float(data['num_stablekarma']) / data['num_stable'] * 100))
        print(
            " * %d stable updates reached the minimum time in testing threshold (%0.2f%%)"
            % (data['num_testingtime'],
               float(data['num_testingtime']) / data['num_stable'] * 100))
        print(" * %d went from testing to stable *without* karma (%0.2f%%)" %
              (data['num_tested_without_karma'],
               float(data['num_tested_without_karma']) / data['num_tested'] *
               100))
        print(
            " * %d updates were pushed to stable with negative karma (%0.2f%%)"
            % (data['stable_with_negative_karma'],
               float(data['stable_with_negative_karma']) / data['num_stable'] *
               100))
        print(" * %d critical path updates pushed to stable *without* karma" %
              (len(data['critpath_without_karma'])))
        print(" * Time spent in testing:")
        print("   * mean = %d days" %
              (data['accumulative'].days / len(data['deltas'])))
        print("   * median = %d days" %
              (data['deltas'][len(data['deltas']) / 2].days))
        print("   * mode = %d days" % (sorted(
            list(data['occurrences'].items()), key=itemgetter(1))[-1][0]))

        print("Out of %d packages updated, the top 50 were:" %
              (len(data['packages'])))
        for package in sorted(six.iteritems(data['packages']),
                              key=itemgetter(1),
                              reverse=True)[:50]:
            print(" * %s (%d)" % (package[0], package[1]))

        print("Out of %d update submitters, the top 50 were:" %
              (len(data['submitters'])))
        for submitter in sorted(six.iteritems(data['submitters']),
                                key=itemgetter(1),
                                reverse=True)[:50]:
            print(" * %s (%d)" % (submitter[0], submitter[1]))

        print("Out of %d critical path updates, the top 50 updated were:" %
              (len(data['critpath_pkgs'])))
        for x in sorted(six.iteritems(data['critpath_pkgs']),
                        key=itemgetter(1),
                        reverse=True)[:50]:
            print(" * %s (%d)" % (x[0], x[1]))

        critpath_not_updated = set()
        for pkg in critpath_pkgs:
            if pkg not in data['critpath_pkgs']:
                critpath_not_updated.add(pkg)
        print("Out of %d critical path packages, %d were never updated:" %
              (len(critpath_pkgs), len(critpath_not_updated)))
        for pkg in sorted(critpath_not_updated):
            print(' * %s' % pkg)

        print()

    print()
    print("Out of %d total updates, %d received feedback (%0.2f%%)" %
          (num_updates, feedback, (float(feedback) / num_updates * 100)))
    print("Out of %d total unique commenters, the top 50 were:" % (len(karma)))
    for submitter in sorted(six.iteritems(karma),
                            key=itemgetter(1),
                            reverse=True)[:50]:
        print(" * %s (%d)" % (submitter[0], submitter[1]))
Ejemplo n.º 9
0
 def test_get_critpath_components_dummy(self):
     """ Ensure that critpath packages can be found using the hardcoded
     list.
     """
     pkgs = util.get_critpath_components()
     assert 'kernel' in pkgs, pkgs