diff mbox series

[v4,1/1] management: introduce replacerelations command

Message ID 20200720104223.6957-2-rohitsarkar5398@gmail.com
State New
Headers show
Series add replacerelations management command | expand

Commit Message

Rohit Sarkar July 20, 2020, 10:42 a.m. UTC
The replacerelations script is used to ingest relations into Patchwork's
patch database. A patch groups file is taken as input, which on each
line contains a space separated list of patchwork ids denoting a
relation. All the existing relations in Patchwork's database are removed
and the relations read from the patch groups file are ingested.

Signed-off-by: Rohit Sarkar <rohitsarkar5398@gmail.com>
---
 docs/deployment/management.rst                | 28 ++++++++
 .../management/commands/replacerelations.py   | 72 +++++++++++++++++++
 patchwork/tests/__init__.py                   |  1 +
 patchwork/tests/relations/patch-groups        |  3 +
 .../relations/patch-groups-missing-patch-ids  |  4 ++
 patchwork/tests/test_management.py            | 27 ++++++-
 6 files changed, 134 insertions(+), 1 deletion(-)
 create mode 100644 patchwork/management/commands/replacerelations.py
 create mode 100644 patchwork/tests/relations/patch-groups
 create mode 100644 patchwork/tests/relations/patch-groups-missing-patch-ids

Comments

Daniel Axtens July 23, 2020, 12:52 a.m. UTC | #1
Hi Rohit,

Some more things:

1) `docker-compose run web --tox` fails, both pep8 and in some of the
substantive tests:

Creating test database for alias 'default'...
System check identified no issues (0 silenced).
.............................................................................................................................................................................................................................................................................Invalid path: xyz123random
.F..........................................................................................................................................................................................................................................s................................................................................
======================================================================
FAIL: test_valid_relations (patchwork.tests.test_management.ReplacerelationsTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/patchwork/patchwork/patchwork/tests/test_management.py", line 143, in test_valid_relations
    self.assertEqual(models.PatchRelation.objects.count(), 2)
AssertionError: 0 != 2

----------------------------------------------------------------------

(Please could you also silence the 'invalid path' print during tests?)

2) There's an line at the end of test_management.py.

3) I reformatted the docs to wrap at 80 columns and I made some minor
changes to the wording and punctuation in the process. Please wrap
future changes at 80 columns. I also found that the patch groups file
doesn't render as separate lines in a web browser, it renders as

"1 3 5 2 7 10 11 12"

I've fixed this.

You can render the docs to html locally with

docker-compose run web --tox -e docs

Here's my proposed docs:

diff --git a/docs/deployment/management.rst b/docs/deployment/management.rst
index 9c57f1962283..7bff699329fb 100644
--- a/docs/deployment/management.rst
+++ b/docs/deployment/management.rst
@@ -116,6 +116,42 @@ the :ref:`deployment installation guide <deployment-parsemail>`.
 
    input mbox filename. If not supplied, a patch will be read from ``stdin``.
 
+replacerelations
+~~~~~~~~~~~~~~~~
+
+.. program:: manage.py replacerelations
+
+Parse a patch groups file and store any relation found
+
+.. code-block:: shell
+
+    ./manage.py replacerelations <infile>
+
+This is a script used to ingest relations into Patchwork.
+
+A patch groups file contains on each line a list of space separated patch IDs
+of patches that form a relation.
+
+For example, consider the contents of a sample patch groups file::
+
+    1 3 5
+    2
+    7 10 11 12
+In this case the script will identify 2 relations, (1, 3, 5) and
+(7, 10, 11, 12). The single patch ID "2" on the second line is ignored as a
+relation always consists of more than 1 patch.
+
+Further, if a patch ID in the patch groups file does not exist in the database
+of the Patchwork instance, that patch ID will be silently ignored while forming
+the relations.
+
+Running this script will remove all existing relations and replace them with
+the relations found in the file.
+
+.. option:: infile
+
+    input patch groups file.
+
 rehash
 ~~~~~~
 
Kind regards,
Daniel
Daniel Axtens July 23, 2020, 1 a.m. UTC | #2
> 1) `docker-compose run web --tox` fails, both pep8 and in some of the
> substantive tests:
>
> Creating test database for alias 'default'...
> System check identified no issues (0 silenced).
> .............................................................................................................................................................................................................................................................................Invalid path: xyz123random
> .F..........................................................................................................................................................................................................................................s................................................................................

I think this might be because by the time you get to your test, a number
of patches have been created and destroyed, so the patches are no longer
numbered the way you expect. I didn't pick this up before because last
time I ran the tests there was only the one with the invalid path.

> 3) I reformatted the docs to wrap at 80 columns and I made some minor
> changes to the wording and punctuation in the process. Please wrap
> future changes at 80 columns. I also found that the patch groups file
> doesn't render as separate lines in a web browser, it renders as

I screwed up the suggested docs, try this one instead.

Kind regards,
Daniel

diff --git a/docs/deployment/management.rst b/docs/deployment/management.rst
index 9c57f1962283..dcee9ff518f6 100644
--- a/docs/deployment/management.rst
+++ b/docs/deployment/management.rst
@@ -116,6 +116,43 @@ the :ref:`deployment installation guide <deployment-parsemail>`.
 
    input mbox filename. If not supplied, a patch will be read from ``stdin``.
 
+replacerelations
+~~~~~~~~~~~~~~~~
+
+.. program:: manage.py replacerelations
+
+Parse a patch groups file and store any relation found
+
+.. code-block:: shell
+
+    ./manage.py replacerelations <infile>
+
+This is a script used to ingest relations into Patchwork.
+
+A patch groups file contains on each line a list of space separated patch IDs
+of patches that form a relation.
+
+For example, consider the contents of a sample patch groups file::
+
+    1 3 5
+    2
+    7 10 11 12
+
+In this case the script will identify 2 relations, (1, 3, 5) and
+(7, 10, 11, 12). The single patch ID "2" on the second line is ignored as a
+relation always consists of more than 1 patch.
+
+Further, if a patch ID in the patch groups file does not exist in the database
+of the Patchwork instance, that patch ID will be silently ignored while forming
+the relations.
+
+Running this script will remove all existing relations and replace them with
+the relations found in the file.
+
+.. option:: infile
+
+    input patch groups file.
+
 rehash
 ~~~~~~
Rohit Sarkar July 25, 2020, 4:24 a.m. UTC | #3
Hey,
On Thu, Jul 23, 2020 at 11:00:08AM +1000, Daniel Axtens wrote:
> > 1) `docker-compose run web --tox` fails, both pep8 and in some of the
> > substantive tests:
> >
> > Creating test database for alias 'default'...
> > System check identified no issues (0 silenced).
> > .............................................................................................................................................................................................................................................................................Invalid path: xyz123random
> > .F..........................................................................................................................................................................................................................................s................................................................................
> 
> I think this might be because by the time you get to your test, a number
> of patches have been created and destroyed, so the patches are no longer
> numbered the way you expect. I didn't pick this up before because last
> time I ran the tests there was only the one with the invalid path.

Yeah, I missed this as I wasnt running the entire suite but just the
individual test. Was wondering if there is any best practice for this
for writing tests that depend on the ids in the database?

Thanks,
Rohit
Daniel Axtens July 27, 2020, 12:33 a.m. UTC | #4
Rohit Sarkar <rohitsarkar5398@gmail.com> writes:

> Hey,
> On Thu, Jul 23, 2020 at 11:00:08AM +1000, Daniel Axtens wrote:
>> > 1) `docker-compose run web --tox` fails, both pep8 and in some of the
>> > substantive tests:
>> >
>> > Creating test database for alias 'default'...
>> > System check identified no issues (0 silenced).
>> > .............................................................................................................................................................................................................................................................................Invalid path: xyz123random
>> > .F..........................................................................................................................................................................................................................................s................................................................................
>> 
>> I think this might be because by the time you get to your test, a number
>> of patches have been created and destroyed, so the patches are no longer
>> numbered the way you expect. I didn't pick this up before because last
>> time I ran the tests there was only the one with the invalid path.
>
> Yeah, I missed this as I wasnt running the entire suite but just the
> individual test. Was wondering if there is any best practice for this
> for writing tests that depend on the ids in the database?

Not sure what best practice would be. I think in your case, you'll need
to create your patches, extract their IDs, and then write those into a
temporary file (see the tempfile module).

Kind regards,
Daniel

>
> Thanks,
> Rohit
Rohit Sarkar Aug. 1, 2020, 12:19 p.m. UTC | #5
Hey,

Thanks for all the patient feedback Daniel. Have sent out a v5
addressing these issues.

Thanks,
Rohit
diff mbox series

Patch

diff --git a/docs/deployment/management.rst b/docs/deployment/management.rst
index 9c57f19..1437550 100644
--- a/docs/deployment/management.rst
+++ b/docs/deployment/management.rst
@@ -116,6 +116,34 @@  the :ref:`deployment installation guide <deployment-parsemail>`.
 
    input mbox filename. If not supplied, a patch will be read from ``stdin``.
 
+replacerelations
+~~~~~~~~~~~~~~~~
+
+.. program:: manage.py replacerelations
+
+Parse a patch groups file and store any relation found
+
+.. code-block:: shell
+
+    ./manage.py replacerelations <infile>
+
+This is a script used to ingest relations into Patchwork.
+A patch groups file contains on each line patch ids of patches that form a relation.
+For example, consider the contents of a sample patch groups file:
+
+    1 3 5
+    2
+    7 10 11 12
+
+In this case the script will identify 2 relations, (1, 3, 5) and (7, 10, 11, 12).
+The single patch id "2" on the second line is ignored as a relation always consists of more than 1 patches.
+Further if a patch id in the patch groups file does not exist in the database of the Patchwork instance, that patch id will be silently ignored while forming the relations
+Running this script will remove all existing relations and replace them with the relations found in the file.
+
+.. option:: infile
+
+    input patch groups file.
+
 rehash
 ~~~~~~
 
diff --git a/patchwork/management/commands/replacerelations.py b/patchwork/management/commands/replacerelations.py
new file mode 100644
index 0000000..0cc4d0a
--- /dev/null
+++ b/patchwork/management/commands/replacerelations.py
@@ -0,0 +1,72 @@ 
+# Patchwork - automated patch tracking system
+# Copyright (C) 2020 Rohit Sarkar <rohitsarkar5398@gmail.com>
+#
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+import logging
+import os
+import sys
+
+from django.db import transaction
+from django.core.management.base import BaseCommand
+
+from patchwork.models import Patch
+from patchwork.models import PatchRelation
+
+logger = logging.getLogger(__name__)
+
+class Command(BaseCommand):
+    help = 'Parse a relations file generated by PaStA and replace existing relations with the ones parsed'
+
+    def add_arguments(self, parser):
+        parser.add_argument(
+            'infile',
+            help='input relations filename')
+
+    def handle(self, *args, **options):
+        verbosity = int(options['verbosity'])
+        if not verbosity:
+            level = logging.CRITICAL
+        elif verbosity == 1:
+            level = logging.ERROR
+        elif verbosity == 2:
+            level = logging.INFO
+        else:
+            level = logging.DEBUG
+
+        logger.setLevel(level)
+
+        path = args and args[0] or options['infile']
+        if not os.path.exists(path):
+            logger.error('Invalid path: %s', path)
+            sys.exit(1)
+
+
+        with open(path, 'r') as f:
+            lines = f.readlines()
+
+        # filter out trailing empty lines
+        while len(lines) and not lines[-1]:
+            lines.pop()
+
+        relations = [line.split(' ') for line in lines]
+
+        with transaction.atomic():
+            PatchRelation.objects.all().delete()
+            count = len(relations)
+            ingested = 0
+            logger.info('Parsing %d relations' % count)
+            for i, patch_ids in enumerate(relations):
+                related_patches = Patch.objects.filter(id__in=patch_ids)
+
+                if len(related_patches) > 1:
+                    relation = PatchRelation()
+                    relation.save()
+                    related_patches.update(related=relation)
+                    ingested += 1
+
+                if i % 10 == 0:
+                    self.stdout.write('%06d/%06d\r' % (i, count), ending='')
+                    self.stdout.flush()
+
+            self.stdout.write('Ingested %d relations' % ingested)
diff --git a/patchwork/tests/__init__.py b/patchwork/tests/__init__.py
index 8f78ea7..83358a6 100644
--- a/patchwork/tests/__init__.py
+++ b/patchwork/tests/__init__.py
@@ -8,3 +8,4 @@  import os
 TEST_MAIL_DIR = os.path.join(os.path.dirname(__file__), 'mail')
 TEST_PATCH_DIR = os.path.join(os.path.dirname(__file__), 'patches')
 TEST_FUZZ_DIR = os.path.join(os.path.dirname(__file__), 'fuzztests')
+TEST_RELATIONS_DIR = os.path.join(os.path.dirname(__file__), 'relations')
diff --git a/patchwork/tests/relations/patch-groups b/patchwork/tests/relations/patch-groups
new file mode 100644
index 0000000..593cf0f
--- /dev/null
+++ b/patchwork/tests/relations/patch-groups
@@ -0,0 +1,3 @@ 
+1 2
+3 4 5
+6
diff --git a/patchwork/tests/relations/patch-groups-missing-patch-ids b/patchwork/tests/relations/patch-groups-missing-patch-ids
new file mode 100644
index 0000000..091b195
--- /dev/null
+++ b/patchwork/tests/relations/patch-groups-missing-patch-ids
@@ -0,0 +1,4 @@ 
+1 2
+3 4 5 7
+6 8
+9 10
diff --git a/patchwork/tests/test_management.py b/patchwork/tests/test_management.py
index 66c6bad..905369d 100644
--- a/patchwork/tests/test_management.py
+++ b/patchwork/tests/test_management.py
@@ -11,7 +11,7 @@  from django.core.management import call_command
 from django.test import TestCase
 
 from patchwork import models
-from patchwork.tests import TEST_MAIL_DIR
+from patchwork.tests import TEST_MAIL_DIR, TEST_RELATIONS_DIR
 from patchwork.tests import utils
 
 
@@ -124,3 +124,28 @@  class ParsearchiveTest(TestCase):
 
         self.assertIn('Processed 1 messages -->', out.getvalue())
         self.assertIn('  1 dropped', out.getvalue())
+
+class ReplacerelationsTest(TestCase):
+    def test_invalid_path(self):
+        out = StringIO()
+        with self.assertRaises(SystemExit) as exc:
+            call_command('replacerelations', 'xyz123random', stdout=out)
+        self.assertEqual(exc.exception.code, 1)
+
+    def test_valid_relations(self):
+        utils.create_patches(6)
+        out = StringIO()
+        call_command('replacerelations',
+                     os.path.join(TEST_RELATIONS_DIR,
+                                  'patch-groups'),
+                                  stdout=out)
+
+        self.assertEqual(models.PatchRelation.objects.count(), 2)
+
+        call_command('replacerelations',
+                     os.path.join(TEST_RELATIONS_DIR,
+                                  'patch-groups-missing-patch-ids'),
+                                  stdout=out)
+
+        self.assertEqual(models.PatchRelation.objects.count(), 2)
+