docs: store the release-calendar information in csv (and fix tests)
authorDylan Baker <dylan.c.baker@intel.com>
Tue, 5 Jan 2021 00:39:27 +0000 (16:39 -0800)
committerMarge Bot <eric+marge@anholt.net>
Thu, 7 Jan 2021 00:07:56 +0000 (00:07 +0000)
Restructured text (and markdown) is painful to programatically
manipulate, most python parsers are geared towards writing markdown and
generating html. I'd like to move the calendar updates to being
scripted, as such using csv to store them will be convenient. This also
allows us to simplify our scripting that manipulates the table
considerably.

Acked-by: Eric Engestrom <eric@engestrom.ch>
Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/8341>

bin/post_version.py
bin/post_version_test.py
docs/release-calendar.csv [new file with mode: 0644]
docs/release-calendar.rst
docs/releasing.rst

index 359cbd9..c4779f1 100755 (executable)
@@ -22,6 +22,7 @@
 """Update the main page, release notes, and calendar."""
 
 import argparse
+import csv
 import pathlib
 import subprocess
 
@@ -52,30 +53,24 @@ def update_release_notes(version: str) -> None:
 
 
 def update_calendar(version: str) -> None:
-    p = pathlib.Path('docs') / 'release-calendar.rst'
-
-    with open(p, 'r') as f:
-        calendar = f.readlines()
-
-    branch = ''
-    skip_line = False
-    new_calendar = []
-    for line in calendar:
-        if version in line:
-            branch = line.split('|')[1].strip()
-            skip_line = True
-        elif skip_line:
-            skip_line = False
-        elif branch:
-            # Put the branch number back on the next line
-            new_calendar.append(line[:2] + branch + line[len(branch) + 2:])
-            branch = ''
-        else:
-            new_calendar.append(line)
-
-    with open(p, 'w') as f:
-        for line in new_calendar:
-            f.write(line)
+    p = pathlib.Path('docs') / 'release-calendar.csv'
+
+    with p.open('r') as f:
+        calendar = csv.reader(f)
+
+    branch = None
+    for i, line in enumerate(calendar):
+        if line[2] == version:
+            if line[0]:
+                branch = line[0]
+            break
+    if branch is not None:
+        calendar[i + 1][0] = branch
+    del calendar[i]
+
+    with p.open('w') as f:
+        writer = csv.writer(f)
+        writer.writerows(calendar)
 
     subprocess.run(['git', 'add', p])
 
index d0794a6..aa008bc 100644 (file)
 # SOFTWARE.
 
 from unittest import mock
-import textwrap
 
-from lxml import html
 import pytest
 
 from . import post_version
 
 
-# Mock out subprocess.run to avoid having git commits
 @mock.patch('bin.post_version.subprocess.run', mock.Mock())
 class TestUpdateCalendar:
 
-    HEAD = textwrap.dedent("""\
-        <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
-        <html lang="en">
-        <head>
-        <meta http-equiv="content-type" content="text/html; charset=utf-8">
-        <title>Release Calendar</title>
-        <link rel="stylesheet" type="text/css" href="mesa.css">
-        </head>
-        <body>
-        """)
-
-    TABLE = textwrap.dedent("""\
-        <table>
-        <tr>
-        <th>Branch</th>
-        <th>Expected date</th>
-        <th>Release</th>
-        <th>Release manager</th>
-        <th>Notes</th>
-        </tr>
-        """)
-
-    FOOT = "</body></html>"
-
-    TABLE_FOOT = "</table>"
-
-    def wrap_table(self, table: str) -> str:
-        return self.HEAD + self.TABLE + table + self.TABLE_FOOT + self.FOOT
+    @pytest.fixture(autouse=True)
+    def mock_sideffects(self) -> None:
+        """Mock out side effects."""
+        with mock.patch('bin.post_version.subprocess.run', mock.Mock()), \
+                mock.patch('bin.post_version.pathlib', mock.MagicMock()):
+            yield
 
     def test_basic(self):
-        data = self.wrap_table(textwrap.dedent("""\
-            <tr>
-            <td rowspan="3">19.2</td>
-            <td>2019-11-06</td>
-            <td>19.2.3</td>
-            <td>Dylan Baker</td>
-            </tr>
-            <tr>
-            <td>2019-11-20</td>
-            <td>19.2.4</td>
-            <td>Dylan Baker</td>
-            </tr>
-            <tr>
-            <td>2019-12-04</td>
-            <td>19.2.5</td>
-            <td>Dylan Baker</td>
-            <td>Last planned 19.2.x release</td>
-            </tr>
-            """))
-
-        parsed = html.fromstring(data)
-        parsed.write = mock.Mock()
-
-        with mock.patch('bin.post_version.html.parse',
-                        mock.Mock(return_value=parsed)):
-            post_version.update_calendar('19.2.3')
-
-        assert len(parsed.findall('.//tr')) == 3
-        # we need the second element becouse the first is the header
-
-        tr = parsed.findall('.//tr')[1]
-        tds = tr.findall('.//td')
-        assert tds[0].get("rowspan") == "2"
-        assert tds[0].text == "19.2"
-        assert tds[1].text == "2019-11-20"
-
-    @pytest.fixture
-    def two_releases(self) -> html.etree.ElementTree:
-        data = self.wrap_table(textwrap.dedent("""\
-            <tr>
-            <td rowspan="1">19.1</td>
-            <td>2019-11-06</td>
-            <td>19.1.8</td>
-            <td>Not Dylan Baker</td>
-            </tr>
-            <tr>
-            <td rowspan="3">19.2</td>
-            <td>2019-11-06</td>
-            <td>19.2.3</td>
-            <td>Dylan Baker</td>
-            </tr>
-            <tr>
-            <td>2019-11-20</td>
-            <td>19.2.4</td>
-            <td>Dylan Baker</td>
-            </tr>
-            <tr>
-            <td>2019-12-04</td>
-            <td>19.2.5</td>
-            <td>Dylan Baker</td>
-            <td>Last planned 19.2.x release</td>
-            </tr>
-            """))
-
-        p = html.fromstring(data)
-        p.write = mock.Mock()
-        return p
-
-    def test_two_releases(self, two_releases: html.etree.ElementTree):
-        with mock.patch('bin.post_version.html.parse',
-                        mock.Mock(return_value=two_releases)):
-            post_version.update_calendar('19.2.3')
-
-        assert len(two_releases.findall('.//tr')) == 4
-        # we need the second element becouse the first is the header
-
-        tr = two_releases.findall('.//tr')[2]
-        tds = tr.findall('.//td')
-        assert tds[0].get("rowspan") == "2"
-        assert tds[0].text == "19.2"
-        assert tds[1].text == "2019-11-20"
-
-    def test_last_Release(self, two_releases: html.etree.ElementTree):
-        with mock.patch('bin.post_version.html.parse',
-                        mock.Mock(return_value=two_releases)):
-            post_version.update_calendar('19.1.8')
-
-        assert len(two_releases.findall('.//tr')) == 4
-        # we need the second element becouse the first is the header
-
-        tr = two_releases.findall('.//tr')[1]
-        tds = tr.findall('.//td')
-        assert tds[0].get("rowspan") == "3"
-        assert tds[0].text == "19.2"
-        assert tds[1].text == "2019-11-06"
+        data = [
+            ['20.3', '2021-01-13', '20.3.3', 'Dylan Baker', None],
+            [None,   '2021-01-27', '20.3.4', 'Dylan Baker', None],
+        ]
+
+        m = mock.Mock()
+        with mock.patch('bin.post_version.csv.reader', mock.Mock(return_value=data.copy())), \
+                mock.patch('bin.post_version.csv.writer', mock.Mock(return_value=m)):
+            post_version.update_calendar('20.3.3')
+
+            m.writerows.assert_called_with([data[1]])
+
+    def test_two_releases(self):
+        data = [
+            ['20.3', '2021-01-13', '20.3.3', 'Dylan Baker', None],
+            [None,   '2021-01-27', '20.3.4', 'Dylan Baker', None],
+            ['21.0', '2021-01-13', '21.0.0', 'Dylan Baker', None],
+            [None,   '2021-01-13', '21.0.1', 'Dylan Baker', None],
+        ]
+
+        m = mock.Mock()
+        with mock.patch('bin.post_version.csv.reader', mock.Mock(return_value=data.copy())), \
+                mock.patch('bin.post_version.csv.writer', mock.Mock(return_value=m)):
+            post_version.update_calendar('20.3.3')
+
+            d = data.copy()
+            del d[0]
+            d[0][0] = '20.3'
+            m.writerows.assert_called_with(d)
diff --git a/docs/release-calendar.csv b/docs/release-calendar.csv
new file mode 100644 (file)
index 0000000..9eec289
--- /dev/null
@@ -0,0 +1,2 @@
+20.3, 2021-01-13, 20.3.3, Dylan Baker,
+    , 2021-01-27, 20.3.4, Dylan Baker,
index 6181410..4b4a854 100644 (file)
@@ -27,10 +27,8 @@ nominate a patch in the next stable release.
 Calendar
 --------
 
-+--------+---------------+------------+-----------------+-----------------------------------------+
-| Branch | Expected date | Release    | Release manager | Notes                                   |
-+========+===============+============+=================+=========================================+
-| 20.3   | 2021-01-13    | 20.3.3     | Dylan Baker     |                                         |
-|        +---------------+------------+-----------------+-----------------------------------------+
-|        | 2021-01-27    | 20.3.4     | Dylan Baker     |                                         |
-+--------+---------------+------------+-----------------+-----------------------------------------+
+.. csv-table:: Calendar
+   :header: "Branch", "Expected date", "Release", "Release manager", "Notes"
+   :file: release-calendar.csv
+   :stub-columns: 1
+   :widths: auto
index 49529f2..b1f35c5 100644 (file)
@@ -312,7 +312,7 @@ Then run the
    ./bin/post_version.py X.Y.Z
 
 , where X.Y.Z is the version you just made. This will update
-docs/relnotes.rst and docs/release-calendar.rst. It will then generate
+docs/relnotes.rst and docs/release-calendar.csv. It will then generate
 a Git commit automatically. Check that everything looks correct and
 push: