From 5bb5d5a25e63babbcdc26479f850f96e10592c5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Lavoie?= Date: Sun, 10 Oct 2021 13:03:03 -0500 Subject: [PATCH] scripts: Improve week status shown in README * Handle new edge cases. See source code for details. --- .gitignore | 8 ++ assets/scripts/README.md | 24 ++++ assets/scripts/pytest.ini | 4 + assets/scripts/requirements.txt | 3 + assets/scripts/test_update_week.py | 219 +++++++++++++++++++++++++++++ assets/scripts/update_week.py | 150 +++++++++++++++----- 6 files changed, 372 insertions(+), 36 deletions(-) create mode 100644 assets/scripts/README.md create mode 100644 assets/scripts/pytest.ini create mode 100644 assets/scripts/requirements.txt create mode 100644 assets/scripts/test_update_week.py diff --git a/.gitignore b/.gitignore index 9fdc447b..3da1df22 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,14 @@ # Created by https://www.gitignore.io/api/latex # Edit at https://www.gitignore.io/?templates=latex + +## Hypothesis test framework +.hypothesis/ + +## Virtual environments +.venv/ +venv/ + ### LaTeX ### ## Core latex/pdflatex auxiliary files: *.aux diff --git a/assets/scripts/README.md b/assets/scripts/README.md new file mode 100644 index 00000000..590480be --- /dev/null +++ b/assets/scripts/README.md @@ -0,0 +1,24 @@ +# Using this script + +Install dependencies (preferably inside a [virtual environment](https://docs.python.org/3/library/venv.html)): + + cd assets/scripts + pip install -r requirements.txt + + # or: + pip install -r assets/scripts/requirements.txt + +Run the test suite: + + cd assets/scripts + pytest + + # or: + pytest assets/scripts + +Update the next semester start date: + +- Set the `semester_start_date` parameter of the `main` function to a new date. +- Execute the script from the root directory: + + python assets/scripts/update_week.py diff --git a/assets/scripts/pytest.ini b/assets/scripts/pytest.ini new file mode 100644 index 00000000..ced1a13d --- /dev/null +++ b/assets/scripts/pytest.ini @@ -0,0 +1,4 @@ +[pytest] +addopts = -rsxX --showlocals --tb=short --strict-markers --cov-report term-missing:skip-covered --cov=. --cov-fail-under 100 +testpaths = . +xfail_strict = true diff --git a/assets/scripts/requirements.txt b/assets/scripts/requirements.txt new file mode 100644 index 00000000..456c530a --- /dev/null +++ b/assets/scripts/requirements.txt @@ -0,0 +1,3 @@ +hypothesis==6.23.2 +pytest==6.2.5 +pytest-cov==3.0.0 \ No newline at end of file diff --git a/assets/scripts/test_update_week.py b/assets/scripts/test_update_week.py new file mode 100644 index 00000000..b27f5c82 --- /dev/null +++ b/assets/scripts/test_update_week.py @@ -0,0 +1,219 @@ +# Standard library +from datetime import datetime + +# Third-party libraries +import pytest +from hypothesis import example, given, strategies as st + + +# Local imports +import update_week + + +@given( + st.datetimes( + min_value=datetime(2019, 1, 1), max_value=datetime(2021, 10, 9) + ) +) +@example(datetime(2021, 10, 9)) +def test_get_current_week_number_returns_non_positive_int_before_semester_has_started( + current_date: datetime, +): + """ + Test random dates before starting and force checking for 2 days before. + Exclude Sunday because we will set the week to 1 on the day before the + next semester starts. + + Args: + current_date (datetime): Receive a datetime object from Hypothesis. + """ + next_semester_start_date = datetime(2021, 10, 11) + assert ( + update_week.get_current_week_number( + current_date, next_semester_start_date + ) + <= 0 + ) + + +def test_get_current_week_number_returns_1_on_Sunday_right_before_semester(): + """ + We want to see "Week 1" when we're about to start the next semester. + """ + next_semester_start_date = datetime(2021, 10, 11) # Monday + today = datetime(2021, 10, 10) # Day before starting + result = update_week.get_current_week_number( + today, next_semester_start_date + ) + assert result == 1 + + +@given( + st.datetimes( + min_value=datetime(2021, 10, 11), max_value=datetime(2021, 10, 17) + ) +) +def test_get_current_week_number_returns_1_on_first_week_of_semester( + current_date: datetime, +): + """ + Assumes a semester ends at week 22 (included), which has been the case so far. + Any date within the first week should return 1, including on the following + Sunday. + + Args: + current_date (datetime): Receive a datetime object from Hypothesis. + """ + next_semester_start_date = datetime(2021, 10, 11) # Monday + result = update_week.get_current_week_number( + current_date, next_semester_start_date + ) + assert result == 1 + + +@pytest.mark.parametrize( + "current_date,expected_week", + [ + (datetime(2021, 10, 18), 2), # Monday + (datetime(2021, 10, 19), 2), # Tuesday + (datetime(2021, 10, 24), 2), # Sunday + (datetime(2021, 10, 25), 3), # Monday + (datetime(2021, 12, 29), 12), # Wednesday (last week 2021) + (datetime(2022, 1, 6), 13), # Thursday (first week 2022) + (datetime(2022, 3, 7), 22), # Monday + (datetime(2022, 3, 13), 22), # Sunday + (datetime(2022, 3, 14), 23), # Monday: semester done + ], +) +def test_get_current_week_number_returns_positive_int_when_semester_is_ongoing_or_done( + current_date: datetime, expected_week: int +): + """ + Assumes a semester ends at week 22 (included). + Any date within the ongoing semester should return a positive integer. + + Args: + current_date (datetime): Receive a datetime object from Hypothesis. + """ + next_semester_start_date = datetime(2021, 10, 11) # Monday + result = update_week.get_current_week_number( + current_date, next_semester_start_date + ) + assert result == expected_week + + +@pytest.mark.parametrize( + "current_week", + [23, 24, 25], +) +def test_get_text_to_display_returns_correct_text_when_semester_has_ended( + current_week: int, +): + semester_start_date = datetime(2021, 10, 11) + expected_text = ( + f"- Semester done/ending :tada:. [Week: **{current_week}**]" + ) + result = update_week.get_text_to_display(current_week, semester_start_date) + assert result == expected_text + + +@pytest.mark.parametrize( + "current_week", + [-2, -1, 0], +) +def test_get_text_to_display_returns_correct_text_when_semester_has_yet_to_start( + current_week: int, +): + semester_start_date = datetime(2021, 10, 11) + next_semester_formatted_date = "Monday 11 October 2021" + expected_text = ( + "- Semester done/ending :tada:. Start date " + f"of the next semester: **{next_semester_formatted_date}**." + ) + result = update_week.get_text_to_display(current_week, semester_start_date) + assert result == expected_text + + +@pytest.mark.parametrize( + "current_week", + [1, 2, 11, 12, 13, 20, 21, 22], +) +def test_get_text_to_display_returns_correct_text_when_semester_is_ongoing( + current_week: int, +): + semester_start_date = datetime(2021, 10, 11) + expected_text = f"- Week **{current_week}**." + result = update_week.get_text_to_display(current_week, semester_start_date) + assert result == expected_text + + +@given(st.integers(min_value=26, max_value=42)) +def test_get_text_to_display_returns_warning_message_if_next_semester_start_date_should_be_updated( + current_week: int, +): + """ + If the week number becomes greater than 25, we probably forgot to set the + next semester date if it is known by that point, so display a message to + let users know they can file an issue in the repo. + + If we got beyond week 42, there's probably a bigger issue to worry about + than updating this file, so don't test further... + """ + semester_start_date = datetime(2021, 10, 11) + url = ( + "https://github.com/world-class/REPL/issues/new?labels=bug" + "&title=%5BBUG%5D%20Start%20date%20of%20next%20semester%20should%20be" + "%20displayed%20in%20the%20README" + ) + expected_text = ( + f"- Week **{current_week}**. Did we forget to specify when the next semester" + f" is starting? Please [let us know by filing an issue]({url})!" + ) + result = update_week.get_text_to_display(current_week, semester_start_date) + assert result == expected_text + + +def test_write_text_to_display_in_README(tmpdir): + # Text we want to put in the README file + text_to_display = "- Semester done/ending :tada:. [Week: **12**]" + + # Create a test output to store some README content + file_path = tmpdir.join("output.md") + + # Let's pretend that's all there is in the README + readme_content = """\ +# Have an issue, some feedback or want to contribute? + +There are two main ways blabla... + +--- + +# Current week + +- Something should be written here... + +# • [Frequently Asked Questions (FAQ)](faq/README.md)""" + + # Write that fictional input to the README + file_path.write(readme_content) + + # Execute the function that should update that content + update_week.write_text_to_display_in_readme(text_to_display, file_path) + + # This is the new content we should have in that file + new_readme_content = f"""\ +# Have an issue, some feedback or want to contribute? + +There are two main ways blabla... + +--- + +# Current week + +{text_to_display} + +# • [Frequently Asked Questions (FAQ)](faq/README.md) +""" + + # At this point, we should have updated the file correctly + assert file_path.read() == new_readme_content diff --git a/assets/scripts/update_week.py b/assets/scripts/update_week.py index 609925a1..a023e2c1 100644 --- a/assets/scripts/update_week.py +++ b/assets/scripts/update_week.py @@ -1,39 +1,117 @@ +""" +Update the status of the (ongoing or not) semester by +writing text in the main README.md file. +""" from datetime import datetime, timedelta import fileinput -# This could either be the start date of the current semester or the next one -# if we're already past week 22 in the ongoing semester. -date_monday_start = datetime(2021, 10, 11) -we_are_sure = True - -today = datetime.today() -sunday_start = date_monday_start - timedelta(days=1) -sunday_now = today - timedelta(days=today.weekday()) - -result = int((sunday_now - sunday_start).days / 7 + 1) -if result < 1: - formatted_date = date_monday_start.strftime("%A %-d %B %Y") - start_date_text = "Start date" if we_are_sure else "Tentative start date" - result = ( - f"- Semester done/ending :tada:. {start_date_text} " - f"of the next semester: **{formatted_date}**." - ) -elif result > 22: - result = f"- Semester done/ending :tada:. [Week: **{result}**]" -else: - result = f"- Week **{result}**." - -filename = "README.md" -skip_lines = 2 -curr_line = False -for line in fileinput.input(filename, inplace=True): - line = line.rstrip("\r\n") - if "# Current week" in line: - print(line) - print(f"\n{result}") - curr_line = True - elif curr_line and skip_lines > 0: - skip_lines -= 1 - continue - else: - print(line) + +def main(semester_start_date: datetime = datetime(2021, 10, 11)) -> None: + """ + Main function to execute. Simply update the date parameter. + """ + current_date = datetime.today() + current_week = get_current_week_number(current_date, semester_start_date) + text_to_display = get_text_to_display(current_week, semester_start_date) + write_text_to_display_in_readme(text_to_display) + + +def get_current_week_number( + current_date: datetime, semester_start_date: datetime +) -> int: + """ + Return the current week number. Assumes that one day before the next start + date (Sunday), we set the week number to 1. + + Args: + current_date (datetime): Today's date. + semester_start_date (datetime): Start date of the semester. + + Returns: + int: Current week number relative to semester_start_date. + """ + sunday_start = semester_start_date - timedelta(days=1) + + # If it's the day before the next semester, set the week to 1 + if current_date.date() == sunday_start.date(): + return 1 + + # If it's Sunday already, don't subtract a week (i.e. % 6) + sunday_now = current_date - timedelta(days=(current_date.weekday())) + + # Start counting weeks at 1 (not zero even if it's CS so we match week + # numbers on Coursera ;)) + return int((sunday_now - sunday_start).days / 7 + 1) + + +def get_text_to_display( + current_week: int, semester_start_date: datetime +) -> str: + """ + Returns the text that should be displayed for the status of the current + week in the README. + + Args: + current_week (int): Week number as integer. + semester_start_date (datetime): Start date of the next semester. + + Returns: + str: Text to display in the README. + """ + # In between semesters + if current_week <= 0: + next_semester_formatted_date = semester_start_date.strftime( + "%A %-d %B %Y" + ) + return ( + "- Semester done/ending :tada:. Start date " + f"of the next semester: **{next_semester_formatted_date}**." + ) + # Semester done but next semester date not set yet + if current_week <= 22: + return f"- Week **{current_week}**." + + # It's been a long time since we updated this file... Is it a mistake? + if current_week > 25: + url = ( + "https://github.com/world-class/REPL/issues/new?labels=bug" + "&title=%5BBUG%5D%20Start%20date%20of%20next%20semester%20should%20be" + "%20displayed%20in%20the%20README" + ) + return ( + f"- Week **{current_week}**. Did we forget to specify when the next semester" + f" is starting? Please [let us know by filing an issue]({url})!" + ) + + # Week between 1 and 22 inclusive = semester ongoing + return f"- Semester done/ending :tada:. [Week: **{current_week}**]" + + +def write_text_to_display_in_readme( + text_to_display: str, file_path: str = "README.md" +) -> None: + """ + Rewrite the line below the heading "Current week" in the README. + + Args: + text_to_display (str): What should be written in the README + (replace existing line). + file_path (str, optional): Defaults to "README.md". + """ + skip_lines = 2 + curr_line = False + for line in fileinput.input(file_path, inplace=True): + line = str(line.rstrip()) + if "# Current week" in line: + print(line) + print(f"\n{text_to_display}") + curr_line = True + elif curr_line and skip_lines > 0: + skip_lines -= 1 + continue + else: + print(line) + + +if __name__ == "__main__": + main()