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

Reply via email to