Dachary has uploaded a new change for review. https://gerrit.wikimedia.org/r/312230
Change subject: qa: refactor and add tests ...................................................................... qa: refactor and add tests Change-Id: Ie135d25cca7229b6d713fa29ba0c05439930c542 Signed-off-by: Loic Dachary <l...@dachary.org> --- M FLOSSbot/qa.py A tests/test_qa.py 2 files changed, 262 insertions(+), 47 deletions(-) git pull ssh://gerrit.wikimedia.org:29418/pywikibot/bots/FLOSSbot refs/changes/30/312230/1 diff --git a/FLOSSbot/qa.py b/FLOSSbot/qa.py index 20addd1..5ad3729 100644 --- a/FLOSSbot/qa.py +++ b/FLOSSbot/qa.py @@ -34,6 +34,18 @@ @staticmethod def get_parser(): parser = argparse.ArgumentParser(add_help=False) + select = parser.add_mutually_exclusive_group() + select.add_argument( + '--filter', + default='', + choices=['verify'], + help='filter with a pre-defined query', + ) + select.add_argument( + '--item', + default=[], + action='append', + help='work on this QID (can be repeated)') return parser @staticmethod @@ -57,60 +69,158 @@ return bot.Bot.factory(QA, argv) def run(self): - QUERY = """ - SELECT DISTINCT ?item WHERE {{ - ?item wdt:{source_code_repository} - ?repo FILTER CONTAINS(str(?repo), "github.com"). - FILTER NOT EXISTS {{ ?item p:{software_quality_assurance} ?qa }} - }} - """.format( - source_code_repository=self.P_source_code_repository, - software_quality_assurance=self.P_software_quality_assurance) - for item in pg.WikidataSPARQLPageGenerator(QUERY, + if len(self.args.item) > 0: + self.run_items() + else: + self.run_query() + + def run_items(self): + for item in self.args.item: + item = pywikibot.ItemPage(self.site, item, 0) + self.fixup(item) + self.verify(item) + + def run_query(self): + format_args = { + 'repository': self.P_source_code_repository, + 'qa': self.P_software_quality_assurance, + 'point_in_time': self.P_point_in_time, + 'delay': self.args.verification_delay, + } + if self.args.filter == 'verify': + query = """ + SELECT DISTINCT ?item WHERE {{ + ?item p:{qa} ?qa . + OPTIONAL {{ ?qa pq:{point_in_time} ?pit }} + FILTER (!BOUND(?pit) || + ?pit < (now() - "P{delay}D"^^xsd:duration)) + }} ORDER BY ?item + """.format(**format_args) + else: + query = """ + SELECT DISTINCT ?item WHERE {{ + ?item p:{repository} ?repository. + FILTER NOT EXISTS {{ ?item p:{qa} ?qa }} + }} ORDER BY ?item + """.format(**format_args) + log.debug(query) + for item in pg.WikidataSPARQLPageGenerator(query, site=self.site, result_type=list): self.fixup(item) + self.verify(item) - def fixup(self, item): - log.debug(str(item)) + def verify(self, item): item_dict = item.get() clm_dict = item_dict["claims"] + status = [] + for qa in clm_dict.get(self.P_software_quality_assurance, []): + if not self.need_verification(qa): + status.append('no need') + continue + if self.Q_Continuous_integration != qa.getTarget(): + status.append('not ci') + continue + repositories = clm_dict.get(self.P_source_code_repository, []) + if len(repositories) == 0: + self.error(item, "has no source code repository") + status.append('no repository') + continue + found = self.extract_ci(item, repositories) + if not found: + self.error(item, "no CI found") + status.append('no ci found') + continue + ok = True + for (qualifier, target) in found.items(): + name = pywikibot.PropertyPage(self.site, qualifier) + name.get() + name = name.labels['en'] + if qualifier not in qa.qualifiers: + msg = "missing qualifier " + name + self.error(item, msg) + status.append(msg) + ok = False + continue + existing = qa.qualifiers[qualifier][0].getTarget() + if existing != target: + self.error(item, name + " is " + existing + + " but should be " + target) + status.append('inconsistent qualifier ' + name) + ok = False + continue + if ok: + self.set_point_in_time(item, qa) + status.append('verified') + return sorted(status) + + def extract_ci(self, item, repositories): + found = None + repository2found = {} + for repository in repositories: + found = self.github2travis(item, repository) + if found: + repository2found[repository] = found + for (repository, found) in repository2found.items(): + if repository.getRank() == 'preferred': + return found + if repository2found: + return sorted(repository2found.items(), + key=lambda t: t[0].getTarget())[0][1] + else: + return None + + def get(self, *args, **kwargs): + return requests.get(*args, **kwargs) + + def github2travis(self, item, repository): + url = repository.getTarget() + if not url or 'github.com' not in url: + return None headers = {'User-Agent': 'FLOSSbot'} - for url in [claim.getTarget() for claim in - clm_dict[self.P_source_code_repository]]: - if 'github.com' not in url: - continue - path = os.path.normpath(urlparse(url).path)[1:] - if len(path.split("/", -1)) != 2: - log.debug("SKIP: GET " + url + - " path does not have exactly two elements") - continue - r = requests.get(url, headers=headers) - if r.status_code != requests.codes.ok: - log.debug("ERROR: GET " + url + " failed") - continue - travis = url + "/blob/master/.travis.yml" - r = requests.get(travis, headers=headers) - if r.status_code != requests.codes.ok: - log.debug("SKIP: GET " + travis + " not found") - continue - travis_ci = "https://travis-ci.org/" + path - r = requests.get(travis_ci, headers=headers) - if r.status_code != requests.codes.ok: - log.debug("SKIP: GET " + travis_ci + " not found") - continue - log.info("FOUND " + travis + " and " + travis_ci) + path = os.path.normpath(urlparse(url).path)[1:] + if len(path.split("/", -1)) != 2: + self.debug(item, "SKIP: GET " + url + + " path does not have exactly two elements") + return None + r = self.get(url, headers=headers) + if r.status_code != requests.codes.ok: + self.debug(item, "ERROR: GET " + url + " failed") + return None + travis = url + "/blob/master/.travis.yml" + r = self.get(travis, headers=headers) + if r.status_code != requests.codes.ok: + self.debug(item, "SKIP: GET " + travis + " not found") + return None + travis_ci = "https://travis-ci.org/" + path + r = self.get(travis_ci, headers=headers) + if r.status_code != requests.codes.ok: + self.debug(item, "SKIP: GET " + travis_ci + " not found") + return None + self.info(item, "FOUND " + travis + " and " + travis_ci) + return { + self.P_described_at_URL: travis, + self.P_archive_URL: travis_ci, + } - software_quality_assurance = pywikibot.Claim( - self.site, self.P_software_quality_assurance, 0) - software_quality_assurance.setTarget(self.Q_Continuous_integration) - item.addClaim(software_quality_assurance) + def fixup(self, item): + item_dict = item.get() + clm_dict = item_dict["claims"] + if self.P_software_quality_assurance in clm_dict: + return + found = self.extract_ci(item, clm_dict.get( + self.P_source_code_repository, [])) + if not found or self.args.dry_run: + return - described_at_url = pywikibot.Claim(self.site, - self.P_described_at_URL, 0) - described_at_url.setTarget(travis) - software_quality_assurance.addQualifier(described_at_url, bot=True) + software_quality_assurance = pywikibot.Claim( + self.site, self.P_software_quality_assurance, 0) + software_quality_assurance.setTarget(self.Q_Continuous_integration) + item.addClaim(software_quality_assurance) - archive_url = pywikibot.Claim(self.site, self.P_archive_URL, 0) - archive_url.setTarget(travis_ci) - software_quality_assurance.addQualifier(archive_url, bot=True) + for (qualifier, target) in found.items(): + claim = pywikibot.Claim(self.site, qualifier, 0) + claim.setTarget(target) + software_quality_assurance.addQualifier(claim, bot=True) + + self.set_point_in_time(item, software_quality_assurance) diff --git a/tests/test_qa.py b/tests/test_qa.py new file mode 100644 index 0000000..3133b51 --- /dev/null +++ b/tests/test_qa.py @@ -0,0 +1,105 @@ +# -*- mode: python; coding: utf-8 -*- +# +# Copyright (C) 2016 Loic Dachary <l...@dachary.org> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +import argparse +import logging + +import mock +import pywikibot +import requests + +from FLOSSbot.qa import QA +from tests.wikidata import WikidataHelper + +log = logging.getLogger('FLOSSbot') + + +class TestRepository(object): + + def setup_class(self): + WikidataHelper().login() + + @mock.patch('FLOSSbot.qa.QA.get') + def test_verify(self, m_get): + url2code = {} + + def get(url, **kwargs): + log.debug(url + " " + str(kwargs)) + + class c: + def __init__(self, code): + self.status_code = code + return c(url2code.get(url, requests.codes.ok)) + + m_get.side_effect = get + qa = QA(argparse.Namespace( + test=True, + user='FLOSSbotCI', + dry_run=False, + verification_delay=0, + )) + item = qa.__getattribute__('Q_' + WikidataHelper.random_name()) + + log.debug(">> do nothing if there is no source code repository") + to_verify = pywikibot.ItemPage(qa.site, item.getID(), 0) + assert [] == qa.verify(to_verify) + + log.debug(">> add a source code repository") + repository = pywikibot.Claim(qa.site, qa.P_source_code_repository, 0) + url = "http://github.com/FAKE1/FAKE2" + repository.setTarget(url) + item.addClaim(repository) + + log.debug(">> add a qa statement") + to_verify = pywikibot.ItemPage(qa.site, item.getID(), 0) + qa.fixup(to_verify) + + log.debug(">> no ci found") + to_verify = pywikibot.ItemPage(qa.site, item.getID(), 0) + url2code['https://travis-ci.org/FAKE1/FAKE2'] = 404 + assert ['no ci found'] == qa.verify(to_verify) + + log.debug(">> verified") + del url2code['https://travis-ci.org/FAKE1/FAKE2'] + assert ['verified'] == qa.verify(to_verify) + + log.debug(">> no need") + qa.args.verification_delay = 30 + assert ['no need'] == qa.verify(to_verify) + qa.args.verification_delay = 0 + + log.debug(">> inconsistent qualifier") + repository.changeTarget("http://github.com/other/other") + to_verify = pywikibot.ItemPage(qa.site, item.getID(), 0) + assert (['inconsistent qualifier archive URL', + 'inconsistent qualifier described at URL'] == + qa.verify(to_verify)) + + log.debug(">> missing qualifier") + qa_claim = to_verify.claims[qa.P_software_quality_assurance][0] + archive_URL = qa_claim.qualifiers[qa.P_archive_URL][0] + qa_claim.removeQualifier(archive_URL) + to_verify = pywikibot.ItemPage(qa.site, item.getID(), 0) + assert ['inconsistent qualifier described at URL', + 'missing qualifier archive URL'] == qa.verify(to_verify) + + qa.clear_entity_label(item.getID()) + + +# Local Variables: +# compile-command: "cd .. ; tox -e py3 tests/test_qa.py" +# End: -- To view, visit https://gerrit.wikimedia.org/r/312230 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: newchange Gerrit-Change-Id: Ie135d25cca7229b6d713fa29ba0c05439930c542 Gerrit-PatchSet: 1 Gerrit-Project: pywikibot/bots/FLOSSbot Gerrit-Branch: master Gerrit-Owner: Dachary <l...@dachary.org> _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits