From patchwork Mon Sep 22 18:00:46 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 386 Return-Path: X-Original-To: u-boot-concept@u-boot.org Delivered-To: u-boot-concept@u-boot.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1758564102; bh=WPBJCA7WcKVupEuH7jlYzMYDzxgcPAK4IgVp2hExuFA=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=AAwCUU9cAT0w2zxM4AoflGKfmhTEUteIlHA2egvNRvJTpmpKuLZjn5ZsizSBYmNtX 8nYssnJo1yX2GfZ9FxssEMBAcKKCyUDbRZ5vrDDypHzXtXReSrHBTV4YPchA2mjmWD KqikRWkvtcGe/O3TB1erR7WhtwGELuWrMvzoOzvCli7G8M+DEQfG/9m18O5GcO3jkL zKPTqnyxAZ0Ab2lA8SMJis8wdbbj1uE4JaQLxcBfMfh7PRvrEOKj5mP7KhWc9zpwfC ZnKYBnGy4XUwfWEgcjFYOGEQZ0NXY3tRB8hGdUJrOYTjo7KK+Hxtr7/1WKDHt+nElh lUkAK/4dWtHlA== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 0068C5FE1A for ; Mon, 22 Sep 2025 12:01:42 -0600 (MDT) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id 8wmsHotirCND for ; Mon, 22 Sep 2025 12:01:41 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1758564101; bh=WPBJCA7WcKVupEuH7jlYzMYDzxgcPAK4IgVp2hExuFA=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=avOO0pqhqrHMQcKPqUFS1Y8eT8xh70KcUZcXLvL/GQfTcO02lZKKtNoHP4ayYtW4Y lq8LO293V1HxetAo/nDkaaysxF+096iwsGeC7Csxgl+MONfdxIqbZmHRPz3anY/03z 8UTX03+VqJ+UXrxgIrRZQHrT90cAxu+cbtzNa1hWWIyBmPnTfsHXf+Cnsp8c5alDOK 4gQ5kGMqPRRAp/18guTQJo3AORc1zxBV9pz8QZJTJL8FynS5h4J6mQnwZgOr6ylDdD ji3tmYvvLjdkPxxafcVZdnqrcJ64C5Js8jM3CvrqQoDyC76903+uNtsQ+OBzHeBGuw +0aXez7SQkpmQ== Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 89E6467B38 for ; Mon, 22 Sep 2025 12:01:41 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1758564099; bh=6w72WixMciGk4MmZseGjjnjE9ZspIheRHuypSYIOTTU=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=N16xgYM7+Cgr6HqPP70yVk+o5dt6PYi4cVmDM0dv4smjaAejjNq0zvUltH9Wq9Yuv 9OaEwVYmLECPu9fjybWU+E3nFiKn21WMbG8BzOuZzXhRAFPX+Fn/63ow6f39qWV7/1 /gMxVAauFZ1sjzSf3tL7ZTZJc5t62eCqPaYp/GGhh3GRBobV2Gr1dGm5Mlu5Ub5ioT 2NYHg0rCE7HKALS9tvmsNSUHFTX9oa2b2wVxp94IoILG7n72vYSyqayf870uf0ii2H qQIxqV5zlx6PHnEL0VHYXhxcEDRy5HjCviFMgQYhwRbkONRMNcPxo9XGdrjUfBwr5g 58sLyPr6RW//w== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id AAA0A67C63; Mon, 22 Sep 2025 12:01:39 -0600 (MDT) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10026) with ESMTP id bslfSVcD0BPV; Mon, 22 Sep 2025 12:01:39 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1758564094; bh=s1BMy72pUAidxqZg8UetNunhqLvoio2RavUBf1akPbE=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=buaeMq+CYfEPlUHjWKR6Bi+WCO1jl1cbvjutMafoTynMKUj3TowfeZFblQp/UxZTx axHHyAlS8xyCdK8Mn570A8LsCOcqAGbhEoJq1G/QromtjHJaPOt369HPYiE88CeD0V q1y4T2IMHLAueNMI9nDff+pHqGSK1krAzQn4nCuohKMArigGIagbP03ZMTdPGZgzzb tR1qaCq7q1HoH3+z05w8Oqi9bYm+5y+A0uh0VVcUswcWDYMBt8OogOKp21lRMsBG7/ 98VjEWbK2etyLRzuO9DL5YLcy1teisKFp14ja6UC7NT02kZYmux+SkaAUIkscQhmwl sbqXtX0CruObQ== Received: from u-boot.org (unknown [73.34.74.121]) by mail.u-boot.org (Postfix) with ESMTPSA id C791567B38; Mon, 22 Sep 2025 12:01:33 -0600 (MDT) From: Simon Glass To: U-Boot Concept Date: Mon, 22 Sep 2025 12:00:46 -0600 Message-ID: <20250922180116.3088502-3-sjg@u-boot.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20250922180116.3088502-1-sjg@u-boot.org> References: <20250922180116.3088502-1-sjg@u-boot.org> MIME-Version: 1.0 Message-ID-Hash: 6VMSPLCCZZHFTUM2U32NTZ27HPYTY7CF X-Message-ID-Hash: 6VMSPLCCZZHFTUM2U32NTZ27HPYTY7CF X-MailFrom: sjg@u-boot.org X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: Heinrich Schuchardt , Simon Glass , Claude X-Mailman-Version: 3.3.10 Precedence: list Subject: [Concept] [PATCH 02/24] scripts: Fix RST formatting in release_version.py List-Id: Discussion and patches related to U-Boot Concept Archived-At: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: From: Simon Glass Fix line continuation in generate_schedule() that caused Sphinx to fail with "Bullet list ends without a blank line; unexpected unindent" error. Add tests to validate RST formatting of generated documentation. Co-developed-by: Claude Signed-off-by: Simon Glass --- scripts/release_version.py | 3 +- test/scripts/test_release_version.py | 164 +++++++++++++++++++++++++++ 2 files changed, 165 insertions(+), 2 deletions(-) diff --git a/scripts/release_version.py b/scripts/release_version.py index 476f394ed1a..5ff179dafb8 100755 --- a/scripts/release_version.py +++ b/scripts/release_version.py @@ -190,8 +190,7 @@ Release candidate schedule: * **{target_year}.{target_month:02d}-rc3**: {rc3_date.strftime('%a %d-%b-%Y')} * **{target_year}.{target_month:02d}-rc2**: {rc2_date.strftime('%a %d-%b-%Y')} * **{target_year}.{target_month:02d}-rc1**: {rc1_date.strftime('%a %d-%b-%Y')} -* **{target_year}.{target_month:02d}** (Final): \\ -{final_date.strftime('%a %d-%b-%Y')} +* **{target_year}.{target_month:02d}** (Final): {final_date.strftime('%a %d-%b-%Y')} ''' return schedule_text diff --git a/test/scripts/test_release_version.py b/test/scripts/test_release_version.py index 14a63beb68c..3758404672d 100755 --- a/test/scripts/test_release_version.py +++ b/test/scripts/test_release_version.py @@ -9,6 +9,7 @@ import io import json import os import shutil +import subprocess import sys import tempfile import unittest @@ -876,6 +877,169 @@ class TestMainFunction(unittest.TestCase): self.assertIn(expected_msg, mock_stdout.getvalue()) +class TestRSTFormatting(unittest.TestCase): + """Test that generated RST content is valid for Sphinx""" + + def setUp(self): + """Set up test fixtures""" + self.test_dir = tempfile.mkdtemp() + + def tearDown(self): + """Clean up test fixtures""" + shutil.rmtree(self.test_dir) + + def test_generate_schedule_rst_validity(self): + """Test that generate_schedule() produces valid RST""" + schedule = generate_schedule() + + # Write schedule to a test file + test_rst_path = os.path.join(self.test_dir, 'test_schedule.rst') + with open(test_rst_path, 'w', encoding='utf-8') as f: + f.write('''.. SPDX-License-Identifier: GPL-2.0+ + +Test Document +============= + +''') + f.write(schedule) + + # Try to validate with rst2html if available (fallback test) + try: + # Run docutils rst2html to validate the RST syntax + result = subprocess.run(['rst2html', test_rst_path], + capture_output=True, text=True, timeout=30) + # If rst2html is available and succeeds, the RST is valid + if result.returncode == 0: + self.assertTrue(True, "RST validation passed with rst2html") + else: + # Check for specific formatting errors + stderr = result.stderr.lower() + if 'bullet list ends without a blank line' in stderr: + self.fail(f"RST formatting error in schedule: {result.stderr}") + elif 'unexpected unindent' in stderr: + self.fail(f"RST formatting error in schedule: {result.stderr}") + except (subprocess.TimeoutExpired, FileNotFoundError): + # rst2html not available or timeout, do basic content checks + pass + + # Basic RST format validation checks + lines = schedule.split('\n') + + # Check for proper bullet list formatting + in_bullet_list = False + last_line_was_bullet = False + + for i, line in enumerate(lines): + stripped = line.strip() + + # Check if this line starts a bullet list item + if stripped.startswith('* '): + in_bullet_list = True + last_line_was_bullet = True + + # Check for line continuation issues + if '\\' in line: + # If there's a backslash continuation, ensure it's properly formatted + # The continuation should be on the same line, not the next line + self.assertFalse(line.rstrip().endswith('\\'), + f"Line {i+1} has improper line continuation: {line}") + elif in_bullet_list and stripped == '': + # Empty line might end the bullet list + last_line_was_bullet = False + elif in_bullet_list and stripped and not stripped.startswith(' '): + # Non-indented content after bullet list should have blank line before it + if last_line_was_bullet: + self.fail(f"Line {i+1} lacks blank line after bullet list: {line}") + in_bullet_list = False + last_line_was_bullet = False + elif in_bullet_list and stripped.startswith(' '): + # Indented content is part of the bullet list + last_line_was_bullet = False + else: + in_bullet_list = False + last_line_was_bullet = False + + def test_update_docs_rst_validity(self): + """Test that update_docs() produces valid RST""" + docs_path = os.path.join(self.test_dir, 'concept_releases.rst') + + # Create initial file + with open(docs_path, 'w', encoding='utf-8') as f: + f.write('''.. SPDX-License-Identifier: GPL-2.0+ + +U-Boot Concept Releases +======================= + +This document tracks all concept releases of U-Boot. + +Release History +--------------- + +''') + + # Add a release that includes schedule generation + release_info = ReleaseInfo( + is_final=False, + version='2025.02-rc1', + year=2025, + month=2, + rc_number=1 + ) + + changes_made = update_docs(release_info, 'abc123def', docs_path) + self.assertTrue(changes_made) + + # Read the updated content + with open(docs_path, 'r', encoding='utf-8') as f: + content = f.read() + + # Try to validate with rst2html if available + try: + result = subprocess.run(['rst2html', docs_path], + capture_output=True, text=True, timeout=30) + if result.returncode != 0: + stderr = result.stderr.lower() + if 'bullet list ends without a blank line' in stderr: + self.fail(f"RST formatting error in generated docs: {result.stderr}") + elif 'unexpected unindent' in stderr: + self.fail(f"RST formatting error in generated docs: {result.stderr}") + except (subprocess.TimeoutExpired, FileNotFoundError): + # rst2html not available, continue with manual checks + pass + + # Manual validation of RST structure + lines = content.split('\n') + + # Find the Next Release section and check its formatting + next_release_idx = -1 + for i, line in enumerate(lines): + if line.strip() == 'Next Release': + next_release_idx = i + break + + if next_release_idx != -1: + # Check the bullet list in the schedule section + in_candidate_schedule = False + for i in range(next_release_idx, len(lines)): + line = lines[i] + if 'Release candidate schedule:' in line: + in_candidate_schedule = True + continue + elif in_candidate_schedule and line.strip().startswith('* '): + # This is a bullet list item + # Check it doesn't have improper line continuation + self.assertFalse(line.rstrip().endswith('\\'), + f"Line {i+1} has improper line continuation in schedule: {line}") + elif in_candidate_schedule and line.strip() == '': + # Empty line - bullet list might be ending + continue + elif (in_candidate_schedule and line.strip() and + not line.strip().startswith('* ') and + not line.startswith(' ')): + # End of bullet list section + break + + class TestReleaseVersionScenarios(unittest.TestCase): """Test realistic release scenarios"""