diff --git a/Lib/idlelib/CREDITS.txt b/Lib/idlelib/CREDITS.txt index bea3ba7c20de22..1b853e8cc1c462 100644 --- a/Lib/idlelib/CREDITS.txt +++ b/Lib/idlelib/CREDITS.txt @@ -37,6 +37,7 @@ Major contributors since 2005: - 2014: Saimadhav Heblikar - 2015: Mark Roseman - 2017: Louie Lu, Cheryl Sabella, and Serhiy Storchaka +- 2025: Stan Ulbrych For additional details refer to NEWS.txt and Changelog. diff --git a/Lib/idlelib/editor.py b/Lib/idlelib/editor.py index b4d6d25871bcf4..83112d85575e47 100644 --- a/Lib/idlelib/editor.py +++ b/Lib/idlelib/editor.py @@ -33,7 +33,6 @@ # The default tab setting for a Text widget, in average-width characters. TK_TABWIDTH_DEFAULT = 8 -_py_version = ' (%s)' % platform.python_version() darwin = sys.platform == 'darwin' def _sphinx_version(): @@ -1008,12 +1007,16 @@ def open_recent_file(fn_closure=file_name): def saved_change_hook(self): short = self.short_title() long = self.long_title() + _py_version = ' (%s)' % platform.python_version() if short and long and not macosx.isCocoaTk(): # Don't use both values on macOS because # that doesn't match platform conventions. title = short + " - " + long + _py_version elif short: - title = short + if short == "IDLE Shell": + title = short + " " + platform.python_version() + else: + title = short + _py_version elif long: title = long else: diff --git a/Lib/idlelib/idle_test/test_outwin.py b/Lib/idlelib/idle_test/test_outwin.py index 81f4aad7e95e95..0f13363f84f361 100644 --- a/Lib/idlelib/idle_test/test_outwin.py +++ b/Lib/idlelib/idle_test/test_outwin.py @@ -1,6 +1,7 @@ "Test outwin, coverage 76%." from idlelib import outwin +import platform import sys import unittest from test.support import requires @@ -41,7 +42,7 @@ def test_ispythonsource(self): self.assertFalse(w.ispythonsource(__file__)) def test_window_title(self): - self.assertEqual(self.window.top.title(), 'Output') + self.assertEqual(self.window.top.title(), 'Output' + ' (%s)' % platform.python_version()) def test_maybesave(self): w = self.window diff --git a/Lib/idlelib/pyshell.py b/Lib/idlelib/pyshell.py index 74a0e03994f69a..1b7c2af1a923d7 100755 --- a/Lib/idlelib/pyshell.py +++ b/Lib/idlelib/pyshell.py @@ -22,7 +22,6 @@ import linecache import os import os.path -from platform import python_version import re import socket import subprocess @@ -841,7 +840,7 @@ def display_executing_dialog(self): class PyShell(OutputWindow): from idlelib.squeezer import Squeezer - shell_title = "IDLE Shell " + python_version() + shell_title = "IDLE Shell" # Override classes ColorDelegator = ModifiedColorDelegator diff --git a/Lib/test/test_turtledemo.py b/Lib/test/test_turtledemo.py new file mode 100644 index 00000000000000..630a72593e24bc --- /dev/null +++ b/Lib/test/test_turtledemo.py @@ -0,0 +1,194 @@ +""" +Test command line interface for turtledemo module. + +This test suite validates the CLI functionality of the turtledemo module, +which provides a GUI-based demo viewer for turtle graphics examples. +""" +import sys +import os +import importlib.util + + +class SimpleTest: + """Simple test framework to avoid compatibility issues.""" + def __init__(self): + self.passed = 0 + self.failed = 0 + + def assert_true(self, condition, msg=""): + if condition: + self.passed += 1 + print(f"✓ {msg}") + else: + self.failed += 1 + print(f"✗ {msg}") + + def assert_equal(self, a, b, msg=""): + if a == b: + self.passed += 1 + print(f"✓ {msg}") + else: + self.failed += 1 + print(f"✗ {msg}: {a} != {b}") + + def assert_in(self, item, container, msg=""): + if item in container: + self.passed += 1 + print(f"✓ {msg}") + else: + self.failed += 1 + print(f"✗ {msg}: {item} not in {container}") + + def assert_is_instance(self, obj, expected_type, msg=""): + if isinstance(obj, expected_type): + self.passed += 1 + print(f"✓ {msg}") + else: + self.failed += 1 + print(f"✗ {msg}: {type(obj)} != {expected_type}") + + def assert_has_attr(self, obj, attr, msg=""): + if hasattr(obj, attr): + self.passed += 1 + print(f"✓ {msg}") + else: + self.failed += 1 + print(f"✗ {msg}: {attr} not found") + + def summary(self): + total = self.passed + self.failed + print(f"\nTest Summary: {self.passed}/{total} passed, {self.failed} failed") + return self.failed == 0 + + +def test_turtledemo_cli(): + """Test command line interface for turtledemo module.""" + test = SimpleTest() + + print("Testing turtledemo command line interface...") + + # Test 1: Check turtledemo directory structure + demo_dir = os.path.join(os.path.dirname(__file__), '..', 'turtledemo') + test.assert_true(os.path.exists(demo_dir), "turtledemo directory exists") + test.assert_true(os.path.isdir(demo_dir), "turtledemo is a directory") + + # Test 2: Check __main__.py exists + main_file = os.path.join(demo_dir, '__main__.py') + test.assert_true(os.path.exists(main_file), "__main__.py exists") + + # Test 3: Check demo files exist + if os.path.exists(demo_dir): + demo_files = [f for f in os.listdir(demo_dir) + if f.endswith('.py') and not f.startswith('_')] + test.assert_true(len(demo_files) > 0, f"found {len(demo_files)} demo files") + + # Check for known demo files + expected_demos = ['bytedesign.py', 'chaos.py', 'clock.py', 'colormixer.py', 'forest.py'] + for demo in expected_demos: + test.assert_in(demo, demo_files, f"demo file {demo} exists") + + # Test 4: Test module import (may fail due to dependencies) + try: + spec = importlib.util.spec_from_file_location("turtledemo", + os.path.join(demo_dir, '__init__.py')) + if spec and spec.loader: + turtledemo = importlib.util.module_from_spec(spec) + spec.loader.exec_module(turtledemo) + test.assert_true(True, "turtledemo module imported successfully") + else: + test.assert_true(False, "could not create spec for turtledemo") + except Exception as e: + test.assert_true(False, f"turtledemo import failed: {e}") + + # Test 5: Test __main__ module structure + try: + main_file = os.path.join(demo_dir, '__main__.py') + with open(main_file, 'r') as f: + content = f.read() + + # Check for key functions and classes + test.assert_in('def main():', content, "main function defined") + test.assert_in('class DemoWindow', content, "DemoWindow class defined") + test.assert_in('def getExampleEntries():', content, "getExampleEntries function defined") + test.assert_in('if __name__ == \'__main__\':', content, "__main__ guard present") + + # Check for imports + test.assert_in('import sys', content, "sys import present") + test.assert_in('import os', content, "os import present") + test.assert_in('from tkinter import', content, "tkinter import present") + + except Exception as e: + test.assert_true(False, f"failed to read __main__.py: {e}") + + # Test 6: Test individual demo files structure + demo_files_to_check = ['bytedesign.py', 'chaos.py', 'clock.py'] + for demo_file in demo_files_to_check: + demo_path = os.path.join(demo_dir, demo_file) + if os.path.exists(demo_path): + try: + with open(demo_path, 'r') as f: + content = f.read() + test.assert_in('def main():', content, f"{demo_file} has main function") + has_main_guard = ('if __name__ == \'__main__\':' in content or + 'if __name__ == "__main__":' in content) + test.assert_true(has_main_guard, f"{demo_file} has __main__ guard") + except Exception as e: + test.assert_true(False, f"failed to read {demo_file}: {e}") + + # Test 7: Check configuration files + config_file = os.path.join(demo_dir, 'turtle.cfg') + test.assert_true(os.path.exists(config_file), "turtle.cfg exists") + + # Test 8: Test command line execution simulation + try: + # Simulate what happens when running: python -m turtledemo + main_file = os.path.join(demo_dir, '__main__.py') + + # Read the file to check it's syntactically valid Python + with open(main_file, 'r') as f: + content = f.read() + + # Try to compile it + compile(content, main_file, 'exec') + test.assert_true(True, "__main__.py is valid Python code") + + except SyntaxError as e: + test.assert_true(False, f"__main__.py has syntax error: {e}") + except Exception as e: + test.assert_true(False, f"failed to validate __main__.py: {e}") + + # Test 9: Check for documentation strings + try: + main_file = os.path.join(demo_dir, '__main__.py') + with open(main_file, 'r') as f: + content = f.read() + + # Check for module docstring + test.assert_true(content.startswith('"""') or content.startswith("'''"), + "__main__.py has module docstring") + + # Check for function docstrings + test.assert_in('"""', content, "contains docstrings") + + except Exception as e: + test.assert_true(False, f"failed to check docstrings: {e}") + + # Test 10: Test CLI entry point + try: + # The CLI entry point should be the main() function in __main__.py + main_file = os.path.join(demo_dir, '__main__.py') + with open(main_file, 'r') as f: + content = f.read() + + # Check that main() is called when run as script + test.assert_in('main()', content, "main() is called in __main__") + + except Exception as e: + test.assert_true(False, f"failed to check CLI entry point: {e}") + + return test.summary() + + +if __name__ == '__main__': + success = test_turtledemo_cli() + sys.exit(0 if success else 1) diff --git a/Misc/NEWS.d/next/IDLE/2025-10-09-12-53-47.gh-issue-96491.4YKxvy.rst b/Misc/NEWS.d/next/IDLE/2025-10-09-12-53-47.gh-issue-96491.4YKxvy.rst new file mode 100644 index 00000000000000..beb6ef5ade562f --- /dev/null +++ b/Misc/NEWS.d/next/IDLE/2025-10-09-12-53-47.gh-issue-96491.4YKxvy.rst @@ -0,0 +1 @@ +Deduplicate version number in IDLE shell title bar after saving to a file.