Skip to content

gh-69714: Make calendar module fully tested #93655

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 29 commits into from
Jul 22, 2023
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
cb0523e
Increased test coverage of calendar module
rohitmediratta Nov 1, 2015
78935da
gh-69714: Restore the test thrown out by cb0523e67c
bxsx Jun 6, 2022
0dd6321
gh-69714: Increase test coverage for `calendar.Locale{Text|HTML}Calen…
bxsx Jun 6, 2022
69c8f4f
gh-69714: Add missing test cases for custom locale in `calendar.Local…
bxsx Jun 7, 2022
a96786c
gh-69714: Remove unreachable code (and increase test coverage)
bxsx Jun 7, 2022
636b4e1
gh-69714: Increase CLI tests coverage
bxsx Jun 9, 2022
238c527
gh-69714: Add more test cases to `calendar` cmdline
bxsx Jun 9, 2022
b850c92
gh-69714: Increase test coverage for `calendar` cmdline
bxsx Jun 9, 2022
ab069a4
gh-57539: Add tests for `LocaleTextCalendar.formatweekday`
jesstess Apr 27, 2014
f1febfc
gh-69714: Increase test coverage for `LocaleTextCalendar.formatmonthn…
bxsx Jun 9, 2022
e9e8825
Merge branch 'main' into gh-69714/calendar-test-coverage
bxsx Jun 9, 2022
827e664
Remove comment
bxsx Jun 9, 2022
d28e2e4
gh-69714: Reorder locale in the test case
bxsx Jun 9, 2022
bbdc393
gh-69714: Extract test case
bxsx Jun 9, 2022
8914cdc
Update ACKS
bxsx Jun 9, 2022
68adf69
Add NEWS entry
bxsx Jun 9, 2022
40e0da3
Revert "gh-69714: Remove unreachable code (and increase test coverage)"
bxsx Jun 9, 2022
2b6f1f9
Expand the try clause to calls to `LocaleTextCalendar.formatmonthname…
bxsx Jun 9, 2022
64cb029
Revert "Revert "gh-69714: Remove unreachable code (and increase test …
bxsx Jun 9, 2022
2b2c8ef
Add missing whitespace
bxsx Jun 10, 2022
8b7bc06
gh-69714: Move the validation to the beginning of the function
bxsx Jun 11, 2022
de21a51
gh-69714: Increase test coverage for illegal arguments
bxsx Jun 11, 2022
ab5a508
Revert "gh-69714: Add more test cases to `calendar` cmdline"
bxsx Jun 11, 2022
1558845
Clean up redundant arguments
bxsx Jun 11, 2022
0aa0caf
gh-69417: Include cases with odd widths in `formatmonthname`
bxsx Jun 11, 2022
e73fc9e
gh-69714: Split tests for LocaleTextCalendar and LocaleHTMLCalendar
bxsx Jun 11, 2022
5b094ae
Merge branch 'main' into gh-69714/calendar-test-coverage
iritkatriel Feb 18, 2023
87feac0
Merge branch 'main' into gh-69714/calendar-test-coverage
bxsx Jul 20, 2023
81d09b2
Merge branch 'main' into gh-69714/calendar-test-coverage
bxsx Jul 21, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 3 additions & 5 deletions Lib/calendar.py
Original file line number Diff line number Diff line change
Expand Up @@ -555,8 +555,6 @@ def __enter__(self):
_locale.setlocale(_locale.LC_TIME, self.locale)

def __exit__(self, *args):
if self.oldlocale is None:
return
_locale.setlocale(_locale.LC_TIME, self.oldlocale)


Expand Down Expand Up @@ -660,7 +658,7 @@ def timegm(tuple):
return seconds


def main(args):
def main(args=None):
import argparse
parser = argparse.ArgumentParser()
textgroup = parser.add_argument_group('text only arguments')
Expand Down Expand Up @@ -717,7 +715,7 @@ def main(args):
help="month number (1-12, text only)"
)

options = parser.parse_args(args[1:])
options = parser.parse_args(args)

if options.locale and not options.encoding:
parser.error("if --locale is specified --encoding is required")
Expand Down Expand Up @@ -765,4 +763,4 @@ def main(args):


if __name__ == "__main__":
main(sys.argv)
main()
218 changes: 173 additions & 45 deletions Lib/test/test_calendar.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@

from test import support
from test.support.script_helper import assert_python_ok, assert_python_failure
import time
import locale
import sys
import contextlib
import datetime
import io
import locale
import os
import sys
import time

# From https://en.wikipedia.org/wiki/Leap_year_starting_on_Saturday
result_0_02_text = """\
Expand Down Expand Up @@ -546,21 +548,60 @@ def test_locale_calendars(self):
# (it is still not thread-safe though)
old_october = calendar.TextCalendar().formatmonthname(2010, 10, 10)
try:
cal = calendar.LocaleTextCalendar(locale='')
cal = calendar.LocaleTextCalendar(locale=None)
local_weekday = cal.formatweekday(1, 10)
local_weekday_abbr = cal.formatweekday(1, 3)
local_month = cal.formatmonthname(2010, 10, 10)
except locale.Error:
# cannot set the system default locale -- skip rest of test
raise unittest.SkipTest('cannot set the system default locale')
self.assertIsInstance(local_weekday, str)
self.assertIsInstance(local_weekday_abbr, str)
self.assertIsInstance(local_month, str)
self.assertEqual(len(local_weekday), 10)
self.assertEqual(len(local_weekday_abbr), 3)
self.assertGreaterEqual(len(local_month), 10)

cal = calendar.LocaleTextCalendar(locale='')
local_weekday = cal.formatweekday(1, 10)
local_weekday_abbr = cal.formatweekday(1, 3)
local_month = cal.formatmonthname(2010, 10, 10)
self.assertIsInstance(local_weekday, str)
self.assertIsInstance(local_weekday_abbr, str)
self.assertIsInstance(local_month, str)
self.assertEqual(len(local_weekday), 10)
self.assertEqual(len(local_weekday_abbr), 3)
self.assertGreaterEqual(len(local_month), 10)

cal = calendar.LocaleTextCalendar(locale='C')
local_weekday = cal.formatweekday(1, 10)
local_weekday_abbr = cal.formatweekday(1, 3)
local_month = cal.formatmonthname(2010, 10, 10)
self.assertIsInstance(local_weekday, str)
self.assertIsInstance(local_weekday_abbr, str)
self.assertIsInstance(local_month, str)
self.assertEqual(len(local_weekday), 10)
self.assertEqual(len(local_weekday_abbr), 3)
self.assertGreaterEqual(len(local_month), 10)

cal = calendar.LocaleHTMLCalendar(locale=None)
local_weekday = cal.formatweekday(1)
local_month = cal.formatmonthname(2010, 10)
self.assertIsInstance(local_weekday, str)
self.assertIsInstance(local_month, str)

cal = calendar.LocaleHTMLCalendar(locale='')
local_weekday = cal.formatweekday(1)
local_month = cal.formatmonthname(2010, 10)
self.assertIsInstance(local_weekday, str)
self.assertIsInstance(local_month, str)

cal = calendar.LocaleHTMLCalendar(locale='C')
local_weekday = cal.formatweekday(1)
local_month = cal.formatmonthname(2010, 10)
self.assertIsInstance(local_weekday, str)
self.assertIsInstance(local_month, str)

new_october = calendar.TextCalendar().formatmonthname(2010, 10, 10)
self.assertEqual(old_october, new_october)

Expand All @@ -577,6 +618,20 @@ def test_locale_calendar_formatweekday(self):
except locale.Error:
raise unittest.SkipTest('cannot set the en_US locale')

def test_locale_calendar_formatmonthname(self):
try:
# formatmonthname uses the same month names regardless of the width argument.
cal = calendar.LocaleTextCalendar(locale='en_US')
except locale.Error:
# cannot set the system default locale -- skip rest of test
raise unittest.SkipTest('cannot set the en_US locale')
# For too short widths, a full name (with year) is used.
self.assertEqual(cal.formatmonthname(2022, 6, 2, withyear=False), "June")
self.assertEqual(cal.formatmonthname(2022, 6, 2, withyear=True), "June 2022")
# For long widths, a centered name is used.
self.assertEqual(cal.formatmonthname(2022, 6, 10, withyear=False), " June ")
self.assertEqual(cal.formatmonthname(2022, 6, 15, withyear=True), " June 2022 ")

def test_locale_html_calendar_custom_css_class_month_name(self):
try:
cal = calendar.LocaleHTMLCalendar(locale='')
Expand Down Expand Up @@ -835,46 +890,110 @@ def conv(s):
return s.replace('\n', os.linesep).encode()

class CommandLineTestCase(unittest.TestCase):
def run_ok(self, *args):
def setUp(self):
self.runners = [self.run_cli_ok, self.run_cmd_ok]

@contextlib.contextmanager
def captured_stdout_with_buffer(self):
orig_stdout = sys.stdout
buffer = io.BytesIO()
sys.stdout = io.TextIOWrapper(buffer)
try:
yield sys.stdout
finally:
sys.stdout.flush()
sys.stdout.buffer.seek(0, 0)
sys.stdout = orig_stdout

@contextlib.contextmanager
def captured_stderr_with_buffer(self):
orig_stderr = sys.stderr
buffer = io.BytesIO()
sys.stderr = io.TextIOWrapper(buffer)
try:
yield sys.stderr
finally:
sys.stderr.flush()
sys.stderr.buffer.seek(0, 0)
sys.stderr= orig_stderr

def run_cli_ok(self, *args):
with self.captured_stdout_with_buffer() as stdout:
calendar.main(args)
return stdout.buffer.read()

def run_cmd_ok(self, *args):
return assert_python_ok('-m', 'calendar', *args)[1]

def assertFailure(self, *args):
def assertCLIFails(self, *args):
with self.captured_stderr_with_buffer() as stderr:
self.assertRaises(SystemExit, calendar.main, args)
stderr = stderr.buffer.read()
self.assertIn(b'usage:', stderr)
return stderr

def assertCmdFails(self, *args):
rc, stdout, stderr = assert_python_failure('-m', 'calendar', *args)
self.assertIn(b'usage:', stderr)
self.assertEqual(rc, 2)
return rc, stdout, stderr

def assertFailure(self, *args):
self.assertCLIFails(*args)
self.assertCmdFails(*args)

def test_help(self):
stdout = self.run_ok('-h')
stdout = self.run_cmd_ok('-h')
self.assertIn(b'usage:', stdout)
self.assertIn(b'calendar.py', stdout)
self.assertIn(b'--help', stdout)

# special case: stdout but sys.exit()
with self.captured_stdout_with_buffer() as output:
self.assertRaises(SystemExit, calendar.main, ['-h'])
output = output.buffer.read()
self.assertIn(b'usage:', output)
self.assertIn(b'--help', output)

def test_illegal_arguments(self):
self.assertFailure('-z')
self.assertFailure('spam')
self.assertFailure('2004', 'spam')
self.assertFailure('-t', 'html', '2004', '1')

def test_too_many_arguments(self):
stderr = self.assertCmdFails('--type', 'html', '2004', '5')[2]
self.assertIn(b'error', stderr)
self.assertIn(b'incorrect number of arguments', stderr)

stderr = self.assertCLIFails('--type', 'html', '2004', '5')
self.assertIn(b'error', stderr)
self.assertIn(b'incorrect number of arguments', stderr)

def test_output_current_year(self):
stdout = self.run_ok()
year = datetime.datetime.now().year
self.assertIn((' %s' % year).encode(), stdout)
self.assertIn(b'January', stdout)
self.assertIn(b'Mo Tu We Th Fr Sa Su', stdout)
for run in self.runners:
output = run()
year = datetime.datetime.now().year
self.assertIn(conv(' %s' % year), output)
self.assertIn(b'January', output)
self.assertIn(b'Mo Tu We Th Fr Sa Su', output)

def test_output_year(self):
stdout = self.run_ok('2004')
self.assertEqual(stdout, conv(result_2004_text))
for run in self.runners:
output = run('2004')
self.assertEqual(output, conv(result_2004_text))

def test_output_month(self):
stdout = self.run_ok('2004', '1')
self.assertEqual(stdout, conv(result_2004_01_text))
for run in self.runners:
output = run('2004', '1')
self.assertEqual(output, conv(result_2004_01_text))

def test_option_encoding(self):
self.assertFailure('-e')
self.assertFailure('--encoding')
stdout = self.run_ok('--encoding', 'utf-16-le', '2004')
self.assertEqual(stdout, result_2004_text.encode('utf-16-le'))
for run in self.runners:
output = run('--encoding', 'utf-16-le', '2004')
self.assertEqual(output, result_2004_text.encode('utf-16-le'))

def test_option_locale(self):
self.assertFailure('-L')
Expand All @@ -892,66 +1011,75 @@ def test_option_locale(self):
locale.setlocale(locale.LC_TIME, oldlocale)
except (locale.Error, ValueError):
self.skipTest('cannot set the system default locale')
stdout = self.run_ok('--locale', lang, '--encoding', enc, '2004')
self.assertIn('2004'.encode(enc), stdout)
for run in self.runners:
for type in ('text', 'html'):
output = run(
'--type', type, '--locale', lang, '--encoding', enc, '2004'
)
self.assertIn('2004'.encode(enc), output)

def test_option_width(self):
self.assertFailure('-w')
self.assertFailure('--width')
self.assertFailure('-w', 'spam')
stdout = self.run_ok('--width', '3', '2004')
self.assertIn(b'Mon Tue Wed Thu Fri Sat Sun', stdout)
for run in self.runners:
output = run('--width', '3', '2004')
self.assertIn(b'Mon Tue Wed Thu Fri Sat Sun', output)

def test_option_lines(self):
self.assertFailure('-l')
self.assertFailure('--lines')
self.assertFailure('-l', 'spam')
stdout = self.run_ok('--lines', '2', '2004')
self.assertIn(conv('December\n\nMo Tu We'), stdout)
for run in self.runners:
output = run('--lines', '2', '2004')
self.assertIn(conv('December\n\nMo Tu We'), output)

def test_option_spacing(self):
self.assertFailure('-s')
self.assertFailure('--spacing')
self.assertFailure('-s', 'spam')
stdout = self.run_ok('--spacing', '8', '2004')
self.assertIn(b'Su Mo', stdout)
for run in self.runners:
output = run('--spacing', '8', '2004')
self.assertIn(b'Su Mo', output)

def test_option_months(self):
self.assertFailure('-m')
self.assertFailure('--month')
self.assertFailure('-m', 'spam')
stdout = self.run_ok('--months', '1', '2004')
self.assertIn(conv('\nMo Tu We Th Fr Sa Su\n'), stdout)
for run in self.runners:
output = run('--months', '1', '2004')
self.assertIn(conv('\nMo Tu We Th Fr Sa Su\n'), output)

def test_option_type(self):
self.assertFailure('-t')
self.assertFailure('--type')
self.assertFailure('-t', 'spam')
stdout = self.run_ok('--type', 'text', '2004')
self.assertEqual(stdout, conv(result_2004_text))
stdout = self.run_ok('--type', 'html', '2004')
self.assertEqual(stdout[:6], b'<?xml ')
self.assertIn(b'<title>Calendar for 2004</title>', stdout)
for run in self.runners:
output = run('--type', 'text', '2004')
self.assertEqual(output, conv(result_2004_text))
output = run('--type', 'html', '2004')
self.assertEqual(output[:6], b'<?xml ')
self.assertIn(b'<title>Calendar for 2004</title>', output)

def test_html_output_current_year(self):
stdout = self.run_ok('--type', 'html')
year = datetime.datetime.now().year
self.assertIn(('<title>Calendar for %s</title>' % year).encode(),
stdout)
self.assertIn(b'<tr><th colspan="7" class="month">January</th></tr>',
stdout)
for run in self.runners:
output = run('--type', 'html')
year = datetime.datetime.now().year
self.assertIn(('<title>Calendar for %s</title>' % year).encode(), output)
self.assertIn(b'<tr><th colspan="7" class="month">January</th></tr>', output)

def test_html_output_year_encoding(self):
stdout = self.run_ok('-t', 'html', '--encoding', 'ascii', '2004')
self.assertEqual(stdout,
result_2004_html.format(**default_format).encode('ascii'))
for run in self.runners:
output = run('-t', 'html', '--encoding', 'ascii', '2004')
self.assertEqual(output, result_2004_html.format(**default_format).encode('ascii'))

def test_html_output_year_css(self):
self.assertFailure('-t', 'html', '-c')
self.assertFailure('-t', 'html', '--css')
stdout = self.run_ok('-t', 'html', '--css', 'custom.css', '2004')
self.assertIn(b'<link rel="stylesheet" type="text/css" '
b'href="custom.css" />', stdout)
for run in self.runners:
output = run('-t', 'html', '--css', 'custom.css', '2004')
self.assertIn(b'<link rel="stylesheet" type="text/css" '
b'href="custom.css" />', output)


class MiscTestCase(unittest.TestCase):
Expand Down