Add a number of integration tests for parsing real series mbox files. Signed-off-by: Stephen Finucane <step...@that.guru> Reviewed-by: Andy Doan <andy.d...@linaro.org> Tested-by: Russell Currey <rus...@russell.cc> --- v7: - Update to reference 'latest_series' property v6: - Split mbox files into a precursor patch to mitigate mailman issues - Add tests for series name parsing --- patchwork/tests/test_series.py | 408 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 408 insertions(+) create mode 100644 patchwork/tests/test_series.py
diff --git a/patchwork/tests/test_series.py b/patchwork/tests/test_series.py new file mode 100644 index 0000000..7879db9 --- /dev/null +++ b/patchwork/tests/test_series.py @@ -0,0 +1,408 @@ +# Patchwork - automated patch tracking system +# Copyright (C) 2016 Stephen Finucane <stephenfinuc...@hotmail.com> +# +# This file is part of the Patchwork package. +# +# Patchwork 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 2 of the License, or +# (at your option) any later version. +# +# Patchwork 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 Patchwork; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +import mailbox +import os + +from django.test import TestCase + +from patchwork import models +from patchwork import parser +from patchwork.tests import utils + + +TEST_SERIES_DIR = os.path.join(os.path.dirname(__file__), 'series') + + +class _BaseTestCase(TestCase): + + def setUp(self): + self.project = utils.create_project() + utils.create_state() + + def _parse_mbox(self, name, counts): + """Parse an mbox file and return the results. + + :param name: Name of mbox file + :param counts: A three-tuple of expected number of cover + letters, patches and replies parsed + """ + results = [[], [], []] + + mbox = mailbox.mbox(os.path.join(TEST_SERIES_DIR, name)) + for msg in mbox: + obj = parser.parse_mail(msg, self.project.listid) + if type(obj) == models.CoverLetter: + results[0].append(obj) + elif type(obj) == models.Patch: + results[1].append(obj) + else: + results[2].append(obj) + + self.assertParsed(results, counts) + + return results + + def assertParsed(self, results, counts): + self.assertEqual([len(x) for x in results], counts) + + def assertSerialized(self, patches, counts): + """Validate correct series-ification. + + TODO(stephen): Eventually this should ensure the series + revisions correctly linked and ordered in a series group + + :param patches: list of Patch instances + :param count: list of integers corrsponding to number of + patches per series + """ + series = models.Series.objects.all().order_by('date') + + # sanity checks + self.assertEqual(series.count(), len(counts)) + self.assertEqual(sum(counts), len(patches)) + + # walk through each series, ensuring each indexed patch + # corresponds to the correct series + start_idx = 0 + for idx, count in enumerate(counts): + end_idx = start_idx + count + + patches_ = patches[start_idx:end_idx] + for patch in patches_: + self.assertEqual(patch.latest_series, series[idx]) + + start_idx = end_idx + + +class BaseSeriesTest(_BaseTestCase): + """Tests for a series without any revisions.""" + + def test_cover_letter(self): + """Series with a cover letter. + + Parse a series with a cover letter and two patches. + + Input: + + - [PATCH 0/2] A sample series + - [PATCH 1/2] test: Add some lorem ipsum + - [PATCH 2/2] test: Convert to Markdown + """ + covers, patches, comments = self._parse_mbox( + 'base-cover-letter.mbox', [1, 2, 0]) + + self.assertSerialized(patches, [2]) + self.assertSerialized(covers, [1]) + + def test_no_cover_letter(self): + """Series without a cover letter. + + Parse a series with two patches but no cover letter. + + Input: + + - [PATCH 1/2] test: Add some lorem ipsum + - [PATCH 2/2] test: Convert to Markdown + """ + _, patches, _ = self._parse_mbox( + 'base-no-cover-letter.mbox', [0, 2, 0]) + + self.assertSerialized(patches, [2]) + + def test_out_of_order(self): + """Series received out of order. + + Parse a series with a cover letter and two patches that is + received out of order. + + Input: + + - [PATCH 2/2] test: Convert to Markdown + - [PATCH 1/2] test: Add some lorem ipsum + - [PATCH 0/2] A sample series + """ + covers, patches, comments = self._parse_mbox( + 'base-out-of-order.mbox', [1, 2, 0]) + + self.assertSerialized(patches, [2]) + self.assertSerialized(covers, [1]) + + +class RevisedSeriesTest(_BaseTestCase): + """Tests for a series plus a single revision. + + NOTE(stephenfin): In each sample mbox, it is necessary to ensure + the first series is placed before the revision. If not, they will + not be parsed correctly. This is OK as in practice it would very + unlikely to receive a revision before a previous revision. + """ + + def test_basic(self): + """Series with a simple revision. + + Parse a series with a cover letter and two patches, followed by + a second revision of the same. The second revision is correctly + labeled and is not sent in reply to the first revision. + + Input: + + - [PATCH 0/2] A sample series + - [PATCH 1/2] test: Add some lorem ipsum + - [PATCH 2/2] test: Convert to Markdown + - [PATCH v2 0/2] A sample series + - [PATCH v2 1/2] test: Add some lorem ipsum + - [PATCH v2 2/2] test: Convert to Markdown + """ + covers, patches, _ = self._parse_mbox( + 'revision-basic.mbox', [2, 4, 0]) + + self.assertSerialized(patches, [2, 2]) + self.assertSerialized(covers, [1, 1]) + + def test_threaded_to_cover(self): + """Series with a revision sent in-reply-to a cover. + + Parse a series with a cover letter and two patches, followed by + a second revision of the same. The second revision is correctly + labeled but is sent in reply to the cover letter of the first + revision. + + Input: + + - [PATCH 0/2] A sample series + - [PATCH 1/2] test: Add some lorem ipsum + - [PATCH 2/2] test: Convert to Markdown + - [PATCH v2 0/2] A sample series + - [PATCH v2 1/2] test: Add some lorem ipsum + - [PATCH v2 2/2] test: Convert to Markdown + """ + covers, patches, _ = self._parse_mbox( + 'revision-threaded-to-cover.mbox', [2, 4, 0]) + + self.assertSerialized(patches, [2, 2]) + self.assertSerialized(covers, [1, 1]) + + def test_threaded_to_patch(self): + """Series with a revision sent in-reply-to a patch. + + Parse a series with a cover letter and two patches, followed by + a second revision of the same. The second revision is correctly + labeled but is sent in reply to the second patch of the first + revision. + + Input: + + - [PATCH 0/2] A sample series + - [PATCH 1/2] test: Add some lorem ipsum + - [PATCH 2/2] test: Convert to Markdown + - [PATCH v2 0/2] A sample series + - [PATCH v2 1/2] test: Add some lorem ipsum + - [PATCH v2 2/2] test: Convert to Markdown + """ + covers, patches, _ = self._parse_mbox( + 'revision-threaded-to-patch.mbox', [2, 4, 0]) + + self.assertSerialized(patches, [2, 2]) + self.assertSerialized(covers, [1, 1]) + + def test_out_of_order(self): + """Series with a revision received out-of-order. + + Parse a series with a cover letter and two patches, followed by + a second revision of the same. The second revision is correctly + labeled but is sent in reply to the second patch and is + received out of order. + + Input: + + - [PATCH 0/2] A sample series + - [PATCH 1/2] test: Add some lorem ipsum + - [PATCH 2/2] test: Convert to Markdown + - [PATCH v2 2/2] test: Convert to Markdown + - [PATCH v2 1/2] test: Add some lorem ipsum + - [PATCH v2 0/2] A sample series + """ + covers, patches, _ = self._parse_mbox( + 'revision-out-of-order.mbox', [2, 4, 0]) + + self.assertSerialized(patches, [2, 2]) + self.assertSerialized(covers, [1, 1]) + + def test_no_cover_letter(self): + """Series with a revision sent without a cover letter. + + Parse a series with a cover letter and two patches, followed by + a second revision of the same. The second revision is not + labeled with a series version marker. + + Input: + + - [PATCH 0/2] A sample series + - [PATCH 1/2] test: Add some lorem ipsum + - [PATCH 2/2] test: Convert to Markdown + - [PATCH 1/2] test: Add some lorem ipsum + - [PATCH 2/2] test: Convert to Markdown + """ + covers, patches, _ = self._parse_mbox( + 'revision-no-cover-letter.mbox', [1, 4, 0]) + + self.assertSerialized(patches, [2, 2]) + self.assertSerialized(covers, [1, 0]) + + def test_unlabeled(self): + """Series with a revision sent without a version label. + + Parse a series with a cover letter and two patches, followed by + a second revision of the same. The second revision is not + labeled with a series version marker. + + Input: + + - [PATCH 0/2] A sample series + - [PATCH 1/2] test: Add some lorem ipsum + - [PATCH 2/2] test: Convert to Markdown + - [PATCH 0/2] A sample series + - [PATCH 1/2] test: Add some lorem ipsum + - [PATCH 2/2] test: Convert to Markdown + """ + covers, patches, _ = self._parse_mbox( + 'revision-unlabeled.mbox', [2, 4, 0]) + + self.assertSerialized(patches, [2, 2]) + self.assertSerialized(covers, [1, 1]) + + +class SeriesNameTestCase(TestCase): + + def setUp(self): + self.project = utils.create_project() + utils.create_state() + + @staticmethod + def _get_mbox(name): + """Open an mbox file. + + :param name: Name of mbox file + """ + return mailbox.mbox(os.path.join(TEST_SERIES_DIR, name)) + + def _parse_mail(self, mail): + return parser.parse_mail(mail, self.project.listid) + + @staticmethod + def _format_name(cover): + return cover.name.split(']')[-1] + + def test_cover_letter(self): + """Cover letter name set as series name. + + Parse a series with a cover letter and two patches and ensure + the series name is set to the cover letter. + + Input: + + - [PATCH 0/2] A sample series + - [PATCH 1/2] test: Add some lorem ipsum + - [PATCH 2/2] test: Convert to Markdown + """ + mbox = self._get_mbox('base-cover-letter.mbox') + + cover = self._parse_mail(mbox[0]) + cover_name = self._format_name(cover) + self.assertEqual(cover.latest_series.name, cover_name) + + self._parse_mail(mbox[1]) + self.assertEqual(cover.latest_series.name, cover_name) + + self._parse_mail(mbox[2]) + self.assertEqual(cover.latest_series.name, cover_name) + + def test_no_cover_letter(self): + """Series without a cover letter. + + Parse a series with two patches but no cover letter and ensure + the series name is set to the first patch's entire subject. + + Input: + + - [PATCH 1/2] test: Add some lorem ipsum + - [PATCH 2/2] test: Convert to Markdown + """ + mbox = self._get_mbox('base-no-cover-letter.mbox') + + patch = self._parse_mail(mbox[0]) + series = patch.latest_series + self.assertEqual(series.name, patch.name) + + self._parse_mail(mbox[1]) + self.assertEqual(series.name, patch.name) + + def test_out_of_order(self): + """Series received out of order. + + Parse a series with a cover letter and two patches that is + received out of order. Ensure the name is updated as new + patches are received. + + Input: + + - [PATCH 2/2] test: Convert to Markdown + - [PATCH 1/2] test: Add some lorem ipsum + - [PATCH 0/2] A sample series + """ + mbox = self._get_mbox('base-out-of-order.mbox') + + patch = self._parse_mail(mbox[0]) + self.assertIsNone(patch.latest_series.name) + + patch = self._parse_mail(mbox[1]) + self.assertEqual(patch.latest_series.name, patch.name) + + cover = self._parse_mail(mbox[2]) + self.assertEqual(cover.latest_series.name, self._format_name(cover)) + + def test_custom_name(self): + """Series with custom name. + + Parse a series with a cover letter and two patches that is + recevied out of order. Ensure a custom name set on the series + is not overriden by subsequent patches received. + + Input: + + - [PATCH 2/2] test: Convert to Markdown + - [PATCH 1/2] test: Add some lorem ipsum + - [PATCH 0/2] A sample series + """ + mbox = self._get_mbox('base-out-of-order.mbox') + + series = self._parse_mail(mbox[0]).latest_series + self.assertIsNone(series.name) + + series_name = 'My custom series name' + series.name = series_name + series.save() + self.assertEqual(series.name, series_name) + + self._parse_mail(mbox[1]) + self.assertEqual(series.name, series_name) + + self._parse_mail(mbox[2]) + self.assertEqual(series.name, series_name) -- 2.7.4 _______________________________________________ Patchwork mailing list Patchwork@lists.ozlabs.org https://lists.ozlabs.org/listinfo/patchwork